Kstars

qcustomplot.cpp
1/***************************************************************************
2** **
3** QCustomPlot, an easy to use, modern plotting widget for Qt **
4** Copyright (C) 2011-2022 Emanuel Eichhammer **
5** **
6** This program is free software: you can redistribute it and/or modify **
7** it under the terms of the GNU General Public License as published by **
8** the Free Software Foundation, either version 3 of the License, or **
9** (at your option) any later version. **
10** **
11** This program is distributed in the hope that it will be useful, **
12** but WITHOUT ANY WARRANTY; without even the implied warranty of **
13** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
14** GNU General Public License for more details. **
15** **
16** You should have received a copy of the GNU General Public License **
17** along with this program. If not, see http://www.gnu.org/licenses/. **
18** **
19****************************************************************************
20** Author: Emanuel Eichhammer **
21** Website/Contact: https://www.qcustomplot.com/ **
22** Date: 06.11.22 **
23** Version: 2.1.1 **
24****************************************************************************/
25
26#include "qcustomplot.h"
27
28
29/* including file 'src/vector2d.cpp' */
30/* modified 2022-11-06T12:45:56, size 7973 */
31
32////////////////////////////////////////////////////////////////////////////////////////////////////
33//////////////////// QCPVector2D
34////////////////////////////////////////////////////////////////////////////////////////////////////
35
36/*! \class QCPVector2D
37 \brief Represents two doubles as a mathematical 2D vector
38
39 This class acts as a replacement for QVector2D with the advantage of double precision instead of
40 single, and some convenience methods tailored for the QCustomPlot library.
41*/
42
43/* start documentation of inline functions */
44
45/*! \fn void QCPVector2D::setX(double x)
46
47 Sets the x coordinate of this vector to \a x.
48
49 \see setY
50*/
51
52/*! \fn void QCPVector2D::setY(double y)
53
54 Sets the y coordinate of this vector to \a y.
55
56 \see setX
57*/
58
59/*! \fn double QCPVector2D::length() const
60
61 Returns the length of this vector.
62
63 \see lengthSquared
64*/
65
66/*! \fn double QCPVector2D::lengthSquared() const
67
68 Returns the squared length of this vector. In some situations, e.g. when just trying to find the
69 shortest vector of a group, this is faster than calculating \ref length, because it avoids
70 calculation of a square root.
71
72 \see length
73*/
74
75/*! \fn double QCPVector2D::angle() const
76
77 Returns the angle of the vector in radians. The angle is measured between the positive x line and
78 the vector, counter-clockwise in a mathematical coordinate system (y axis upwards positive). In
79 screen/widget coordinates where the y axis is inverted, the angle appears clockwise.
80*/
81
82/*! \fn QPoint QCPVector2D::toPoint() const
83
84 Returns a QPoint which has the x and y coordinates of this vector, truncating any floating point
85 information.
86
87 \see toPointF
88*/
89
90/*! \fn QPointF QCPVector2D::toPointF() const
91
92 Returns a QPointF which has the x and y coordinates of this vector.
93
94 \see toPoint
95*/
96
97/*! \fn bool QCPVector2D::isNull() const
98
99 Returns whether this vector is null. A vector is null if \c qIsNull returns true for both x and y
100 coordinates, i.e. if both are binary equal to 0.
101*/
102
103/*! \fn QCPVector2D QCPVector2D::perpendicular() const
104
105 Returns a vector perpendicular to this vector, with the same length.
106*/
107
108/*! \fn double QCPVector2D::dot() const
109
110 Returns the dot/scalar product of this vector with the specified vector \a vec.
111*/
112
113/* end documentation of inline functions */
114
115/*!
116 Creates a QCPVector2D object and initializes the x and y coordinates to 0.
117*/
119 mX(0),
120 mY(0)
121{
122}
123
124/*!
125 Creates a QCPVector2D object and initializes the \a x and \a y coordinates with the specified
126 values.
127*/
128QCPVector2D::QCPVector2D(double x, double y) :
129 mX(x),
130 mY(y)
131{
132}
133
134/*!
135 Creates a QCPVector2D object and initializes the x and y coordinates respective coordinates of
136 the specified \a point.
137*/
139 mX(point.x()),
140 mY(point.y())
141{
142}
143
144/*!
145 Creates a QCPVector2D object and initializes the x and y coordinates respective coordinates of
146 the specified \a point.
147*/
149 mX(point.x()),
150 mY(point.y())
151{
152}
153
154/*!
155 Normalizes this vector. After this operation, the length of the vector is equal to 1.
156
157 If the vector has both entries set to zero, this method does nothing.
158
159 \see normalized, length, lengthSquared
160*/
162{
163 if (mX == 0.0 && mY == 0.0) return;
164 const double lenInv = 1.0/length();
165 mX *= lenInv;
166 mY *= lenInv;
167}
168
169/*!
170 Returns a normalized version of this vector. The length of the returned vector is equal to 1.
171
172 If the vector has both entries set to zero, this method returns the vector unmodified.
173
174 \see normalize, length, lengthSquared
175*/
177{
178 if (mX == 0.0 && mY == 0.0) return *this;
179 const double lenInv = 1.0/length();
180 return QCPVector2D(mX*lenInv, mY*lenInv);
181}
182
183/*! \overload
184
185 Returns the squared shortest distance of this vector (interpreted as a point) to the finite line
186 segment given by \a start and \a end.
187
188 \see distanceToStraightLine
189*/
190double QCPVector2D::distanceSquaredToLine(const QCPVector2D &start, const QCPVector2D &end) const
191{
192 const QCPVector2D v(end-start);
193 const double vLengthSqr = v.lengthSquared();
194 if (!qFuzzyIsNull(vLengthSqr))
195 {
196 const double mu = v.dot(*this-start)/vLengthSqr;
197 if (mu < 0)
198 return (*this-start).lengthSquared();
199 else if (mu > 1)
200 return (*this-end).lengthSquared();
201 else
202 return ((start + mu*v)-*this).lengthSquared();
203 } else
204 return (*this-start).lengthSquared();
205}
206
207/*! \overload
208
209 Returns the squared shortest distance of this vector (interpreted as a point) to the finite line
210 segment given by \a line.
211
212 \see distanceToStraightLine
213*/
215{
216 return distanceSquaredToLine(QCPVector2D(line.p1()), QCPVector2D(line.p2()));
217}
218
219/*!
220 Returns the shortest distance of this vector (interpreted as a point) to the infinite straight
221 line given by a \a base point and a \a direction vector.
222
223 \see distanceSquaredToLine
224*/
225double QCPVector2D::distanceToStraightLine(const QCPVector2D &base, const QCPVector2D &direction) const
226{
227 return qAbs((*this-base).dot(direction.perpendicular()))/direction.length();
228}
229
230/*!
231 Scales this vector by the given \a factor, i.e. the x and y components are multiplied by \a
232 factor.
233*/
235{
236 mX *= factor;
237 mY *= factor;
238 return *this;
239}
240
241/*!
242 Scales this vector by the given \a divisor, i.e. the x and y components are divided by \a
243 divisor.
244*/
246{
247 mX /= divisor;
248 mY /= divisor;
249 return *this;
250}
251
252/*!
253 Adds the given \a vector to this vector component-wise.
254*/
256{
257 mX += vector.mX;
258 mY += vector.mY;
259 return *this;
260}
261
262/*!
263 subtracts the given \a vector from this vector component-wise.
264*/
266{
267 mX -= vector.mX;
268 mY -= vector.mY;
269 return *this;
270}
271/* end of 'src/vector2d.cpp' */
272
273
274/* including file 'src/painter.cpp' */
275/* modified 2022-11-06T12:45:56, size 8656 */
276
277////////////////////////////////////////////////////////////////////////////////////////////////////
278//////////////////// QCPPainter
279////////////////////////////////////////////////////////////////////////////////////////////////////
280
281/*! \class QCPPainter
282 \brief QPainter subclass used internally
283
284 This QPainter subclass is used to provide some extended functionality e.g. for tweaking position
285 consistency between antialiased and non-antialiased painting. Further it provides workarounds
286 for QPainter quirks.
287
288 \warning This class intentionally hides non-virtual functions of QPainter, e.g. setPen, save and
289 restore. So while it is possible to pass a QCPPainter instance to a function that expects a
290 QPainter pointer, some of the workarounds and tweaks will be unavailable to the function (because
291 it will call the base class implementations of the functions actually hidden by QCPPainter).
292*/
293
294/*!
295 Creates a new QCPPainter instance and sets default values
296*/
298 mModes(pmDefault),
299 mIsAntialiasing(false)
300{
301 // don't setRenderHint(QPainter::NonCosmeticDefautPen) here, because painter isn't active yet and
302 // a call to begin() will follow
303}
304
305/*!
306 Creates a new QCPPainter instance on the specified paint \a device and sets default values. Just
307 like the analogous QPainter constructor, begins painting on \a device immediately.
308
309 Like \ref begin, this method sets QPainter::NonCosmeticDefaultPen in Qt versions before Qt5.
310*/
312 QPainter(device),
313 mModes(pmDefault),
314 mIsAntialiasing(false)
315{
316#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) // before Qt5, default pens used to be cosmetic if NonCosmeticDefaultPen flag isn't set. So we set it to get consistency across Qt versions.
317 if (isActive())
318 setRenderHint(QPainter::NonCosmeticDefaultPen);
319#endif
320}
321
322/*!
323 Sets the pen of the painter and applies certain fixes to it, depending on the mode of this
324 QCPPainter.
325
326 \note this function hides the non-virtual base class implementation.
327*/
328void QCPPainter::setPen(const QPen &pen)
329{
331 if (mModes.testFlag(pmNonCosmetic))
333}
334
335/*! \overload
336
337 Sets the pen (by color) of the painter and applies certain fixes to it, depending on the mode of
338 this QCPPainter.
339
340 \note this function hides the non-virtual base class implementation.
341*/
342void QCPPainter::setPen(const QColor &color)
343{
344 QPainter::setPen(color);
345 if (mModes.testFlag(pmNonCosmetic))
347}
348
349/*! \overload
350
351 Sets the pen (by style) of the painter and applies certain fixes to it, depending on the mode of
352 this QCPPainter.
353
354 \note this function hides the non-virtual base class implementation.
355*/
357{
358 QPainter::setPen(penStyle);
359 if (mModes.testFlag(pmNonCosmetic))
361}
362
363/*! \overload
364
365 Works around a Qt bug introduced with Qt 4.8 which makes drawing QLineF unpredictable when
366 antialiasing is disabled. Thus when antialiasing is disabled, it rounds the \a line to
367 integer coordinates and then passes it to the original drawLine.
368
369 \note this function hides the non-virtual base class implementation.
370*/
372{
373 if (mIsAntialiasing || mModes.testFlag(pmVectorized))
374 QPainter::drawLine(line);
375 else
377}
378
379/*!
380 Sets whether painting uses antialiasing or not. Use this method instead of using setRenderHint
381 with QPainter::Antialiasing directly, as it allows QCPPainter to regain pixel exactness between
382 antialiased and non-antialiased painting (Since Qt < 5.0 uses slightly different coordinate systems for
383 AA/Non-AA painting).
384*/
386{
388 if (mIsAntialiasing != enabled)
389 {
390 mIsAntialiasing = enabled;
391 if (!mModes.testFlag(pmVectorized)) // antialiasing half-pixel shift only needed for rasterized outputs
392 {
393 if (mIsAntialiasing)
394 translate(0.5, 0.5);
395 else
396 translate(-0.5, -0.5);
397 }
398 }
399}
400
401/*!
402 Sets the mode of the painter. This controls whether the painter shall adjust its
403 fixes/workarounds optimized for certain output devices.
404*/
406{
407 mModes = modes;
408}
409
410/*!
411 Sets the QPainter::NonCosmeticDefaultPen in Qt versions before Qt5 after beginning painting on \a
412 device. This is necessary to get cosmetic pen consistency across Qt versions, because since Qt5,
413 all pens are non-cosmetic by default, and in Qt4 this render hint must be set to get that
414 behaviour.
415
416 The Constructor \ref QCPPainter(QPaintDevice *device) which directly starts painting also sets
417 the render hint as appropriate.
418
419 \note this function hides the non-virtual base class implementation.
420*/
422{
423 bool result = QPainter::begin(device);
424#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) // before Qt5, default pens used to be cosmetic if NonCosmeticDefaultPen flag isn't set. So we set it to get consistency across Qt versions.
425 if (result)
426 setRenderHint(QPainter::NonCosmeticDefaultPen);
427#endif
428 return result;
429}
430
431/*! \overload
432
433 Sets the mode of the painter. This controls whether the painter shall adjust its
434 fixes/workarounds optimized for certain output devices.
435*/
437{
438 if (!enabled && mModes.testFlag(mode))
439 mModes &= ~mode;
440 else if (enabled && !mModes.testFlag(mode))
441 mModes |= mode;
442}
443
444/*!
445 Saves the painter (see QPainter::save). Since QCPPainter adds some new internal state to
446 QPainter, the save/restore functions are reimplemented to also save/restore those members.
447
448 \note this function hides the non-virtual base class implementation.
449
450 \see restore
451*/
453{
454 mAntialiasingStack.push(mIsAntialiasing);
456}
457
458/*!
459 Restores the painter (see QPainter::restore). Since QCPPainter adds some new internal state to
460 QPainter, the save/restore functions are reimplemented to also save/restore those members.
461
462 \note this function hides the non-virtual base class implementation.
463
464 \see save
465*/
467{
468 if (!mAntialiasingStack.isEmpty())
469 mIsAntialiasing = mAntialiasingStack.pop();
470 else
471 qDebug() << Q_FUNC_INFO << "Unbalanced save/restore";
473}
474
475/*!
476 Changes the pen width to 1 if it currently is 0. This function is called in the \ref setPen
477 overrides when the \ref pmNonCosmetic mode is set.
478*/
480{
481 if (qFuzzyIsNull(pen().widthF()))
482 {
483 QPen p = pen();
484 p.setWidth(1);
486 }
487}
488/* end of 'src/painter.cpp' */
489
490
491/* including file 'src/paintbuffer.cpp' */
492/* modified 2022-11-06T12:45:56, size 18915 */
493
494////////////////////////////////////////////////////////////////////////////////////////////////////
495//////////////////// QCPAbstractPaintBuffer
496////////////////////////////////////////////////////////////////////////////////////////////////////
497
498/*! \class QCPAbstractPaintBuffer
499 \brief The abstract base class for paint buffers, which define the rendering backend
500
501 This abstract base class defines the basic interface that a paint buffer needs to provide in
502 order to be usable by QCustomPlot.
503
504 A paint buffer manages both a surface to draw onto, and the matching paint device. The size of
505 the surface can be changed via \ref setSize. External classes (\ref QCustomPlot and \ref
506 QCPLayer) request a painter via \ref startPainting and then perform the draw calls. Once the
507 painting is complete, \ref donePainting is called, so the paint buffer implementation can do
508 clean up if necessary. Before rendering a frame, each paint buffer is usually filled with a color
509 using \ref clear (usually the color is \c Qt::transparent), to remove the contents of the
510 previous frame.
511
512 The simplest paint buffer implementation is \ref QCPPaintBufferPixmap which allows regular
513 software rendering via the raster engine. Hardware accelerated rendering via pixel buffers and
514 frame buffer objects is provided by \ref QCPPaintBufferGlPbuffer and \ref QCPPaintBufferGlFbo.
515 They are used automatically if \ref QCustomPlot::setOpenGl is enabled.
516*/
517
518/* start documentation of pure virtual functions */
519
520/*! \fn virtual QCPPainter *QCPAbstractPaintBuffer::startPainting() = 0
521
522 Returns a \ref QCPPainter which is ready to draw to this buffer. The ownership and thus the
523 responsibility to delete the painter after the painting operations are complete is given to the
524 caller of this method.
525
526 Once you are done using the painter, delete the painter and call \ref donePainting.
527
528 While a painter generated with this method is active, you must not call \ref setSize, \ref
529 setDevicePixelRatio or \ref clear.
530
531 This method may return 0, if a painter couldn't be activated on the buffer. This usually
532 indicates a problem with the respective painting backend.
533*/
534
535/*! \fn virtual void QCPAbstractPaintBuffer::draw(QCPPainter *painter) const = 0
536
537 Draws the contents of this buffer with the provided \a painter. This is the method that is used
538 to finally join all paint buffers and draw them onto the screen.
539*/
540
541/*! \fn virtual void QCPAbstractPaintBuffer::clear(const QColor &color) = 0
542
543 Fills the entire buffer with the provided \a color. To have an empty transparent buffer, use the
544 named color \c Qt::transparent.
545
546 This method must not be called if there is currently a painter (acquired with \ref startPainting)
547 active.
548*/
549
550/*! \fn virtual void QCPAbstractPaintBuffer::reallocateBuffer() = 0
551
552 Reallocates the internal buffer with the currently configured size (\ref setSize) and device
553 pixel ratio, if applicable (\ref setDevicePixelRatio). It is called as soon as any of those
554 properties are changed on this paint buffer.
555
556 \note Subclasses of \ref QCPAbstractPaintBuffer must call their reimplementation of this method
557 in their constructor, to perform the first allocation (this can not be done by the base class
558 because calling pure virtual methods in base class constructors is not possible).
559*/
560
561/* end documentation of pure virtual functions */
562/* start documentation of inline functions */
563
564/*! \fn virtual void QCPAbstractPaintBuffer::donePainting()
565
566 If you have acquired a \ref QCPPainter to paint onto this paint buffer via \ref startPainting,
567 call this method as soon as you are done with the painting operations and have deleted the
568 painter.
569
570 paint buffer subclasses may use this method to perform any type of cleanup that is necessary. The
571 default implementation does nothing.
572*/
573
574/* end documentation of inline functions */
575
576/*!
577 Creates a paint buffer and initializes it with the provided \a size and \a devicePixelRatio.
578
579 Subclasses must call their \ref reallocateBuffer implementation in their respective constructors.
580*/
581QCPAbstractPaintBuffer::QCPAbstractPaintBuffer(const QSize &size, double devicePixelRatio) :
582 mSize(size),
583 mDevicePixelRatio(devicePixelRatio),
584 mInvalidated(true)
585{
586}
587
588QCPAbstractPaintBuffer::~QCPAbstractPaintBuffer()
589{
590}
591
592/*!
593 Sets the paint buffer size.
594
595 The buffer is reallocated (by calling \ref reallocateBuffer), so any painters that were obtained
596 by \ref startPainting are invalidated and must not be used after calling this method.
597
598 If \a size is already the current buffer size, this method does nothing.
599*/
601{
602 if (mSize != size)
603 {
604 mSize = size;
606 }
607}
608
609/*!
610 Sets the invalidated flag to \a invalidated.
611
612 This mechanism is used internally in conjunction with isolated replotting of \ref QCPLayer
613 instances (in \ref QCPLayer::lmBuffered mode). If \ref QCPLayer::replot is called on a buffered
614 layer, i.e. an isolated repaint of only that layer (and its dedicated paint buffer) is requested,
615 QCustomPlot will decide depending on the invalidated flags of other paint buffers whether it also
616 replots them, instead of only the layer on which the replot was called.
617
618 The invalidated flag is set to true when \ref QCPLayer association has changed, i.e. if layers
619 were added or removed from this buffer, or if they were reordered. It is set to false as soon as
620 all associated \ref QCPLayer instances are drawn onto the buffer.
621
622 Under normal circumstances, it is not necessary to manually call this method.
623*/
625{
626 mInvalidated = invalidated;
627}
628
629/*!
630 Sets the device pixel ratio to \a ratio. This is useful to render on high-DPI output devices.
631 The ratio is automatically set to the device pixel ratio used by the parent QCustomPlot instance.
632
633 The buffer is reallocated (by calling \ref reallocateBuffer), so any painters that were obtained
634 by \ref startPainting are invalidated and must not be used after calling this method.
635
636 \note This method is only available for Qt versions 5.4 and higher.
637*/
639{
640 if (!qFuzzyCompare(ratio, mDevicePixelRatio))
641 {
642#ifdef QCP_DEVICEPIXELRATIO_SUPPORTED
643 mDevicePixelRatio = ratio;
645#else
646 qDebug() << Q_FUNC_INFO << "Device pixel ratios not supported for Qt versions before 5.4";
647 mDevicePixelRatio = 1.0;
648#endif
649 }
650}
651
652////////////////////////////////////////////////////////////////////////////////////////////////////
653//////////////////// QCPPaintBufferPixmap
654////////////////////////////////////////////////////////////////////////////////////////////////////
655
656/*! \class QCPPaintBufferPixmap
657 \brief A paint buffer based on QPixmap, using software raster rendering
658
659 This paint buffer is the default and fall-back paint buffer which uses software rendering and
660 QPixmap as internal buffer. It is used if \ref QCustomPlot::setOpenGl is false.
661*/
662
663/*!
664 Creates a pixmap paint buffer instancen with the specified \a size and \a devicePixelRatio, if
665 applicable.
666*/
667QCPPaintBufferPixmap::QCPPaintBufferPixmap(const QSize &size, double devicePixelRatio) :
668 QCPAbstractPaintBuffer(size, devicePixelRatio)
669{
671}
672
673QCPPaintBufferPixmap::~QCPPaintBufferPixmap()
674{
675}
676
677/* inherits documentation from base class */
679{
680 QCPPainter *result = new QCPPainter(&mBuffer);
681#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
682 result->setRenderHint(QPainter::HighQualityAntialiasing);
683#endif
684 return result;
685}
686
687/* inherits documentation from base class */
689{
690 if (painter && painter->isActive())
691 painter->drawPixmap(0, 0, mBuffer);
692 else
693 qDebug() << Q_FUNC_INFO << "invalid or inactive painter passed";
694}
695
696/* inherits documentation from base class */
698{
699 mBuffer.fill(color);
700}
701
702/* inherits documentation from base class */
704{
706 if (!qFuzzyCompare(1.0, mDevicePixelRatio))
707 {
708#ifdef QCP_DEVICEPIXELRATIO_SUPPORTED
709 mBuffer = QPixmap(mSize*mDevicePixelRatio);
710 mBuffer.setDevicePixelRatio(mDevicePixelRatio);
711#else
712 qDebug() << Q_FUNC_INFO << "Device pixel ratios not supported for Qt versions before 5.4";
713 mDevicePixelRatio = 1.0;
714 mBuffer = QPixmap(mSize);
715#endif
716 } else
717 {
718 mBuffer = QPixmap(mSize);
719 }
720}
721
722
723#ifdef QCP_OPENGL_PBUFFER
724////////////////////////////////////////////////////////////////////////////////////////////////////
725//////////////////// QCPPaintBufferGlPbuffer
726////////////////////////////////////////////////////////////////////////////////////////////////////
727
728/*! \class QCPPaintBufferGlPbuffer
729 \brief A paint buffer based on OpenGL pixel buffers, using hardware accelerated rendering
730
731 This paint buffer is one of the OpenGL paint buffers which facilitate hardware accelerated plot
732 rendering. It is based on OpenGL pixel buffers (pbuffer) and is used in Qt versions before 5.0.
733 (See \ref QCPPaintBufferGlFbo used in newer Qt versions.)
734
735 The OpenGL paint buffers are used if \ref QCustomPlot::setOpenGl is set to true, and if they are
736 supported by the system.
737*/
738
739/*!
740 Creates a \ref QCPPaintBufferGlPbuffer instance with the specified \a size and \a
741 devicePixelRatio, if applicable.
742
743 The parameter \a multisamples defines how many samples are used per pixel. Higher values thus
744 result in higher quality antialiasing. If the specified \a multisamples value exceeds the
745 capability of the graphics hardware, the highest supported multisampling is used.
746*/
747QCPPaintBufferGlPbuffer::QCPPaintBufferGlPbuffer(const QSize &size, double devicePixelRatio, int multisamples) :
748 QCPAbstractPaintBuffer(size, devicePixelRatio),
749 mGlPBuffer(0),
750 mMultisamples(qMax(0, multisamples))
751{
752 QCPPaintBufferGlPbuffer::reallocateBuffer();
753}
754
755QCPPaintBufferGlPbuffer::~QCPPaintBufferGlPbuffer()
756{
757 if (mGlPBuffer)
758 delete mGlPBuffer;
759}
760
761/* inherits documentation from base class */
762QCPPainter *QCPPaintBufferGlPbuffer::startPainting()
763{
764 if (!mGlPBuffer->isValid())
765 {
766 qDebug() << Q_FUNC_INFO << "OpenGL frame buffer object doesn't exist, reallocateBuffer was not called?";
767 return 0;
768 }
769
770 QCPPainter *result = new QCPPainter(mGlPBuffer);
771 result->setRenderHint(QPainter::HighQualityAntialiasing);
772 return result;
773}
774
775/* inherits documentation from base class */
776void QCPPaintBufferGlPbuffer::draw(QCPPainter *painter) const
777{
778 if (!painter || !painter->isActive())
779 {
780 qDebug() << Q_FUNC_INFO << "invalid or inactive painter passed";
781 return;
782 }
783 if (!mGlPBuffer->isValid())
784 {
785 qDebug() << Q_FUNC_INFO << "OpenGL pbuffer isn't valid, reallocateBuffer was not called?";
786 return;
787 }
788 painter->drawImage(0, 0, mGlPBuffer->toImage());
789}
790
791/* inherits documentation from base class */
792void QCPPaintBufferGlPbuffer::clear(const QColor &color)
793{
794 if (mGlPBuffer->isValid())
795 {
796 mGlPBuffer->makeCurrent();
797 glClearColor(color.redF(), color.greenF(), color.blueF(), color.alphaF());
798 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
799 mGlPBuffer->doneCurrent();
800 } else
801 qDebug() << Q_FUNC_INFO << "OpenGL pbuffer invalid or context not current";
802}
803
804/* inherits documentation from base class */
805void QCPPaintBufferGlPbuffer::reallocateBuffer()
806{
807 if (mGlPBuffer)
808 delete mGlPBuffer;
809
810 QGLFormat format;
811 format.setAlpha(true);
812 format.setSamples(mMultisamples);
813 mGlPBuffer = new QGLPixelBuffer(mSize, format);
814}
815#endif // QCP_OPENGL_PBUFFER
816
817
818#ifdef QCP_OPENGL_FBO
819////////////////////////////////////////////////////////////////////////////////////////////////////
820//////////////////// QCPPaintBufferGlFbo
821////////////////////////////////////////////////////////////////////////////////////////////////////
822
823/*! \class QCPPaintBufferGlFbo
824 \brief A paint buffer based on OpenGL frame buffers objects, using hardware accelerated rendering
825
826 This paint buffer is one of the OpenGL paint buffers which facilitate hardware accelerated plot
827 rendering. It is based on OpenGL frame buffer objects (fbo) and is used in Qt versions 5.0 and
828 higher. (See \ref QCPPaintBufferGlPbuffer used in older Qt versions.)
829
830 The OpenGL paint buffers are used if \ref QCustomPlot::setOpenGl is set to true, and if they are
831 supported by the system.
832*/
833
834/*!
835 Creates a \ref QCPPaintBufferGlFbo instance with the specified \a size and \a devicePixelRatio,
836 if applicable.
837
838 All frame buffer objects shall share one OpenGL context and paint device, which need to be set up
839 externally and passed via \a glContext and \a glPaintDevice. The set-up is done in \ref
840 QCustomPlot::setupOpenGl and the context and paint device are managed by the parent QCustomPlot
841 instance.
842*/
843QCPPaintBufferGlFbo::QCPPaintBufferGlFbo(const QSize &size, double devicePixelRatio, QWeakPointer<QOpenGLContext> glContext, QWeakPointer<QOpenGLPaintDevice> glPaintDevice) :
844 QCPAbstractPaintBuffer(size, devicePixelRatio),
845 mGlContext(glContext),
846 mGlPaintDevice(glPaintDevice),
847 mGlFrameBuffer(0)
848{
849 QCPPaintBufferGlFbo::reallocateBuffer();
850}
851
852QCPPaintBufferGlFbo::~QCPPaintBufferGlFbo()
853{
854 if (mGlFrameBuffer)
855 delete mGlFrameBuffer;
856}
857
858/* inherits documentation from base class */
859QCPPainter *QCPPaintBufferGlFbo::startPainting()
860{
861 QSharedPointer<QOpenGLPaintDevice> paintDevice = mGlPaintDevice.toStrongRef();
862 QSharedPointer<QOpenGLContext> context = mGlContext.toStrongRef();
863 if (!paintDevice)
864 {
865 qDebug() << Q_FUNC_INFO << "OpenGL paint device doesn't exist";
866 return 0;
867 }
868 if (!context)
869 {
870 qDebug() << Q_FUNC_INFO << "OpenGL context doesn't exist";
871 return 0;
872 }
873 if (!mGlFrameBuffer)
874 {
875 qDebug() << Q_FUNC_INFO << "OpenGL frame buffer object doesn't exist, reallocateBuffer was not called?";
876 return 0;
877 }
878
879 if (QOpenGLContext::currentContext() != context.data())
880 context->makeCurrent(context->surface());
881 mGlFrameBuffer->bind();
882 QCPPainter *result = new QCPPainter(paintDevice.data());
883#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
884 result->setRenderHint(QPainter::HighQualityAntialiasing);
885#endif
886 return result;
887}
888
889/* inherits documentation from base class */
890void QCPPaintBufferGlFbo::donePainting()
891{
892 if (mGlFrameBuffer && mGlFrameBuffer->isBound())
893 mGlFrameBuffer->release();
894 else
895 qDebug() << Q_FUNC_INFO << "Either OpenGL frame buffer not valid or was not bound";
896}
897
898/* inherits documentation from base class */
899void QCPPaintBufferGlFbo::draw(QCPPainter *painter) const
900{
901 if (!painter || !painter->isActive())
902 {
903 qDebug() << Q_FUNC_INFO << "invalid or inactive painter passed";
904 return;
905 }
906 if (!mGlFrameBuffer)
907 {
908 qDebug() << Q_FUNC_INFO << "OpenGL frame buffer object doesn't exist, reallocateBuffer was not called?";
909 return;
910 }
911 painter->drawImage(0, 0, mGlFrameBuffer->toImage());
912}
913
914/* inherits documentation from base class */
915void QCPPaintBufferGlFbo::clear(const QColor &color)
916{
917 QSharedPointer<QOpenGLContext> context = mGlContext.toStrongRef();
918 if (!context)
919 {
920 qDebug() << Q_FUNC_INFO << "OpenGL context doesn't exist";
921 return;
922 }
923 if (!mGlFrameBuffer)
924 {
925 qDebug() << Q_FUNC_INFO << "OpenGL frame buffer object doesn't exist, reallocateBuffer was not called?";
926 return;
927 }
928
929 if (QOpenGLContext::currentContext() != context.data())
930 context->makeCurrent(context->surface());
931 mGlFrameBuffer->bind();
932 glClearColor(color.redF(), color.greenF(), color.blueF(), color.alphaF());
933 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
934 mGlFrameBuffer->release();
935}
936
937/* inherits documentation from base class */
938void QCPPaintBufferGlFbo::reallocateBuffer()
939{
940 // release and delete possibly existing framebuffer:
941 if (mGlFrameBuffer)
942 {
943 if (mGlFrameBuffer->isBound())
944 mGlFrameBuffer->release();
945 delete mGlFrameBuffer;
946 mGlFrameBuffer = 0;
947 }
948
949 QSharedPointer<QOpenGLPaintDevice> paintDevice = mGlPaintDevice.toStrongRef();
950 QSharedPointer<QOpenGLContext> context = mGlContext.toStrongRef();
951 if (!paintDevice)
952 {
953 qDebug() << Q_FUNC_INFO << "OpenGL paint device doesn't exist";
954 return;
955 }
956 if (!context)
957 {
958 qDebug() << Q_FUNC_INFO << "OpenGL context doesn't exist";
959 return;
960 }
961
962 // create new fbo with appropriate size:
963 context->makeCurrent(context->surface());
964 QOpenGLFramebufferObjectFormat frameBufferFormat;
965 frameBufferFormat.setSamples(context->format().samples());
967 mGlFrameBuffer = new QOpenGLFramebufferObject(mSize*mDevicePixelRatio, frameBufferFormat);
968 if (paintDevice->size() != mSize*mDevicePixelRatio)
969 paintDevice->setSize(mSize*mDevicePixelRatio);
970#ifdef QCP_DEVICEPIXELRATIO_SUPPORTED
971 paintDevice->setDevicePixelRatio(mDevicePixelRatio);
972#endif
973}
974#endif // QCP_OPENGL_FBO
975/* end of 'src/paintbuffer.cpp' */
976
977
978/* including file 'src/layer.cpp' */
979/* modified 2022-11-06T12:45:56, size 37615 */
980
981////////////////////////////////////////////////////////////////////////////////////////////////////
982//////////////////// QCPLayer
983////////////////////////////////////////////////////////////////////////////////////////////////////
984
985/*! \class QCPLayer
986 \brief A layer that may contain objects, to control the rendering order
987
988 The Layering system of QCustomPlot is the mechanism to control the rendering order of the
989 elements inside the plot.
990
991 It is based on the two classes QCPLayer and QCPLayerable. QCustomPlot holds an ordered list of
992 one or more instances of QCPLayer (see QCustomPlot::addLayer, QCustomPlot::layer,
993 QCustomPlot::moveLayer, etc.). When replotting, QCustomPlot goes through the list of layers
994 bottom to top and successively draws the layerables of the layers into the paint buffer(s).
995
996 A QCPLayer contains an ordered list of QCPLayerable instances. QCPLayerable is an abstract base
997 class from which almost all visible objects derive, like axes, grids, graphs, items, etc.
998
999 \section qcplayer-defaultlayers Default layers
1000
1001 Initially, QCustomPlot has six layers: "background", "grid", "main", "axes", "legend" and
1002 "overlay" (in that order). On top is the "overlay" layer, which only contains the QCustomPlot's
1003 selection rect (\ref QCustomPlot::selectionRect). The next two layers "axes" and "legend" contain
1004 the default axes and legend, so they will be drawn above plottables. In the middle, there is the
1005 "main" layer. It is initially empty and set as the current layer (see
1006 QCustomPlot::setCurrentLayer). This means, all new plottables, items etc. are created on this
1007 layer by default. Then comes the "grid" layer which contains the QCPGrid instances (which belong
1008 tightly to QCPAxis, see \ref QCPAxis::grid). The Axis rect background shall be drawn behind
1009 everything else, thus the default QCPAxisRect instance is placed on the "background" layer. Of
1010 course, the layer affiliation of the individual objects can be changed as required (\ref
1011 QCPLayerable::setLayer).
1012
1013 \section qcplayer-ordering Controlling the rendering order via layers
1014
1015 Controlling the ordering of layerables in the plot is easy: Create a new layer in the position
1016 you want the layerable to be in, e.g. above "main", with \ref QCustomPlot::addLayer. Then set the
1017 current layer with \ref QCustomPlot::setCurrentLayer to that new layer and finally create the
1018 objects normally. They will be placed on the new layer automatically, due to the current layer
1019 setting. Alternatively you could have also ignored the current layer setting and just moved the
1020 objects with \ref QCPLayerable::setLayer to the desired layer after creating them.
1021
1022 It is also possible to move whole layers. For example, If you want the grid to be shown in front
1023 of all plottables/items on the "main" layer, just move it above "main" with
1024 QCustomPlot::moveLayer.
1025
1026 The rendering order within one layer is simply by order of creation or insertion. The item
1027 created last (or added last to the layer), is drawn on top of all other objects on that layer.
1028
1029 When a layer is deleted, the objects on it are not deleted with it, but fall on the layer below
1030 the deleted layer, see QCustomPlot::removeLayer.
1031
1032 \section qcplayer-buffering Replotting only a specific layer
1033
1034 If the layer mode (\ref setMode) is set to \ref lmBuffered, you can replot only this specific
1035 layer by calling \ref replot. In certain situations this can provide better replot performance,
1036 compared with a full replot of all layers. Upon creation of a new layer, the layer mode is
1037 initialized to \ref lmLogical. The only layer that is set to \ref lmBuffered in a new \ref
1038 QCustomPlot instance is the "overlay" layer, containing the selection rect.
1039*/
1040
1041/* start documentation of inline functions */
1042
1043/*! \fn QList<QCPLayerable*> QCPLayer::children() const
1044
1045 Returns a list of all layerables on this layer. The order corresponds to the rendering order:
1046 layerables with higher indices are drawn above layerables with lower indices.
1047*/
1048
1049/*! \fn int QCPLayer::index() const
1050
1051 Returns the index this layer has in the QCustomPlot. The index is the integer number by which this layer can be
1052 accessed via \ref QCustomPlot::layer.
1053
1054 Layers with higher indices will be drawn above layers with lower indices.
1055*/
1056
1057/* end documentation of inline functions */
1058
1059/*!
1060 Creates a new QCPLayer instance.
1061
1062 Normally you shouldn't directly instantiate layers, use \ref QCustomPlot::addLayer instead.
1063
1064 \warning It is not checked that \a layerName is actually a unique layer name in \a parentPlot.
1065 This check is only performed by \ref QCustomPlot::addLayer.
1066*/
1067QCPLayer::QCPLayer(QCustomPlot *parentPlot, const QString &layerName) :
1068 QObject(parentPlot),
1069 mParentPlot(parentPlot),
1070 mName(layerName),
1071 mIndex(-1), // will be set to a proper value by the QCustomPlot layer creation function
1072 mVisible(true),
1073 mMode(lmLogical)
1074{
1075 // Note: no need to make sure layerName is unique, because layer
1076 // management is done with QCustomPlot functions.
1077}
1078
1079QCPLayer::~QCPLayer()
1080{
1081 // If child layerables are still on this layer, detach them, so they don't try to reach back to this
1082 // then invalid layer once they get deleted/moved themselves. This only happens when layers are deleted
1083 // directly, like in the QCustomPlot destructor. (The regular layer removal procedure for the user is to
1084 // call QCustomPlot::removeLayer, which moves all layerables off this layer before deleting it.)
1085
1086 while (!mChildren.isEmpty())
1087 mChildren.last()->setLayer(nullptr); // removes itself from mChildren via removeChild()
1088
1089 if (mParentPlot->currentLayer() == this)
1090 qDebug() << Q_FUNC_INFO << "The parent plot's mCurrentLayer will be a dangling pointer. Should have been set to a valid layer or nullptr beforehand.";
1091}
1092
1093/*!
1094 Sets whether this layer is visible or not. If \a visible is set to false, all layerables on this
1095 layer will be invisible.
1096
1097 This function doesn't change the visibility property of the layerables (\ref
1098 QCPLayerable::setVisible), but the \ref QCPLayerable::realVisibility of each layerable takes the
1099 visibility of the parent layer into account.
1100*/
1101void QCPLayer::setVisible(bool visible)
1102{
1103 mVisible = visible;
1104}
1105
1106/*!
1107 Sets the rendering mode of this layer.
1108
1109 If \a mode is set to \ref lmBuffered for a layer, it will be given a dedicated paint buffer by
1110 the parent QCustomPlot instance. This means it may be replotted individually by calling \ref
1111 QCPLayer::replot, without needing to replot all other layers.
1112
1113 Layers which are set to \ref lmLogical (the default) are used only to define the rendering order
1114 and can't be replotted individually.
1115
1116 Note that each layer which is set to \ref lmBuffered requires additional paint buffers for the
1117 layers below, above and for the layer itself. This increases the memory consumption and
1118 (slightly) decreases the repainting speed because multiple paint buffers need to be joined. So
1119 you should carefully choose which layers benefit from having their own paint buffer. A typical
1120 example would be a layer which contains certain layerables (e.g. items) that need to be changed
1121 and thus replotted regularly, while all other layerables on other layers stay static. By default,
1122 only the topmost layer called "overlay" is in mode \ref lmBuffered, and contains the selection
1123 rect.
1124
1125 \see replot
1126*/
1128{
1129 if (mMode != mode)
1130 {
1131 mMode = mode;
1133 pb->setInvalidated();
1134 }
1135}
1136
1137/*! \internal
1138
1139 Draws the contents of this layer with the provided \a painter.
1140
1141 \see replot, drawToPaintBuffer
1142*/
1144{
1145 foreach (QCPLayerable *child, mChildren)
1146 {
1147 if (child->realVisibility())
1148 {
1149 painter->save();
1150 painter->setClipRect(child->clipRect().translated(0, -1));
1151 child->applyDefaultAntialiasingHint(painter);
1152 child->draw(painter);
1153 painter->restore();
1154 }
1155 }
1156}
1157
1158/*! \internal
1159
1160 Draws the contents of this layer into the paint buffer which is associated with this layer. The
1161 association is established by the parent QCustomPlot, which manages all paint buffers (see \ref
1162 QCustomPlot::setupPaintBuffers).
1163
1164 \see draw
1165*/
1167{
1169 {
1170 if (QCPPainter *painter = pb->startPainting())
1171 {
1172 if (painter->isActive())
1173 draw(painter);
1174 else
1175 qDebug() << Q_FUNC_INFO << "paint buffer returned inactive painter";
1176 delete painter;
1177 pb->donePainting();
1178 } else
1179 qDebug() << Q_FUNC_INFO << "paint buffer returned nullptr painter";
1180 } else
1181 qDebug() << Q_FUNC_INFO << "no valid paint buffer associated with this layer";
1182}
1183
1184/*!
1185 If the layer mode (\ref setMode) is set to \ref lmBuffered, this method allows replotting only
1186 the layerables on this specific layer, without the need to replot all other layers (as a call to
1187 \ref QCustomPlot::replot would do).
1188
1189 QCustomPlot also makes sure to replot all layers instead of only this one, if the layer ordering
1190 or any layerable-layer-association has changed since the last full replot and any other paint
1191 buffers were thus invalidated.
1192
1193 If the layer mode is \ref lmLogical however, this method simply calls \ref QCustomPlot::replot on
1194 the parent QCustomPlot instance.
1195
1196 \see draw
1197*/
1199{
1200 if (mMode == lmBuffered && !mParentPlot->hasInvalidatedPaintBuffers())
1201 {
1203 {
1204 pb->clear(Qt::transparent);
1206 pb->setInvalidated(false); // since layer is lmBuffered, we know only this layer is on buffer and we can reset invalidated flag
1207 mParentPlot->update();
1208 } else
1209 qDebug() << Q_FUNC_INFO << "no valid paint buffer associated with this layer";
1210 } else
1211 mParentPlot->replot();
1212}
1213
1214/*! \internal
1215
1216 Adds the \a layerable to the list of this layer. If \a prepend is set to true, the layerable will
1217 be prepended to the list, i.e. be drawn beneath the other layerables already in the list.
1218
1219 This function does not change the \a mLayer member of \a layerable to this layer. (Use
1220 QCPLayerable::setLayer to change the layer of an object, not this function.)
1221
1222 \see removeChild
1223*/
1224void QCPLayer::addChild(QCPLayerable *layerable, bool prepend)
1225{
1226 if (!mChildren.contains(layerable))
1227 {
1228 if (prepend)
1229 mChildren.prepend(layerable);
1230 else
1231 mChildren.append(layerable);
1233 pb->setInvalidated();
1234 } else
1235 qDebug() << Q_FUNC_INFO << "layerable is already child of this layer" << reinterpret_cast<quintptr>(layerable);
1236}
1237
1238/*! \internal
1239
1240 Removes the \a layerable from the list of this layer.
1241
1242 This function does not change the \a mLayer member of \a layerable. (Use QCPLayerable::setLayer
1243 to change the layer of an object, not this function.)
1244
1245 \see addChild
1246*/
1248{
1249 if (mChildren.removeOne(layerable))
1250 {
1252 pb->setInvalidated();
1253 } else
1254 qDebug() << Q_FUNC_INFO << "layerable is not child of this layer" << reinterpret_cast<quintptr>(layerable);
1255}
1256
1257
1258////////////////////////////////////////////////////////////////////////////////////////////////////
1259//////////////////// QCPLayerable
1260////////////////////////////////////////////////////////////////////////////////////////////////////
1261
1262/*! \class QCPLayerable
1263 \brief Base class for all drawable objects
1264
1265 This is the abstract base class most visible objects derive from, e.g. plottables, axes, grid
1266 etc.
1267
1268 Every layerable is on a layer (QCPLayer) which allows controlling the rendering order by stacking
1269 the layers accordingly.
1270
1271 For details about the layering mechanism, see the QCPLayer documentation.
1272*/
1273
1274/* start documentation of inline functions */
1275
1276/*! \fn QCPLayerable *QCPLayerable::parentLayerable() const
1277
1278 Returns the parent layerable of this layerable. The parent layerable is used to provide
1279 visibility hierarchies in conjunction with the method \ref realVisibility. This way, layerables
1280 only get drawn if their parent layerables are visible, too.
1281
1282 Note that a parent layerable is not necessarily also the QObject parent for memory management.
1283 Further, a layerable doesn't always have a parent layerable, so this function may return \c
1284 nullptr.
1285
1286 A parent layerable is set implicitly when placed inside layout elements and doesn't need to be
1287 set manually by the user.
1288*/
1289
1290/* end documentation of inline functions */
1291/* start documentation of pure virtual functions */
1292
1293/*! \fn virtual void QCPLayerable::applyDefaultAntialiasingHint(QCPPainter *painter) const = 0
1294 \internal
1295
1296 This function applies the default antialiasing setting to the specified \a painter, using the
1297 function \ref applyAntialiasingHint. It is the antialiasing state the painter is put in, when
1298 \ref draw is called on the layerable. If the layerable has multiple entities whose antialiasing
1299 setting may be specified individually, this function should set the antialiasing state of the
1300 most prominent entity. In this case however, the \ref draw function usually calls the specialized
1301 versions of this function before drawing each entity, effectively overriding the setting of the
1302 default antialiasing hint.
1303
1304 <b>First example:</b> QCPGraph has multiple entities that have an antialiasing setting: The graph
1305 line, fills and scatters. Those can be configured via QCPGraph::setAntialiased,
1306 QCPGraph::setAntialiasedFill and QCPGraph::setAntialiasedScatters. Consequently, there isn't only
1307 the QCPGraph::applyDefaultAntialiasingHint function (which corresponds to the graph line's
1308 antialiasing), but specialized ones like QCPGraph::applyFillAntialiasingHint and
1309 QCPGraph::applyScattersAntialiasingHint. So before drawing one of those entities, QCPGraph::draw
1310 calls the respective specialized applyAntialiasingHint function.
1311
1312 <b>Second example:</b> QCPItemLine consists only of a line so there is only one antialiasing
1313 setting which can be controlled with QCPItemLine::setAntialiased. (This function is inherited by
1314 all layerables. The specialized functions, as seen on QCPGraph, must be added explicitly to the
1315 respective layerable subclass.) Consequently it only has the normal
1316 QCPItemLine::applyDefaultAntialiasingHint. The \ref QCPItemLine::draw function doesn't need to
1317 care about setting any antialiasing states, because the default antialiasing hint is already set
1318 on the painter when the \ref draw function is called, and that's the state it wants to draw the
1319 line with.
1320*/
1321
1322/*! \fn virtual void QCPLayerable::draw(QCPPainter *painter) const = 0
1323 \internal
1324
1325 This function draws the layerable with the specified \a painter. It is only called by
1326 QCustomPlot, if the layerable is visible (\ref setVisible).
1327
1328 Before this function is called, the painter's antialiasing state is set via \ref
1329 applyDefaultAntialiasingHint, see the documentation there. Further, the clipping rectangle was
1330 set to \ref clipRect.
1331*/
1332
1333/* end documentation of pure virtual functions */
1334/* start documentation of signals */
1335
1336/*! \fn void QCPLayerable::layerChanged(QCPLayer *newLayer);
1337
1338 This signal is emitted when the layer of this layerable changes, i.e. this layerable is moved to
1339 a different layer.
1340
1341 \see setLayer
1342*/
1343
1344/* end documentation of signals */
1345
1346/*!
1347 Creates a new QCPLayerable instance.
1348
1349 Since QCPLayerable is an abstract base class, it can't be instantiated directly. Use one of the
1350 derived classes.
1351
1352 If \a plot is provided, it automatically places itself on the layer named \a targetLayer. If \a
1353 targetLayer is an empty string, it places itself on the current layer of the plot (see \ref
1354 QCustomPlot::setCurrentLayer).
1355
1356 It is possible to provide \c nullptr as \a plot. In that case, you should assign a parent plot at
1357 a later time with \ref initializeParentPlot.
1358
1359 The layerable's parent layerable is set to \a parentLayerable, if provided. Direct layerable
1360 parents are mainly used to control visibility in a hierarchy of layerables. This means a
1361 layerable is only drawn, if all its ancestor layerables are also visible. Note that \a
1362 parentLayerable does not become the QObject-parent (for memory management) of this layerable, \a
1363 plot does. It is not uncommon to set the QObject-parent to something else in the constructors of
1364 QCPLayerable subclasses, to guarantee a working destruction hierarchy.
1365*/
1366QCPLayerable::QCPLayerable(QCustomPlot *plot, QString targetLayer, QCPLayerable *parentLayerable) :
1367 QObject(plot),
1368 mVisible(true),
1369 mParentPlot(plot),
1370 mParentLayerable(parentLayerable),
1371 mLayer(nullptr),
1372 mAntialiased(true)
1373{
1374 if (mParentPlot)
1375 {
1376 if (targetLayer.isEmpty())
1377 setLayer(mParentPlot->currentLayer());
1378 else if (!setLayer(targetLayer))
1379 qDebug() << Q_FUNC_INFO << "setting QCPlayerable initial layer to" << targetLayer << "failed.";
1380 }
1381}
1382
1383QCPLayerable::~QCPLayerable()
1384{
1385 if (mLayer)
1386 {
1387 mLayer->removeChild(this);
1388 mLayer = nullptr;
1389 }
1390}
1391
1392/*!
1393 Sets the visibility of this layerable object. If an object is not visible, it will not be drawn
1394 on the QCustomPlot surface, and user interaction with it (e.g. click and selection) is not
1395 possible.
1396*/
1398{
1399 mVisible = on;
1400}
1401
1402/*!
1403 Sets the \a layer of this layerable object. The object will be placed on top of the other objects
1404 already on \a layer.
1405
1406 If \a layer is 0, this layerable will not be on any layer and thus not appear in the plot (or
1407 interact/receive events).
1408
1409 Returns true if the layer of this layerable was successfully changed to \a layer.
1410*/
1412{
1413 return moveToLayer(layer, false);
1414}
1415
1416/*! \overload
1417 Sets the layer of this layerable object by name
1418
1419 Returns true on success, i.e. if \a layerName is a valid layer name.
1420*/
1421bool QCPLayerable::setLayer(const QString &layerName)
1422{
1423 if (!mParentPlot)
1424 {
1425 qDebug() << Q_FUNC_INFO << "no parent QCustomPlot set";
1426 return false;
1427 }
1428 if (QCPLayer *layer = mParentPlot->layer(layerName))
1429 {
1430 return setLayer(layer);
1431 } else
1432 {
1433 qDebug() << Q_FUNC_INFO << "there is no layer with name" << layerName;
1434 return false;
1435 }
1436}
1437
1438/*!
1439 Sets whether this object will be drawn antialiased or not.
1440
1441 Note that antialiasing settings may be overridden by QCustomPlot::setAntialiasedElements and
1442 QCustomPlot::setNotAntialiasedElements.
1443*/
1445{
1446 mAntialiased = enabled;
1447}
1448
1449/*!
1450 Returns whether this layerable is visible, taking the visibility of the layerable parent and the
1451 visibility of this layerable's layer into account. This is the method that is consulted to decide
1452 whether a layerable shall be drawn or not.
1453
1454 If this layerable has a direct layerable parent (usually set via hierarchies implemented in
1455 subclasses, like in the case of \ref QCPLayoutElement), this function returns true only if this
1456 layerable has its visibility set to true and the parent layerable's \ref realVisibility returns
1457 true.
1458*/
1460{
1461 return mVisible && (!mLayer || mLayer->visible()) && (!mParentLayerable || mParentLayerable.data()->realVisibility());
1462}
1463
1464/*!
1465 This function is used to decide whether a click hits a layerable object or not.
1466
1467 \a pos is a point in pixel coordinates on the QCustomPlot surface. This function returns the
1468 shortest pixel distance of this point to the object. If the object is either invisible or the
1469 distance couldn't be determined, -1.0 is returned. Further, if \a onlySelectable is true and the
1470 object is not selectable, -1.0 is returned, too.
1471
1472 If the object is represented not by single lines but by an area like a \ref QCPItemText or the
1473 bars of a \ref QCPBars plottable, a click inside the area should also be considered a hit. In
1474 these cases this function thus returns a constant value greater zero but still below the parent
1475 plot's selection tolerance. (typically the selectionTolerance multiplied by 0.99).
1476
1477 Providing a constant value for area objects allows selecting line objects even when they are
1478 obscured by such area objects, by clicking close to the lines (i.e. closer than
1479 0.99*selectionTolerance).
1480
1481 The actual setting of the selection state is not done by this function. This is handled by the
1482 parent QCustomPlot when the mouseReleaseEvent occurs, and the finally selected object is notified
1483 via the \ref selectEvent/\ref deselectEvent methods.
1484
1485 \a details is an optional output parameter. Every layerable subclass may place any information
1486 in \a details. This information will be passed to \ref selectEvent when the parent QCustomPlot
1487 decides on the basis of this selectTest call, that the object was successfully selected. The
1488 subsequent call to \ref selectEvent will carry the \a details. This is useful for multi-part
1489 objects (like QCPAxis). This way, a possibly complex calculation to decide which part was clicked
1490 is only done once in \ref selectTest. The result (i.e. the actually clicked part) can then be
1491 placed in \a details. So in the subsequent \ref selectEvent, the decision which part was
1492 selected doesn't have to be done a second time for a single selection operation.
1493
1494 In the case of 1D Plottables (\ref QCPAbstractPlottable1D, like \ref QCPGraph or \ref QCPBars) \a
1495 details will be set to a \ref QCPDataSelection, describing the closest data point to \a pos.
1496
1497 You may pass \c nullptr as \a details to indicate that you are not interested in those selection
1498 details.
1499
1500 \see selectEvent, deselectEvent, mousePressEvent, wheelEvent, QCustomPlot::setInteractions,
1501 QCPAbstractPlottable1D::selectTestRect
1502*/
1503double QCPLayerable::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
1504{
1505 Q_UNUSED(pos)
1506 Q_UNUSED(onlySelectable)
1507 Q_UNUSED(details)
1508 return -1.0;
1509}
1510
1511/*! \internal
1512
1513 Sets the parent plot of this layerable. Use this function once to set the parent plot if you have
1514 passed \c nullptr in the constructor. It can not be used to move a layerable from one QCustomPlot
1515 to another one.
1516
1517 Note that, unlike when passing a non \c nullptr parent plot in the constructor, this function
1518 does not make \a parentPlot the QObject-parent of this layerable. If you want this, call
1519 QObject::setParent(\a parentPlot) in addition to this function.
1520
1521 Further, you will probably want to set a layer (\ref setLayer) after calling this function, to
1522 make the layerable appear on the QCustomPlot.
1523
1524 The parent plot change will be propagated to subclasses via a call to \ref parentPlotInitialized
1525 so they can react accordingly (e.g. also initialize the parent plot of child layerables, like
1526 QCPLayout does).
1527*/
1529{
1530 if (mParentPlot)
1531 {
1532 qDebug() << Q_FUNC_INFO << "called with mParentPlot already initialized";
1533 return;
1534 }
1535
1536 if (!parentPlot)
1537 qDebug() << Q_FUNC_INFO << "called with parentPlot zero";
1538
1539 mParentPlot = parentPlot;
1540 parentPlotInitialized(mParentPlot);
1541}
1542
1543/*! \internal
1544
1545 Sets the parent layerable of this layerable to \a parentLayerable. Note that \a parentLayerable does not
1546 become the QObject-parent (for memory management) of this layerable.
1547
1548 The parent layerable has influence on the return value of the \ref realVisibility method. Only
1549 layerables with a fully visible parent tree will return true for \ref realVisibility, and thus be
1550 drawn.
1551
1552 \see realVisibility
1553*/
1555{
1556 mParentLayerable = parentLayerable;
1557}
1558
1559/*! \internal
1560
1561 Moves this layerable object to \a layer. If \a prepend is true, this object will be prepended to
1562 the new layer's list, i.e. it will be drawn below the objects already on the layer. If it is
1563 false, the object will be appended.
1564
1565 Returns true on success, i.e. if \a layer is a valid layer.
1566*/
1567bool QCPLayerable::moveToLayer(QCPLayer *layer, bool prepend)
1568{
1569 if (layer && !mParentPlot)
1570 {
1571 qDebug() << Q_FUNC_INFO << "no parent QCustomPlot set";
1572 return false;
1573 }
1574 if (layer && layer->parentPlot() != mParentPlot)
1575 {
1576 qDebug() << Q_FUNC_INFO << "layer" << layer->name() << "is not in same QCustomPlot as this layerable";
1577 return false;
1578 }
1579
1580 QCPLayer *oldLayer = mLayer;
1581 if (mLayer)
1582 mLayer->removeChild(this);
1583 mLayer = layer;
1584 if (mLayer)
1585 mLayer->addChild(this, prepend);
1586 if (mLayer != oldLayer)
1587 emit layerChanged(mLayer);
1588 return true;
1589}
1590
1591/*! \internal
1592
1593 Sets the QCPainter::setAntialiasing state on the provided \a painter, depending on the \a
1594 localAntialiased value as well as the overrides \ref QCustomPlot::setAntialiasedElements and \ref
1595 QCustomPlot::setNotAntialiasedElements. Which override enum this function takes into account is
1596 controlled via \a overrideElement.
1597*/
1598void QCPLayerable::applyAntialiasingHint(QCPPainter *painter, bool localAntialiased, QCP::AntialiasedElement overrideElement) const
1599{
1600 if (mParentPlot && mParentPlot->notAntialiasedElements().testFlag(overrideElement))
1601 painter->setAntialiasing(false);
1602 else if (mParentPlot && mParentPlot->antialiasedElements().testFlag(overrideElement))
1603 painter->setAntialiasing(true);
1604 else
1605 painter->setAntialiasing(localAntialiased);
1606}
1607
1608/*! \internal
1609
1610 This function is called by \ref initializeParentPlot, to allow subclasses to react on the setting
1611 of a parent plot. This is the case when \c nullptr was passed as parent plot in the constructor,
1612 and the parent plot is set at a later time.
1613
1614 For example, QCPLayoutElement/QCPLayout hierarchies may be created independently of any
1615 QCustomPlot at first. When they are then added to a layout inside the QCustomPlot, the top level
1616 element of the hierarchy gets its parent plot initialized with \ref initializeParentPlot. To
1617 propagate the parent plot to all the children of the hierarchy, the top level element then uses
1618 this function to pass the parent plot on to its child elements.
1619
1620 The default implementation does nothing.
1621
1622 \see initializeParentPlot
1623*/
1625{
1626 Q_UNUSED(parentPlot)
1627}
1628
1629/*! \internal
1630
1631 Returns the selection category this layerable shall belong to. The selection category is used in
1632 conjunction with \ref QCustomPlot::setInteractions to control which objects are selectable and
1633 which aren't.
1634
1635 Subclasses that don't fit any of the normal \ref QCP::Interaction values can use \ref
1636 QCP::iSelectOther. This is what the default implementation returns.
1637
1638 \see QCustomPlot::setInteractions
1639*/
1644
1645/*! \internal
1646
1647 Returns the clipping rectangle of this layerable object. By default, this is the viewport of the
1648 parent QCustomPlot. Specific subclasses may reimplement this function to provide different
1649 clipping rects.
1650
1651 The returned clipping rect is set on the painter before the draw function of the respective
1652 object is called.
1653*/
1655{
1656 if (mParentPlot)
1657 return mParentPlot->viewport();
1658 else
1659 return {};
1660}
1661
1662/*! \internal
1663
1664 This event is called when the layerable shall be selected, as a consequence of a click by the
1665 user. Subclasses should react to it by setting their selection state appropriately. The default
1666 implementation does nothing.
1667
1668 \a event is the mouse event that caused the selection. \a additive indicates, whether the user
1669 was holding the multi-select-modifier while performing the selection (see \ref
1670 QCustomPlot::setMultiSelectModifier). if \a additive is true, the selection state must be toggled
1671 (i.e. become selected when unselected and unselected when selected).
1672
1673 Every selectEvent is preceded by a call to \ref selectTest, which has returned positively (i.e.
1674 returned a value greater than 0 and less than the selection tolerance of the parent QCustomPlot).
1675 The \a details data you output from \ref selectTest is fed back via \a details here. You may
1676 use it to transport any kind of information from the selectTest to the possibly subsequent
1677 selectEvent. Usually \a details is used to transfer which part was clicked, if it is a layerable
1678 that has multiple individually selectable parts (like QCPAxis). This way selectEvent doesn't need
1679 to do the calculation again to find out which part was actually clicked.
1680
1681 \a selectionStateChanged is an output parameter. If the pointer is non-null, this function must
1682 set the value either to true or false, depending on whether the selection state of this layerable
1683 was actually changed. For layerables that only are selectable as a whole and not in parts, this
1684 is simple: if \a additive is true, \a selectionStateChanged must also be set to true, because the
1685 selection toggles. If \a additive is false, \a selectionStateChanged is only set to true, if the
1686 layerable was previously unselected and now is switched to the selected state.
1687
1688 \see selectTest, deselectEvent
1689*/
1690void QCPLayerable::selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged)
1691{
1692 Q_UNUSED(event)
1693 Q_UNUSED(additive)
1694 Q_UNUSED(details)
1695 Q_UNUSED(selectionStateChanged)
1696}
1697
1698/*! \internal
1699
1700 This event is called when the layerable shall be deselected, either as consequence of a user
1701 interaction or a call to \ref QCustomPlot::deselectAll. Subclasses should react to it by
1702 unsetting their selection appropriately.
1703
1704 just as in \ref selectEvent, the output parameter \a selectionStateChanged (if non-null), must
1705 return true or false when the selection state of this layerable has changed or not changed,
1706 respectively.
1707
1708 \see selectTest, selectEvent
1709*/
1710void QCPLayerable::deselectEvent(bool *selectionStateChanged)
1711{
1712 Q_UNUSED(selectionStateChanged)
1713}
1714
1715/*!
1716 This event gets called when the user presses a mouse button while the cursor is over the
1717 layerable. Whether a cursor is over the layerable is decided by a preceding call to \ref
1718 selectTest.
1719
1720 The current pixel position of the cursor on the QCustomPlot widget is accessible via \c
1721 event->pos(). The parameter \a details contains layerable-specific details about the hit, which
1722 were generated in the previous call to \ref selectTest. For example, One-dimensional plottables
1723 like \ref QCPGraph or \ref QCPBars convey the clicked data point in the \a details parameter, as
1724 \ref QCPDataSelection packed as QVariant. Multi-part objects convey the specific \c
1725 SelectablePart that was hit (e.g. \ref QCPAxis::SelectablePart in the case of axes).
1726
1727 QCustomPlot uses an event propagation system that works the same as Qt's system. If your
1728 layerable doesn't reimplement the \ref mousePressEvent or explicitly calls \c event->ignore() in
1729 its reimplementation, the event will be propagated to the next layerable in the stacking order.
1730
1731 Once a layerable has accepted the \ref mousePressEvent, it is considered the mouse grabber and
1732 will receive all following calls to \ref mouseMoveEvent or \ref mouseReleaseEvent for this mouse
1733 interaction (a "mouse interaction" in this context ends with the release).
1734
1735 The default implementation does nothing except explicitly ignoring the event with \c
1736 event->ignore().
1737
1738 \see mouseMoveEvent, mouseReleaseEvent, mouseDoubleClickEvent, wheelEvent
1739*/
1741{
1742 Q_UNUSED(details)
1743 event->ignore();
1744}
1745
1746/*!
1747 This event gets called when the user moves the mouse while holding a mouse button, after this
1748 layerable has become the mouse grabber by accepting the preceding \ref mousePressEvent.
1749
1750 The current pixel position of the cursor on the QCustomPlot widget is accessible via \c
1751 event->pos(). The parameter \a startPos indicates the position where the initial \ref
1752 mousePressEvent occurred, that started the mouse interaction.
1753
1754 The default implementation does nothing.
1755
1756 \see mousePressEvent, mouseReleaseEvent, mouseDoubleClickEvent, wheelEvent
1757*/
1759{
1760 Q_UNUSED(startPos)
1761 event->ignore();
1762}
1763
1764/*!
1765 This event gets called when the user releases the mouse button, after this layerable has become
1766 the mouse grabber by accepting the preceding \ref mousePressEvent.
1767
1768 The current pixel position of the cursor on the QCustomPlot widget is accessible via \c
1769 event->pos(). The parameter \a startPos indicates the position where the initial \ref
1770 mousePressEvent occurred, that started the mouse interaction.
1771
1772 The default implementation does nothing.
1773
1774 \see mousePressEvent, mouseMoveEvent, mouseDoubleClickEvent, wheelEvent
1775*/
1777{
1778 Q_UNUSED(startPos)
1779 event->ignore();
1780}
1781
1782/*!
1783 This event gets called when the user presses the mouse button a second time in a double-click,
1784 while the cursor is over the layerable. Whether a cursor is over the layerable is decided by a
1785 preceding call to \ref selectTest.
1786
1787 The \ref mouseDoubleClickEvent is called instead of the second \ref mousePressEvent. So in the
1788 case of a double-click, the event succession is
1789 <i>pressEvent &ndash; releaseEvent &ndash; doubleClickEvent &ndash; releaseEvent</i>.
1790
1791 The current pixel position of the cursor on the QCustomPlot widget is accessible via \c
1792 event->pos(). The parameter \a details contains layerable-specific details about the hit, which
1793 were generated in the previous call to \ref selectTest. For example, One-dimensional plottables
1794 like \ref QCPGraph or \ref QCPBars convey the clicked data point in the \a details parameter, as
1795 \ref QCPDataSelection packed as QVariant. Multi-part objects convey the specific \c
1796 SelectablePart that was hit (e.g. \ref QCPAxis::SelectablePart in the case of axes).
1797
1798 Similarly to \ref mousePressEvent, once a layerable has accepted the \ref mouseDoubleClickEvent,
1799 it is considered the mouse grabber and will receive all following calls to \ref mouseMoveEvent
1800 and \ref mouseReleaseEvent for this mouse interaction (a "mouse interaction" in this context ends
1801 with the release).
1802
1803 The default implementation does nothing except explicitly ignoring the event with \c
1804 event->ignore().
1805
1806 \see mousePressEvent, mouseMoveEvent, mouseReleaseEvent, wheelEvent
1807*/
1809{
1810 Q_UNUSED(details)
1811 event->ignore();
1812}
1813
1814/*!
1815 This event gets called when the user turns the mouse scroll wheel while the cursor is over the
1816 layerable. Whether a cursor is over the layerable is decided by a preceding call to \ref
1817 selectTest.
1818
1819 The current pixel position of the cursor on the QCustomPlot widget is accessible via \c
1820 event->pos().
1821
1822 The \c event->angleDelta() indicates how far the mouse wheel was turned, which is usually +/- 120
1823 for single rotation steps. However, if the mouse wheel is turned rapidly, multiple steps may
1824 accumulate to one event, making the delta larger. On the other hand, if the wheel has very smooth
1825 steps or none at all, the delta may be smaller.
1826
1827 The default implementation does nothing.
1828
1829 \see mousePressEvent, mouseMoveEvent, mouseReleaseEvent, mouseDoubleClickEvent
1830*/
1832{
1833 event->ignore();
1834}
1835/* end of 'src/layer.cpp' */
1836
1837
1838/* including file 'src/axis/range.cpp' */
1839/* modified 2022-11-06T12:45:56, size 12221 */
1840
1841////////////////////////////////////////////////////////////////////////////////////////////////////
1842//////////////////// QCPRange
1843////////////////////////////////////////////////////////////////////////////////////////////////////
1844/*! \class QCPRange
1845 \brief Represents the range an axis is encompassing.
1846
1847 contains a \a lower and \a upper double value and provides convenience input, output and
1848 modification functions.
1849
1850 \see QCPAxis::setRange
1851*/
1852
1853/* start of documentation of inline functions */
1854
1855/*! \fn double QCPRange::size() const
1856
1857 Returns the size of the range, i.e. \a upper-\a lower
1858*/
1859
1860/*! \fn double QCPRange::center() const
1861
1862 Returns the center of the range, i.e. (\a upper+\a lower)*0.5
1863*/
1864
1865/*! \fn void QCPRange::normalize()
1866
1867 Makes sure \a lower is numerically smaller than \a upper. If this is not the case, the values are
1868 swapped.
1869*/
1870
1871/*! \fn bool QCPRange::contains(double value) const
1872
1873 Returns true when \a value lies within or exactly on the borders of the range.
1874*/
1875
1876/*! \fn QCPRange &QCPRange::operator+=(const double& value)
1877
1878 Adds \a value to both boundaries of the range.
1879*/
1880
1881/*! \fn QCPRange &QCPRange::operator-=(const double& value)
1882
1883 Subtracts \a value from both boundaries of the range.
1884*/
1885
1886/*! \fn QCPRange &QCPRange::operator*=(const double& value)
1887
1888 Multiplies both boundaries of the range by \a value.
1889*/
1890
1891/*! \fn QCPRange &QCPRange::operator/=(const double& value)
1892
1893 Divides both boundaries of the range by \a value.
1894*/
1895
1896/* end of documentation of inline functions */
1897
1898/*!
1899 Minimum range size (\a upper - \a lower) the range changing functions will accept. Smaller
1900 intervals would cause errors due to the 11-bit exponent of double precision numbers,
1901 corresponding to a minimum magnitude of roughly 1e-308.
1902
1903 \warning Do not use this constant to indicate "arbitrarily small" values in plotting logic (as
1904 values that will appear in the plot)! It is intended only as a bound to compare against, e.g. to
1905 prevent axis ranges from obtaining underflowing ranges.
1906
1907 \see validRange, maxRange
1908*/
1909const double QCPRange::minRange = 1e-280;
1910
1911/*!
1912 Maximum values (negative and positive) the range will accept in range-changing functions.
1913 Larger absolute values would cause errors due to the 11-bit exponent of double precision numbers,
1914 corresponding to a maximum magnitude of roughly 1e308.
1915
1916 \warning Do not use this constant to indicate "arbitrarily large" values in plotting logic (as
1917 values that will appear in the plot)! It is intended only as a bound to compare against, e.g. to
1918 prevent axis ranges from obtaining overflowing ranges.
1919
1920 \see validRange, minRange
1921*/
1922const double QCPRange::maxRange = 1e250;
1923
1924/*!
1925 Constructs a range with \a lower and \a upper set to zero.
1926*/
1928 lower(0),
1929 upper(0)
1930{
1931}
1932
1933/*! \overload
1934
1935 Constructs a range with the specified \a lower and \a upper values.
1936
1937 The resulting range will be normalized (see \ref normalize), so if \a lower is not numerically
1938 smaller than \a upper, they will be swapped.
1939*/
1940QCPRange::QCPRange(double lower, double upper) :
1941 lower(lower),
1942 upper(upper)
1943{
1944 normalize();
1945}
1946
1947/*! \overload
1948
1949 Expands this range such that \a otherRange is contained in the new range. It is assumed that both
1950 this range and \a otherRange are normalized (see \ref normalize).
1951
1952 If this range contains NaN as lower or upper bound, it will be replaced by the respective bound
1953 of \a otherRange.
1954
1955 If \a otherRange is already inside the current range, this function does nothing.
1956
1957 \see expanded
1958*/
1959void QCPRange::expand(const QCPRange &otherRange)
1960{
1961 if (lower > otherRange.lower || qIsNaN(lower))
1962 lower = otherRange.lower;
1963 if (upper < otherRange.upper || qIsNaN(upper))
1964 upper = otherRange.upper;
1965}
1966
1967/*! \overload
1968
1969 Expands this range such that \a includeCoord is contained in the new range. It is assumed that
1970 this range is normalized (see \ref normalize).
1971
1972 If this range contains NaN as lower or upper bound, the respective bound will be set to \a
1973 includeCoord.
1974
1975 If \a includeCoord is already inside the current range, this function does nothing.
1976
1977 \see expand
1978*/
1979void QCPRange::expand(double includeCoord)
1980{
1981 if (lower > includeCoord || qIsNaN(lower))
1982 lower = includeCoord;
1983 if (upper < includeCoord || qIsNaN(upper))
1984 upper = includeCoord;
1985}
1986
1987
1988/*! \overload
1989
1990 Returns an expanded range that contains this and \a otherRange. It is assumed that both this
1991 range and \a otherRange are normalized (see \ref normalize).
1992
1993 If this range contains NaN as lower or upper bound, the returned range's bound will be taken from
1994 \a otherRange.
1995
1996 \see expand
1997*/
1998QCPRange QCPRange::expanded(const QCPRange &otherRange) const
1999{
2000 QCPRange result = *this;
2001 result.expand(otherRange);
2002 return result;
2003}
2004
2005/*! \overload
2006
2007 Returns an expanded range that includes the specified \a includeCoord. It is assumed that this
2008 range is normalized (see \ref normalize).
2009
2010 If this range contains NaN as lower or upper bound, the returned range's bound will be set to \a
2011 includeCoord.
2012
2013 \see expand
2014*/
2015QCPRange QCPRange::expanded(double includeCoord) const
2016{
2017 QCPRange result = *this;
2018 result.expand(includeCoord);
2019 return result;
2020}
2021
2022/*!
2023 Returns this range, possibly modified to not exceed the bounds provided as \a lowerBound and \a
2024 upperBound. If possible, the size of the current range is preserved in the process.
2025
2026 If the range shall only be bounded at the lower side, you can set \a upperBound to \ref
2027 QCPRange::maxRange. If it shall only be bounded at the upper side, set \a lowerBound to -\ref
2028 QCPRange::maxRange.
2029*/
2030QCPRange QCPRange::bounded(double lowerBound, double upperBound) const
2031{
2032 if (lowerBound > upperBound)
2033 qSwap(lowerBound, upperBound);
2034
2035 QCPRange result(lower, upper);
2036 if (result.lower < lowerBound)
2037 {
2038 result.lower = lowerBound;
2039 result.upper = lowerBound + size();
2040 if (result.upper > upperBound || qFuzzyCompare(size(), upperBound-lowerBound))
2041 result.upper = upperBound;
2042 } else if (result.upper > upperBound)
2043 {
2044 result.upper = upperBound;
2045 result.lower = upperBound - size();
2046 if (result.lower < lowerBound || qFuzzyCompare(size(), upperBound-lowerBound))
2047 result.lower = lowerBound;
2048 }
2049
2050 return result;
2051}
2052
2053/*!
2054 Returns a sanitized version of the range. Sanitized means for logarithmic scales, that
2055 the range won't span the positive and negative sign domain, i.e. contain zero. Further
2056 \a lower will always be numerically smaller (or equal) to \a upper.
2057
2058 If the original range does span positive and negative sign domains or contains zero,
2059 the returned range will try to approximate the original range as good as possible.
2060 If the positive interval of the original range is wider than the negative interval, the
2061 returned range will only contain the positive interval, with lower bound set to \a rangeFac or
2062 \a rangeFac *\a upper, whichever is closer to zero. Same procedure is used if the negative interval
2063 is wider than the positive interval, this time by changing the \a upper bound.
2064*/
2066{
2067 double rangeFac = 1e-3;
2068 QCPRange sanitizedRange(lower, upper);
2069 sanitizedRange.normalize();
2070 // can't have range spanning negative and positive values in log plot, so change range to fix it
2071 //if (qFuzzyCompare(sanitizedRange.lower+1, 1) && !qFuzzyCompare(sanitizedRange.upper+1, 1))
2072 if (sanitizedRange.lower == 0.0 && sanitizedRange.upper != 0.0)
2073 {
2074 // case lower is 0
2075 if (rangeFac < sanitizedRange.upper*rangeFac)
2076 sanitizedRange.lower = rangeFac;
2077 else
2078 sanitizedRange.lower = sanitizedRange.upper*rangeFac;
2079 } //else if (!qFuzzyCompare(lower+1, 1) && qFuzzyCompare(upper+1, 1))
2080 else if (sanitizedRange.lower != 0.0 && sanitizedRange.upper == 0.0)
2081 {
2082 // case upper is 0
2083 if (-rangeFac > sanitizedRange.lower*rangeFac)
2084 sanitizedRange.upper = -rangeFac;
2085 else
2086 sanitizedRange.upper = sanitizedRange.lower*rangeFac;
2087 } else if (sanitizedRange.lower < 0 && sanitizedRange.upper > 0)
2088 {
2089 // find out whether negative or positive interval is wider to decide which sign domain will be chosen
2090 if (-sanitizedRange.lower > sanitizedRange.upper)
2091 {
2092 // negative is wider, do same as in case upper is 0
2093 if (-rangeFac > sanitizedRange.lower*rangeFac)
2094 sanitizedRange.upper = -rangeFac;
2095 else
2096 sanitizedRange.upper = sanitizedRange.lower*rangeFac;
2097 } else
2098 {
2099 // positive is wider, do same as in case lower is 0
2100 if (rangeFac < sanitizedRange.upper*rangeFac)
2101 sanitizedRange.lower = rangeFac;
2102 else
2103 sanitizedRange.lower = sanitizedRange.upper*rangeFac;
2104 }
2105 }
2106 // due to normalization, case lower>0 && upper<0 should never occur, because that implies upper<lower
2107 return sanitizedRange;
2108}
2109
2110/*!
2111 Returns a sanitized version of the range. Sanitized means for linear scales, that
2112 \a lower will always be numerically smaller (or equal) to \a upper.
2113*/
2115{
2116 QCPRange sanitizedRange(lower, upper);
2117 sanitizedRange.normalize();
2118 return sanitizedRange;
2119}
2120
2121/*!
2122 Checks, whether the specified range is within valid bounds, which are defined
2123 as QCPRange::maxRange and QCPRange::minRange.
2124 A valid range means:
2125 \li range bounds within -maxRange and maxRange
2126 \li range size above minRange
2127 \li range size below maxRange
2128*/
2129bool QCPRange::validRange(double lower, double upper)
2130{
2131 return (lower > -maxRange &&
2132 upper < maxRange &&
2133 qAbs(lower-upper) > minRange &&
2134 qAbs(lower-upper) < maxRange &&
2135 !(lower > 0 && qIsInf(upper/lower)) &&
2136 !(upper < 0 && qIsInf(lower/upper)));
2137}
2138
2139/*!
2140 \overload
2141 Checks, whether the specified range is within valid bounds, which are defined
2142 as QCPRange::maxRange and QCPRange::minRange.
2143 A valid range means:
2144 \li range bounds within -maxRange and maxRange
2145 \li range size above minRange
2146 \li range size below maxRange
2147*/
2149{
2150 return (range.lower > -maxRange &&
2151 range.upper < maxRange &&
2152 qAbs(range.lower-range.upper) > minRange &&
2153 qAbs(range.lower-range.upper) < maxRange &&
2154 !(range.lower > 0 && qIsInf(range.upper/range.lower)) &&
2155 !(range.upper < 0 && qIsInf(range.lower/range.upper)));
2156}
2157/* end of 'src/axis/range.cpp' */
2158
2159
2160/* including file 'src/selection.cpp' */
2161/* modified 2022-11-06T12:45:56, size 21837 */
2162
2163////////////////////////////////////////////////////////////////////////////////////////////////////
2164//////////////////// QCPDataRange
2165////////////////////////////////////////////////////////////////////////////////////////////////////
2166
2167/*! \class QCPDataRange
2168 \brief Describes a data range given by begin and end index
2169
2170 QCPDataRange holds two integers describing the begin (\ref setBegin) and end (\ref setEnd) index
2171 of a contiguous set of data points. The \a end index corresponds to the data point just after the
2172 last data point of the data range, like in standard iterators.
2173
2174 Data Ranges are not bound to a certain plottable, thus they can be freely exchanged, created and
2175 modified. If a non-contiguous data set shall be described, the class \ref QCPDataSelection is
2176 used, which holds and manages multiple instances of \ref QCPDataRange. In most situations, \ref
2177 QCPDataSelection is thus used.
2178
2179 Both \ref QCPDataRange and \ref QCPDataSelection offer convenience methods to work with them,
2180 e.g. \ref bounded, \ref expanded, \ref intersects, \ref intersection, \ref adjusted, \ref
2181 contains. Further, addition and subtraction operators (defined in \ref QCPDataSelection) can be
2182 used to join/subtract data ranges and data selections (or mixtures), to retrieve a corresponding
2183 \ref QCPDataSelection.
2184
2185 %QCustomPlot's \ref dataselection "data selection mechanism" is based on \ref QCPDataSelection and
2186 QCPDataRange.
2187
2188 \note Do not confuse \ref QCPDataRange with \ref QCPRange. A \ref QCPRange describes an interval
2189 in floating point plot coordinates, e.g. the current axis range.
2190*/
2191
2192/* start documentation of inline functions */
2193
2194/*! \fn int QCPDataRange::size() const
2195
2196 Returns the number of data points described by this data range. This is equal to the end index
2197 minus the begin index.
2198
2199 \see length
2200*/
2201
2202/*! \fn int QCPDataRange::length() const
2203
2204 Returns the number of data points described by this data range. Equivalent to \ref size.
2205*/
2206
2207/*! \fn void QCPDataRange::setBegin(int begin)
2208
2209 Sets the begin of this data range. The \a begin index points to the first data point that is part
2210 of the data range.
2211
2212 No checks or corrections are made to ensure the resulting range is valid (\ref isValid).
2213
2214 \see setEnd
2215*/
2216
2217/*! \fn void QCPDataRange::setEnd(int end)
2218
2219 Sets the end of this data range. The \a end index points to the data point just after the last
2220 data point that is part of the data range.
2221
2222 No checks or corrections are made to ensure the resulting range is valid (\ref isValid).
2223
2224 \see setBegin
2225*/
2226
2227/*! \fn bool QCPDataRange::isValid() const
2228
2229 Returns whether this range is valid. A valid range has a begin index greater or equal to 0, and
2230 an end index greater or equal to the begin index.
2231
2232 \note Invalid ranges should be avoided and are never the result of any of QCustomPlot's methods
2233 (unless they are themselves fed with invalid ranges). Do not pass invalid ranges to QCustomPlot's
2234 methods. The invalid range is not inherently prevented in QCPDataRange, to allow temporary
2235 invalid begin/end values while manipulating the range. An invalid range is not necessarily empty
2236 (\ref isEmpty), since its \ref length can be negative and thus non-zero.
2237*/
2238
2239/*! \fn bool QCPDataRange::isEmpty() const
2240
2241 Returns whether this range is empty, i.e. whether its begin index equals its end index.
2242
2243 \see size, length
2244*/
2245
2246/*! \fn QCPDataRange QCPDataRange::adjusted(int changeBegin, int changeEnd) const
2247
2248 Returns a data range where \a changeBegin and \a changeEnd were added to the begin and end
2249 indices, respectively.
2250*/
2251
2252/* end documentation of inline functions */
2253
2254/*!
2255 Creates an empty QCPDataRange, with begin and end set to 0.
2256*/
2258 mBegin(0),
2259 mEnd(0)
2260{
2261}
2262
2263/*!
2264 Creates a QCPDataRange, initialized with the specified \a begin and \a end.
2265
2266 No checks or corrections are made to ensure the resulting range is valid (\ref isValid).
2267*/
2268QCPDataRange::QCPDataRange(int begin, int end) :
2269 mBegin(begin),
2270 mEnd(end)
2271{
2272}
2273
2274/*!
2275 Returns a data range that matches this data range, except that parts exceeding \a other are
2276 excluded.
2277
2278 This method is very similar to \ref intersection, with one distinction: If this range and the \a
2279 other range share no intersection, the returned data range will be empty with begin and end set
2280 to the respective boundary side of \a other, at which this range is residing. (\ref intersection
2281 would just return a range with begin and end set to 0.)
2282*/
2284{
2285 QCPDataRange result(intersection(other));
2286 if (result.isEmpty()) // no intersection, preserve respective bounding side of otherRange as both begin and end of return value
2287 {
2288 if (mEnd <= other.mBegin)
2289 result = QCPDataRange(other.mBegin, other.mBegin);
2290 else
2291 result = QCPDataRange(other.mEnd, other.mEnd);
2292 }
2293 return result;
2294}
2295
2296/*!
2297 Returns a data range that contains both this data range as well as \a other.
2298*/
2300{
2301 return {qMin(mBegin, other.mBegin), qMax(mEnd, other.mEnd)};
2302}
2303
2304/*!
2305 Returns the data range which is contained in both this data range and \a other.
2306
2307 This method is very similar to \ref bounded, with one distinction: If this range and the \a other
2308 range share no intersection, the returned data range will be empty with begin and end set to 0.
2309 (\ref bounded would return a range with begin and end set to one of the boundaries of \a other,
2310 depending on which side this range is on.)
2311
2312 \see QCPDataSelection::intersection
2313*/
2315{
2316 QCPDataRange result(qMax(mBegin, other.mBegin), qMin(mEnd, other.mEnd));
2317 if (result.isValid())
2318 return result;
2319 else
2320 return {};
2321}
2322
2323/*!
2324 Returns whether this data range and \a other share common data points.
2325
2326 \see intersection, contains
2327*/
2329{
2330 return !( (mBegin > other.mBegin && mBegin >= other.mEnd) ||
2331 (mEnd <= other.mBegin && mEnd < other.mEnd) );
2332}
2333
2334/*!
2335 Returns whether all data points of \a other are also contained inside this data range.
2336
2337 \see intersects
2338*/
2339bool QCPDataRange::contains(const QCPDataRange &other) const
2340{
2341 return mBegin <= other.mBegin && mEnd >= other.mEnd;
2342}
2343
2344
2345
2346////////////////////////////////////////////////////////////////////////////////////////////////////
2347//////////////////// QCPDataSelection
2348////////////////////////////////////////////////////////////////////////////////////////////////////
2349
2350/*! \class QCPDataSelection
2351 \brief Describes a data set by holding multiple QCPDataRange instances
2352
2353 QCPDataSelection manages multiple instances of QCPDataRange in order to represent any (possibly
2354 disjoint) set of data selection.
2355
2356 The data selection can be modified with addition and subtraction operators which take
2357 QCPDataSelection and QCPDataRange instances, as well as methods such as \ref addDataRange and
2358 \ref clear. Read access is provided by \ref dataRange, \ref dataRanges, \ref dataRangeCount, etc.
2359
2360 The method \ref simplify is used to join directly adjacent or even overlapping QCPDataRange
2361 instances. QCPDataSelection automatically simplifies when using the addition/subtraction
2362 operators. The only case when \ref simplify is left to the user, is when calling \ref
2363 addDataRange, with the parameter \a simplify explicitly set to false. This is useful if many data
2364 ranges will be added to the selection successively and the overhead for simplifying after each
2365 iteration shall be avoided. In this case, you should make sure to call \ref simplify after
2366 completing the operation.
2367
2368 Use \ref enforceType to bring the data selection into a state complying with the constraints for
2369 selections defined in \ref QCP::SelectionType.
2370
2371 %QCustomPlot's \ref dataselection "data selection mechanism" is based on QCPDataSelection and
2372 QCPDataRange.
2373
2374 \section qcpdataselection-iterating Iterating over a data selection
2375
2376 As an example, the following code snippet calculates the average value of a graph's data
2377 \ref QCPAbstractPlottable::selection "selection":
2378
2379 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpdataselection-iterating-1
2380
2381*/
2382
2383/* start documentation of inline functions */
2384
2385/*! \fn int QCPDataSelection::dataRangeCount() const
2386
2387 Returns the number of ranges that make up the data selection. The ranges can be accessed by \ref
2388 dataRange via their index.
2389
2390 \see dataRange, dataPointCount
2391*/
2392
2393/*! \fn QList<QCPDataRange> QCPDataSelection::dataRanges() const
2394
2395 Returns all data ranges that make up the data selection. If the data selection is simplified (the
2396 usual state of the selection, see \ref simplify), the ranges are sorted by ascending data point
2397 index.
2398
2399 \see dataRange
2400*/
2401
2402/*! \fn bool QCPDataSelection::isEmpty() const
2403
2404 Returns true if there are no data ranges, and thus no data points, in this QCPDataSelection
2405 instance.
2406
2407 \see dataRangeCount
2408*/
2409
2410/* end documentation of inline functions */
2411
2412/*!
2413 Creates an empty QCPDataSelection.
2414*/
2418
2419/*!
2420 Creates a QCPDataSelection containing the provided \a range.
2421*/
2423{
2424 mDataRanges.append(range);
2425}
2426
2427/*!
2428 Returns true if this selection is identical (contains the same data ranges with the same begin
2429 and end indices) to \a other.
2430
2431 Note that both data selections must be in simplified state (the usual state of the selection, see
2432 \ref simplify) for this operator to return correct results.
2433*/
2435{
2436 if (mDataRanges.size() != other.mDataRanges.size())
2437 return false;
2438 for (int i=0; i<mDataRanges.size(); ++i)
2439 {
2440 if (mDataRanges.at(i) != other.mDataRanges.at(i))
2441 return false;
2442 }
2443 return true;
2444}
2445
2446/*!
2447 Adds the data selection of \a other to this data selection, and then simplifies this data
2448 selection (see \ref simplify).
2449*/
2451{
2452 mDataRanges << other.mDataRanges;
2453 simplify();
2454 return *this;
2455}
2456
2457/*!
2458 Adds the data range \a other to this data selection, and then simplifies this data selection (see
2459 \ref simplify).
2460*/
2462{
2463 addDataRange(other);
2464 return *this;
2465}
2466
2467/*!
2468 Removes all data point indices that are described by \a other from this data selection.
2469*/
2471{
2472 for (int i=0; i<other.dataRangeCount(); ++i)
2473 *this -= other.dataRange(i);
2474
2475 return *this;
2476}
2477
2478/*!
2479 Removes all data point indices that are described by \a other from this data selection.
2480*/
2482{
2483 if (other.isEmpty() || isEmpty())
2484 return *this;
2485
2486 simplify();
2487 int i=0;
2488 while (i < mDataRanges.size())
2489 {
2490 const int thisBegin = mDataRanges.at(i).begin();
2491 const int thisEnd = mDataRanges.at(i).end();
2492 if (thisBegin >= other.end())
2493 break; // since data ranges are sorted after the simplify() call, no ranges which contain other will come after this
2494
2495 if (thisEnd > other.begin()) // ranges which don't fulfill this are entirely before other and can be ignored
2496 {
2497 if (thisBegin >= other.begin()) // range leading segment is encompassed
2498 {
2499 if (thisEnd <= other.end()) // range fully encompassed, remove completely
2500 {
2501 mDataRanges.removeAt(i);
2502 continue;
2503 } else // only leading segment is encompassed, trim accordingly
2504 mDataRanges[i].setBegin(other.end());
2505 } else // leading segment is not encompassed
2506 {
2507 if (thisEnd <= other.end()) // only trailing segment is encompassed, trim accordingly
2508 {
2509 mDataRanges[i].setEnd(other.begin());
2510 } else // other lies inside this range, so split range
2511 {
2512 mDataRanges[i].setEnd(other.begin());
2513 mDataRanges.insert(i+1, QCPDataRange(other.end(), thisEnd));
2514 break; // since data ranges are sorted (and don't overlap) after simplify() call, we're done here
2515 }
2516 }
2517 }
2518 ++i;
2519 }
2520
2521 return *this;
2522}
2523
2524/*!
2525 Returns the total number of data points contained in all data ranges that make up this data
2526 selection.
2527*/
2529{
2530 int result = 0;
2531 foreach (QCPDataRange dataRange, mDataRanges)
2532 result += dataRange.length();
2533 return result;
2534}
2535
2536/*!
2537 Returns the data range with the specified \a index.
2538
2539 If the data selection is simplified (the usual state of the selection, see \ref simplify), the
2540 ranges are sorted by ascending data point index.
2541
2542 \see dataRangeCount
2543*/
2545{
2546 if (index >= 0 && index < mDataRanges.size())
2547 {
2548 return mDataRanges.at(index);
2549 } else
2550 {
2551 qDebug() << Q_FUNC_INFO << "index out of range:" << index;
2552 return {};
2553 }
2554}
2555
2556/*!
2557 Returns a \ref QCPDataRange which spans the entire data selection, including possible
2558 intermediate segments which are not part of the original data selection.
2559*/
2561{
2562 if (isEmpty())
2563 return {};
2564 else
2565 return {mDataRanges.first().begin(), mDataRanges.last().end()};
2566}
2567
2568/*!
2569 Adds the given \a dataRange to this data selection. This is equivalent to the += operator but
2570 allows disabling immediate simplification by setting \a simplify to false. This can improve
2571 performance if adding a very large amount of data ranges successively. In this case, make sure to
2572 call \ref simplify manually, after the operation.
2573*/
2574void QCPDataSelection::addDataRange(const QCPDataRange &dataRange, bool simplify)
2575{
2576 mDataRanges.append(dataRange);
2577 if (simplify)
2578 this->simplify();
2579}
2580
2581/*!
2582 Removes all data ranges. The data selection then contains no data points.
2583
2584 \ref isEmpty
2585*/
2587{
2588 mDataRanges.clear();
2589}
2590
2591/*!
2592 Sorts all data ranges by range begin index in ascending order, and then joins directly adjacent
2593 or overlapping ranges. This can reduce the number of individual data ranges in the selection, and
2594 prevents possible double-counting when iterating over the data points held by the data ranges.
2595
2596 This method is automatically called when using the addition/subtraction operators. The only case
2597 when \ref simplify is left to the user, is when calling \ref addDataRange, with the parameter \a
2598 simplify explicitly set to false.
2599*/
2601{
2602 // remove any empty ranges:
2603 for (int i=mDataRanges.size()-1; i>=0; --i)
2604 {
2605 if (mDataRanges.at(i).isEmpty())
2606 mDataRanges.removeAt(i);
2607 }
2608 if (mDataRanges.isEmpty())
2609 return;
2610
2611 // sort ranges by starting value, ascending:
2612 std::sort(mDataRanges.begin(), mDataRanges.end(), lessThanDataRangeBegin);
2613
2614 // join overlapping/contiguous ranges:
2615 int i = 1;
2616 while (i < mDataRanges.size())
2617 {
2618 if (mDataRanges.at(i-1).end() >= mDataRanges.at(i).begin()) // range i overlaps/joins with i-1, so expand range i-1 appropriately and remove range i from list
2619 {
2620 mDataRanges[i-1].setEnd(qMax(mDataRanges.at(i-1).end(), mDataRanges.at(i).end()));
2621 mDataRanges.removeAt(i);
2622 } else
2623 ++i;
2624 }
2625}
2626
2627/*!
2628 Makes sure this data selection conforms to the specified \a type selection type. Before the type
2629 is enforced, \ref simplify is called.
2630
2631 Depending on \a type, enforcing means adding new data points that were previously not part of the
2632 selection, or removing data points from the selection. If the current selection already conforms
2633 to \a type, the data selection is not changed.
2634
2635 \see QCP::SelectionType
2636*/
2638{
2639 simplify();
2640 switch (type)
2641 {
2642 case QCP::stNone:
2643 {
2644 mDataRanges.clear();
2645 break;
2646 }
2647 case QCP::stWhole:
2648 {
2649 // whole selection isn't defined by data range, so don't change anything (is handled in plottable methods)
2650 break;
2651 }
2652 case QCP::stSingleData:
2653 {
2654 // reduce all data ranges to the single first data point:
2655 if (!mDataRanges.isEmpty())
2656 {
2657 if (mDataRanges.size() > 1)
2658 mDataRanges = QList<QCPDataRange>() << mDataRanges.first();
2659 if (mDataRanges.first().length() > 1)
2660 mDataRanges.first().setEnd(mDataRanges.first().begin()+1);
2661 }
2662 break;
2663 }
2664 case QCP::stDataRange:
2665 {
2666 if (!isEmpty())
2667 mDataRanges = QList<QCPDataRange>() << span();
2668 break;
2669 }
2671 {
2672 // this is the selection type that allows all concievable combinations of ranges, so do nothing
2673 break;
2674 }
2675 }
2676}
2677
2678/*!
2679 Returns true if the data selection \a other is contained entirely in this data selection, i.e.
2680 all data point indices that are in \a other are also in this data selection.
2681
2682 \see QCPDataRange::contains
2683*/
2685{
2686 if (other.isEmpty()) return false;
2687
2688 int otherIndex = 0;
2689 int thisIndex = 0;
2690 while (thisIndex < mDataRanges.size() && otherIndex < other.mDataRanges.size())
2691 {
2692 if (mDataRanges.at(thisIndex).contains(other.mDataRanges.at(otherIndex)))
2693 ++otherIndex;
2694 else
2695 ++thisIndex;
2696 }
2697 return thisIndex < mDataRanges.size(); // if thisIndex ran all the way to the end to find a containing range for the current otherIndex, other is not contained in this
2698}
2699
2700/*!
2701 Returns a data selection containing the points which are both in this data selection and in the
2702 data range \a other.
2703
2704 A common use case is to limit an unknown data selection to the valid range of a data container,
2705 using \ref QCPDataContainer::dataRange as \a other. One can then safely iterate over the returned
2706 data selection without exceeding the data container's bounds.
2707*/
2709{
2710 QCPDataSelection result;
2711 foreach (QCPDataRange dataRange, mDataRanges)
2712 result.addDataRange(dataRange.intersection(other), false);
2713 result.simplify();
2714 return result;
2715}
2716
2717/*!
2718 Returns a data selection containing the points which are both in this data selection and in the
2719 data selection \a other.
2720*/
2722{
2723 QCPDataSelection result;
2724 for (int i=0; i<other.dataRangeCount(); ++i)
2725 result += intersection(other.dataRange(i));
2726 result.simplify();
2727 return result;
2728}
2729
2730/*!
2731 Returns a data selection which is the exact inverse of this data selection, with \a outerRange
2732 defining the base range on which to invert. If \a outerRange is smaller than the \ref span of
2733 this data selection, it is expanded accordingly.
2734
2735 For example, this method can be used to retrieve all unselected segments by setting \a outerRange
2736 to the full data range of the plottable, and calling this method on a data selection holding the
2737 selected segments.
2738*/
2740{
2741 if (isEmpty())
2742 return QCPDataSelection(outerRange);
2743 QCPDataRange fullRange = outerRange.expanded(span());
2744
2745 QCPDataSelection result;
2746 // first unselected segment:
2747 if (mDataRanges.first().begin() != fullRange.begin())
2748 result.addDataRange(QCPDataRange(fullRange.begin(), mDataRanges.first().begin()), false);
2749 // intermediate unselected segments:
2750 for (int i=1; i<mDataRanges.size(); ++i)
2751 result.addDataRange(QCPDataRange(mDataRanges.at(i-1).end(), mDataRanges.at(i).begin()), false);
2752 // last unselected segment:
2753 if (mDataRanges.last().end() != fullRange.end())
2754 result.addDataRange(QCPDataRange(mDataRanges.last().end(), fullRange.end()), false);
2755 result.simplify();
2756 return result;
2757}
2758/* end of 'src/selection.cpp' */
2759
2760
2761/* including file 'src/selectionrect.cpp' */
2762/* modified 2022-11-06T12:45:56, size 9215 */
2763
2764////////////////////////////////////////////////////////////////////////////////////////////////////
2765//////////////////// QCPSelectionRect
2766////////////////////////////////////////////////////////////////////////////////////////////////////
2767
2768/*! \class QCPSelectionRect
2769 \brief Provides rect/rubber-band data selection and range zoom interaction
2770
2771 QCPSelectionRect is used by QCustomPlot when the \ref QCustomPlot::setSelectionRectMode is not
2772 \ref QCP::srmNone. When the user drags the mouse across the plot, the current selection rect
2773 instance (\ref QCustomPlot::setSelectionRect) is forwarded these events and makes sure an
2774 according rect shape is drawn. At the begin, during, and after completion of the interaction, it
2775 emits the corresponding signals \ref started, \ref changed, \ref canceled, and \ref accepted.
2776
2777 The QCustomPlot instance connects own slots to the current selection rect instance, in order to
2778 react to an accepted selection rect interaction accordingly.
2779
2780 \ref isActive can be used to check whether the selection rect is currently active. An ongoing
2781 selection interaction can be cancelled programmatically via calling \ref cancel at any time.
2782
2783 The appearance of the selection rect can be controlled via \ref setPen and \ref setBrush.
2784
2785 If you wish to provide custom behaviour, e.g. a different visual representation of the selection
2786 rect (\ref QCPSelectionRect::draw), you can subclass QCPSelectionRect and pass an instance of
2787 your subclass to \ref QCustomPlot::setSelectionRect.
2788*/
2789
2790/* start of documentation of inline functions */
2791
2792/*! \fn bool QCPSelectionRect::isActive() const
2793
2794 Returns true if there is currently a selection going on, i.e. the user has started dragging a
2795 selection rect, but hasn't released the mouse button yet.
2796
2797 \see cancel
2798*/
2799
2800/* end of documentation of inline functions */
2801/* start documentation of signals */
2802
2803/*! \fn void QCPSelectionRect::started(QMouseEvent *event);
2804
2805 This signal is emitted when a selection rect interaction was initiated, i.e. the user just
2806 started dragging the selection rect with the mouse.
2807*/
2808
2809/*! \fn void QCPSelectionRect::changed(const QRect &rect, QMouseEvent *event);
2810
2811 This signal is emitted while the selection rect interaction is ongoing and the \a rect has
2812 changed its size due to the user moving the mouse.
2813
2814 Note that \a rect may have a negative width or height, if the selection is being dragged to the
2815 upper or left side of the selection rect origin.
2816*/
2817
2818/*! \fn void QCPSelectionRect::canceled(const QRect &rect, QInputEvent *event);
2819
2820 This signal is emitted when the selection interaction was cancelled. Note that \a event is \c
2821 nullptr if the selection interaction was cancelled programmatically, by a call to \ref cancel.
2822
2823 The user may cancel the selection interaction by pressing the escape key. In this case, \a event
2824 holds the respective input event.
2825
2826 Note that \a rect may have a negative width or height, if the selection is being dragged to the
2827 upper or left side of the selection rect origin.
2828*/
2829
2830/*! \fn void QCPSelectionRect::accepted(const QRect &rect, QMouseEvent *event);
2831
2832 This signal is emitted when the selection interaction was completed by the user releasing the
2833 mouse button.
2834
2835 Note that \a rect may have a negative width or height, if the selection is being dragged to the
2836 upper or left side of the selection rect origin.
2837*/
2838
2839/* end documentation of signals */
2840
2841/*!
2842 Creates a new QCPSelectionRect instance. To make QCustomPlot use the selection rect instance,
2843 pass it to \ref QCustomPlot::setSelectionRect. \a parentPlot should be set to the same
2844 QCustomPlot widget.
2845*/
2847 QCPLayerable(parentPlot),
2848 mPen(QBrush(Qt::gray), 0, Qt::DashLine),
2849 mBrush(Qt::NoBrush),
2850 mActive(false)
2851{
2852}
2853
2854QCPSelectionRect::~QCPSelectionRect()
2855{
2856 cancel();
2857}
2858
2859/*!
2860 A convenience function which returns the coordinate range of the provided \a axis, that this
2861 selection rect currently encompasses.
2862*/
2864{
2865 if (axis)
2866 {
2867 if (axis->orientation() == Qt::Horizontal)
2868 return {axis->pixelToCoord(mRect.left()), axis->pixelToCoord(mRect.left()+mRect.width())};
2869 else
2870 return {axis->pixelToCoord(mRect.top()+mRect.height()), axis->pixelToCoord(mRect.top())};
2871 } else
2872 {
2873 qDebug() << Q_FUNC_INFO << "called with axis zero";
2874 return {};
2875 }
2876}
2877
2878/*!
2879 Sets the pen that will be used to draw the selection rect outline.
2880
2881 \see setBrush
2882*/
2884{
2885 mPen = pen;
2886}
2887
2888/*!
2889 Sets the brush that will be used to fill the selection rect. By default the selection rect is not
2890 filled, i.e. \a brush is <tt>Qt::NoBrush</tt>.
2891
2892 \see setPen
2893*/
2895{
2896 mBrush = brush;
2897}
2898
2899/*!
2900 If there is currently a selection interaction going on (\ref isActive), the interaction is
2901 canceled. The selection rect will emit the \ref canceled signal.
2902*/
2904{
2905 if (mActive)
2906 {
2907 mActive = false;
2908 emit canceled(mRect, nullptr);
2909 }
2910}
2911
2912/*! \internal
2913
2914 This method is called by QCustomPlot to indicate that a selection rect interaction was initiated.
2915 The default implementation sets the selection rect to active, initializes the selection rect
2916 geometry and emits the \ref started signal.
2917*/
2919{
2920 mActive = true;
2921 mRect = QRect(event->pos(), event->pos());
2922 emit started(event);
2923}
2924
2925/*! \internal
2926
2927 This method is called by QCustomPlot to indicate that an ongoing selection rect interaction needs
2928 to update its geometry. The default implementation updates the rect and emits the \ref changed
2929 signal.
2930*/
2932{
2933 mRect.setBottomRight(event->pos());
2934 emit changed(mRect, event);
2935 layer()->replot();
2936}
2937
2938/*! \internal
2939
2940 This method is called by QCustomPlot to indicate that an ongoing selection rect interaction has
2941 finished by the user releasing the mouse button. The default implementation deactivates the
2942 selection rect and emits the \ref accepted signal.
2943*/
2945{
2946 mRect.setBottomRight(event->pos());
2947 mActive = false;
2948 emit accepted(mRect, event);
2949}
2950
2951/*! \internal
2952
2953 This method is called by QCustomPlot when a key has been pressed by the user while the selection
2954 rect interaction is active. The default implementation allows to \ref cancel the interaction by
2955 hitting the escape key.
2956*/
2958{
2959 if (event->key() == Qt::Key_Escape && mActive)
2960 {
2961 mActive = false;
2962 emit canceled(mRect, event);
2963 }
2964}
2965
2966/* inherits documentation from base class */
2968{
2969 applyAntialiasingHint(painter, mAntialiased, QCP::aeOther);
2970}
2971
2972/*! \internal
2973
2974 If the selection rect is active (\ref isActive), draws the selection rect defined by \a mRect.
2975
2976 \seebaseclassmethod
2977*/
2979{
2980 if (mActive)
2981 {
2982 painter->setPen(mPen);
2983 painter->setBrush(mBrush);
2984 painter->drawRect(mRect);
2985 }
2986}
2987/* end of 'src/selectionrect.cpp' */
2988
2989
2990/* including file 'src/layout.cpp' */
2991/* modified 2022-11-06T12:45:56, size 78863 */
2992
2993////////////////////////////////////////////////////////////////////////////////////////////////////
2994//////////////////// QCPMarginGroup
2995////////////////////////////////////////////////////////////////////////////////////////////////////
2996
2997/*! \class QCPMarginGroup
2998 \brief A margin group allows synchronization of margin sides if working with multiple layout elements.
2999
3000 QCPMarginGroup allows you to tie a margin side of two or more layout elements together, such that
3001 they will all have the same size, based on the largest required margin in the group.
3002
3003 \n
3004 \image html QCPMarginGroup.png "Demonstration of QCPMarginGroup"
3005 \n
3006
3007 In certain situations it is desirable that margins at specific sides are synchronized across
3008 layout elements. For example, if one QCPAxisRect is below another one in a grid layout, it will
3009 provide a cleaner look to the user if the left and right margins of the two axis rects are of the
3010 same size. The left axis of the top axis rect will then be at the same horizontal position as the
3011 left axis of the lower axis rect, making them appear aligned. The same applies for the right
3012 axes. This is what QCPMarginGroup makes possible.
3013
3014 To add/remove a specific side of a layout element to/from a margin group, use the \ref
3015 QCPLayoutElement::setMarginGroup method. To completely break apart the margin group, either call
3016 \ref clear, or just delete the margin group.
3017
3018 \section QCPMarginGroup-example Example
3019
3020 First create a margin group:
3021 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpmargingroup-creation-1
3022 Then set this group on the layout element sides:
3023 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpmargingroup-creation-2
3024 Here, we've used the first two axis rects of the plot and synchronized their left margins with
3025 each other and their right margins with each other.
3026*/
3027
3028/* start documentation of inline functions */
3029
3030/*! \fn QList<QCPLayoutElement*> QCPMarginGroup::elements(QCP::MarginSide side) const
3031
3032 Returns a list of all layout elements that have their margin \a side associated with this margin
3033 group.
3034*/
3035
3036/* end documentation of inline functions */
3037
3038/*!
3039 Creates a new QCPMarginGroup instance in \a parentPlot.
3040*/
3042 QObject(parentPlot),
3043 mParentPlot(parentPlot)
3044{
3049}
3050
3051QCPMarginGroup::~QCPMarginGroup()
3052{
3053 clear();
3054}
3055
3056/*!
3057 Returns whether this margin group is empty. If this function returns true, no layout elements use
3058 this margin group to synchronize margin sides.
3059*/
3061{
3063 while (it.hasNext())
3064 {
3065 it.next();
3066 if (!it.value().isEmpty())
3067 return false;
3068 }
3069 return true;
3070}
3071
3072/*!
3073 Clears this margin group. The synchronization of the margin sides that use this margin group is
3074 lifted and they will use their individual margin sizes again.
3075*/
3077{
3078 // make all children remove themselves from this margin group:
3080 while (it.hasNext())
3081 {
3082 it.next();
3084 for (int i=elements.size()-1; i>=0; --i)
3085 elements.at(i)->setMarginGroup(it.key(), nullptr); // removes itself from mChildren via removeChild
3086 }
3087}
3088
3089/*! \internal
3090
3091 Returns the synchronized common margin for \a side. This is the margin value that will be used by
3092 the layout element on the respective side, if it is part of this margin group.
3093
3094 The common margin is calculated by requesting the automatic margin (\ref
3095 QCPLayoutElement::calculateAutoMargin) of each element associated with \a side in this margin
3096 group, and choosing the largest returned value. (QCPLayoutElement::minimumMargins is taken into
3097 account, too.)
3098*/
3100{
3101 // query all automatic margins of the layout elements in this margin group side and find maximum:
3102 int result = 0;
3103 foreach (QCPLayoutElement *el, mChildren.value(side))
3104 {
3105 if (!el->autoMargins().testFlag(side))
3106 continue;
3107 int m = qMax(el->calculateAutoMargin(side), QCP::getMarginValue(el->minimumMargins(), side));
3108 if (m > result)
3109 result = m;
3110 }
3111 return result;
3112}
3113
3114/*! \internal
3115
3116 Adds \a element to the internal list of child elements, for the margin \a side.
3117
3118 This function does not modify the margin group property of \a element.
3119*/
3121{
3122 if (!mChildren[side].contains(element))
3123 mChildren[side].append(element);
3124 else
3125 qDebug() << Q_FUNC_INFO << "element is already child of this margin group side" << reinterpret_cast<quintptr>(element);
3126}
3127
3128/*! \internal
3129
3130 Removes \a element from the internal list of child elements, for the margin \a side.
3131
3132 This function does not modify the margin group property of \a element.
3133*/
3135{
3136 if (!mChildren[side].removeOne(element))
3137 qDebug() << Q_FUNC_INFO << "element is not child of this margin group side" << reinterpret_cast<quintptr>(element);
3138}
3139
3140
3141////////////////////////////////////////////////////////////////////////////////////////////////////
3142//////////////////// QCPLayoutElement
3143////////////////////////////////////////////////////////////////////////////////////////////////////
3144
3145/*! \class QCPLayoutElement
3146 \brief The abstract base class for all objects that form \ref thelayoutsystem "the layout system".
3147
3148 This is an abstract base class. As such, it can't be instantiated directly, rather use one of its subclasses.
3149
3150 A Layout element is a rectangular object which can be placed in layouts. It has an outer rect
3151 (QCPLayoutElement::outerRect) and an inner rect (\ref QCPLayoutElement::rect). The difference
3152 between outer and inner rect is called its margin. The margin can either be set to automatic or
3153 manual (\ref setAutoMargins) on a per-side basis. If a side is set to manual, that margin can be
3154 set explicitly with \ref setMargins and will stay fixed at that value. If it's set to automatic,
3155 the layout element subclass will control the value itself (via \ref calculateAutoMargin).
3156
3157 Layout elements can be placed in layouts (base class QCPLayout) like QCPLayoutGrid. The top level
3158 layout is reachable via \ref QCustomPlot::plotLayout, and is a \ref QCPLayoutGrid. Since \ref
3159 QCPLayout itself derives from \ref QCPLayoutElement, layouts can be nested.
3160
3161 Thus in QCustomPlot one can divide layout elements into two categories: The ones that are
3162 invisible by themselves, because they don't draw anything. Their only purpose is to manage the
3163 position and size of other layout elements. This category of layout elements usually use
3164 QCPLayout as base class. Then there is the category of layout elements which actually draw
3165 something. For example, QCPAxisRect, QCPLegend and QCPTextElement are of this category. This does
3166 not necessarily mean that the latter category can't have child layout elements. QCPLegend for
3167 instance, actually derives from QCPLayoutGrid and the individual legend items are child layout
3168 elements in the grid layout.
3169*/
3170
3171/* start documentation of inline functions */
3172
3173/*! \fn QCPLayout *QCPLayoutElement::layout() const
3174
3175 Returns the parent layout of this layout element.
3176*/
3177
3178/*! \fn QRect QCPLayoutElement::rect() const
3179
3180 Returns the inner rect of this layout element. The inner rect is the outer rect (\ref outerRect, \ref
3181 setOuterRect) shrinked by the margins (\ref setMargins, \ref setAutoMargins).
3182
3183 In some cases, the area between outer and inner rect is left blank. In other cases the margin
3184 area is used to display peripheral graphics while the main content is in the inner rect. This is
3185 where automatic margin calculation becomes interesting because it allows the layout element to
3186 adapt the margins to the peripheral graphics it wants to draw. For example, \ref QCPAxisRect
3187 draws the axis labels and tick labels in the margin area, thus needs to adjust the margins (if
3188 \ref setAutoMargins is enabled) according to the space required by the labels of the axes.
3189
3190 \see outerRect
3191*/
3192
3193/*! \fn QRect QCPLayoutElement::outerRect() const
3194
3195 Returns the outer rect of this layout element. The outer rect is the inner rect expanded by the
3196 margins (\ref setMargins, \ref setAutoMargins). The outer rect is used (and set via \ref
3197 setOuterRect) by the parent \ref QCPLayout to control the size of this layout element.
3198
3199 \see rect
3200*/
3201
3202/* end documentation of inline functions */
3203
3204/*!
3205 Creates an instance of QCPLayoutElement and sets default values.
3206*/
3208 QCPLayerable(parentPlot), // parenthood is changed as soon as layout element gets inserted into a layout (except for top level layout)
3209 mParentLayout(nullptr),
3210 mMinimumSize(),
3211 mMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX),
3212 mSizeConstraintRect(scrInnerRect),
3213 mRect(0, 0, 0, 0),
3214 mOuterRect(0, 0, 0, 0),
3215 mMargins(0, 0, 0, 0),
3216 mMinimumMargins(0, 0, 0, 0),
3217 mAutoMargins(QCP::msAll)
3218{
3219}
3220
3221QCPLayoutElement::~QCPLayoutElement()
3222{
3223 setMarginGroup(QCP::msAll, nullptr); // unregister at margin groups, if there are any
3224 // unregister at layout:
3225 if (qobject_cast<QCPLayout*>(mParentLayout)) // the qobject_cast is just a safeguard in case the layout forgets to call clear() in its dtor and this dtor is called by QObject dtor
3226 mParentLayout->take(this);
3227}
3228
3229/*!
3230 Sets the outer rect of this layout element. If the layout element is inside a layout, the layout
3231 sets the position and size of this layout element using this function.
3232
3233 Calling this function externally has no effect, since the layout will overwrite any changes to
3234 the outer rect upon the next replot.
3235
3236 The layout element will adapt its inner \ref rect by applying the margins inward to the outer rect.
3237
3238 \see rect
3239*/
3241{
3242 if (mOuterRect != rect)
3243 {
3244 mOuterRect = rect;
3245 mRect = mOuterRect.adjusted(mMargins.left(), mMargins.top(), -mMargins.right(), -mMargins.bottom());
3246 }
3247}
3248
3249/*!
3250 Sets the margins of this layout element. If \ref setAutoMargins is disabled for some or all
3251 sides, this function is used to manually set the margin on those sides. Sides that are still set
3252 to be handled automatically are ignored and may have any value in \a margins.
3253
3254 The margin is the distance between the outer rect (controlled by the parent layout via \ref
3255 setOuterRect) and the inner \ref rect (which usually contains the main content of this layout
3256 element).
3257
3258 \see setAutoMargins
3259*/
3261{
3262 if (mMargins != margins)
3263 {
3264 mMargins = margins;
3265 mRect = mOuterRect.adjusted(mMargins.left(), mMargins.top(), -mMargins.right(), -mMargins.bottom());
3266 }
3267}
3268
3269/*!
3270 If \ref setAutoMargins is enabled on some or all margins, this function is used to provide
3271 minimum values for those margins.
3272
3273 The minimum values are not enforced on margin sides that were set to be under manual control via
3274 \ref setAutoMargins.
3275
3276 \see setAutoMargins
3277*/
3279{
3280 if (mMinimumMargins != margins)
3281 {
3282 mMinimumMargins = margins;
3283 }
3284}
3285
3286/*!
3287 Sets on which sides the margin shall be calculated automatically. If a side is calculated
3288 automatically, a minimum margin value may be provided with \ref setMinimumMargins. If a side is
3289 set to be controlled manually, the value may be specified with \ref setMargins.
3290
3291 Margin sides that are under automatic control may participate in a \ref QCPMarginGroup (see \ref
3292 setMarginGroup), to synchronize (align) it with other layout elements in the plot.
3293
3294 \see setMinimumMargins, setMargins, QCP::MarginSide
3295*/
3297{
3298 mAutoMargins = sides;
3299}
3300
3301/*!
3302 Sets the minimum size of this layout element. A parent layout tries to respect the \a size here
3303 by changing row/column sizes in the layout accordingly.
3304
3305 If the parent layout size is not sufficient to satisfy all minimum size constraints of its child
3306 layout elements, the layout may set a size that is actually smaller than \a size. QCustomPlot
3307 propagates the layout's size constraints to the outside by setting its own minimum QWidget size
3308 accordingly, so violations of \a size should be exceptions.
3309
3310 Whether this constraint applies to the inner or the outer rect can be specified with \ref
3311 setSizeConstraintRect (see \ref rect and \ref outerRect).
3312*/
3314{
3315 if (mMinimumSize != size)
3316 {
3317 mMinimumSize = size;
3318 if (mParentLayout)
3319 mParentLayout->sizeConstraintsChanged();
3320 }
3321}
3322
3323/*! \overload
3324
3325 Sets the minimum size of this layout element.
3326
3327 Whether this constraint applies to the inner or the outer rect can be specified with \ref
3328 setSizeConstraintRect (see \ref rect and \ref outerRect).
3329*/
3330void QCPLayoutElement::setMinimumSize(int width, int height)
3331{
3332 setMinimumSize(QSize(width, height));
3333}
3334
3335/*!
3336 Sets the maximum size of this layout element. A parent layout tries to respect the \a size here
3337 by changing row/column sizes in the layout accordingly.
3338
3339 Whether this constraint applies to the inner or the outer rect can be specified with \ref
3340 setSizeConstraintRect (see \ref rect and \ref outerRect).
3341*/
3343{
3344 if (mMaximumSize != size)
3345 {
3346 mMaximumSize = size;
3347 if (mParentLayout)
3348 mParentLayout->sizeConstraintsChanged();
3349 }
3350}
3351
3352/*! \overload
3353
3354 Sets the maximum size of this layout element.
3355
3356 Whether this constraint applies to the inner or the outer rect can be specified with \ref
3357 setSizeConstraintRect (see \ref rect and \ref outerRect).
3358*/
3359void QCPLayoutElement::setMaximumSize(int width, int height)
3360{
3361 setMaximumSize(QSize(width, height));
3362}
3363
3364/*!
3365 Sets to which rect of a layout element the size constraints apply. Size constraints can be set
3366 via \ref setMinimumSize and \ref setMaximumSize.
3367
3368 The outer rect (\ref outerRect) includes the margins (e.g. in the case of a QCPAxisRect the axis
3369 labels), whereas the inner rect (\ref rect) does not.
3370
3371 \see setMinimumSize, setMaximumSize
3372*/
3374{
3375 if (mSizeConstraintRect != constraintRect)
3376 {
3377 mSizeConstraintRect = constraintRect;
3378 if (mParentLayout)
3379 mParentLayout->sizeConstraintsChanged();
3380 }
3381}
3382
3383/*!
3384 Sets the margin \a group of the specified margin \a sides.
3385
3386 Margin groups allow synchronizing specified margins across layout elements, see the documentation
3387 of \ref QCPMarginGroup.
3388
3389 To unset the margin group of \a sides, set \a group to \c nullptr.
3390
3391 Note that margin groups only work for margin sides that are set to automatic (\ref
3392 setAutoMargins).
3393
3394 \see QCP::MarginSide
3395*/
3397{
3398 QVector<QCP::MarginSide> sideVector;
3399 if (sides.testFlag(QCP::msLeft)) sideVector.append(QCP::msLeft);
3400 if (sides.testFlag(QCP::msRight)) sideVector.append(QCP::msRight);
3401 if (sides.testFlag(QCP::msTop)) sideVector.append(QCP::msTop);
3402 if (sides.testFlag(QCP::msBottom)) sideVector.append(QCP::msBottom);
3403
3404 foreach (QCP::MarginSide side, sideVector)
3405 {
3406 if (marginGroup(side) != group)
3407 {
3408 QCPMarginGroup *oldGroup = marginGroup(side);
3409 if (oldGroup) // unregister at old group
3410 oldGroup->removeChild(side, this);
3411
3412 if (!group) // if setting to 0, remove hash entry. Else set hash entry to new group and register there
3413 {
3414 mMarginGroups.remove(side);
3415 } else // setting to a new group
3416 {
3417 mMarginGroups[side] = group;
3418 group->addChild(side, this);
3419 }
3420 }
3421 }
3422}
3423
3424/*!
3425 Updates the layout element and sub-elements. This function is automatically called before every
3426 replot by the parent layout element. It is called multiple times, once for every \ref
3427 UpdatePhase. The phases are run through in the order of the enum values. For details about what
3428 happens at the different phases, see the documentation of \ref UpdatePhase.
3429
3430 Layout elements that have child elements should call the \ref update method of their child
3431 elements, and pass the current \a phase unchanged.
3432
3433 The default implementation executes the automatic margin mechanism in the \ref upMargins phase.
3434 Subclasses should make sure to call the base class implementation.
3435*/
3437{
3438 if (phase == upMargins)
3439 {
3440 if (mAutoMargins != QCP::msNone)
3441 {
3442 // set the margins of this layout element according to automatic margin calculation, either directly or via a margin group:
3443 QMargins newMargins = mMargins;
3445 foreach (QCP::MarginSide side, allMarginSides)
3446 {
3447 if (mAutoMargins.testFlag(side)) // this side's margin shall be calculated automatically
3448 {
3449 if (mMarginGroups.contains(side))
3450 QCP::setMarginValue(newMargins, side, mMarginGroups[side]->commonMargin(side)); // this side is part of a margin group, so get the margin value from that group
3451 else
3452 QCP::setMarginValue(newMargins, side, calculateAutoMargin(side)); // this side is not part of a group, so calculate the value directly
3453 // apply minimum margin restrictions:
3454 if (QCP::getMarginValue(newMargins, side) < QCP::getMarginValue(mMinimumMargins, side))
3455 QCP::setMarginValue(newMargins, side, QCP::getMarginValue(mMinimumMargins, side));
3456 }
3457 }
3458 setMargins(newMargins);
3459 }
3460 }
3461}
3462
3463/*!
3464 Returns the suggested minimum size this layout element (the \ref outerRect) may be compressed to,
3465 if no manual minimum size is set.
3466
3467 if a minimum size (\ref setMinimumSize) was not set manually, parent layouts use the returned size
3468 (usually indirectly through \ref QCPLayout::getFinalMinimumOuterSize) to determine the minimum
3469 allowed size of this layout element.
3470
3471 A manual minimum size is considered set if it is non-zero.
3472
3473 The default implementation simply returns the sum of the horizontal margins for the width and the
3474 sum of the vertical margins for the height. Reimplementations may use their detailed knowledge
3475 about the layout element's content to provide size hints.
3476*/
3478{
3479 return {mMargins.left()+mMargins.right(), mMargins.top()+mMargins.bottom()};
3480}
3481
3482/*!
3483 Returns the suggested maximum size this layout element (the \ref outerRect) may be expanded to,
3484 if no manual maximum size is set.
3485
3486 if a maximum size (\ref setMaximumSize) was not set manually, parent layouts use the returned
3487 size (usually indirectly through \ref QCPLayout::getFinalMaximumOuterSize) to determine the
3488 maximum allowed size of this layout element.
3489
3490 A manual maximum size is considered set if it is smaller than Qt's \c QWIDGETSIZE_MAX.
3491
3492 The default implementation simply returns \c QWIDGETSIZE_MAX for both width and height, implying
3493 no suggested maximum size. Reimplementations may use their detailed knowledge about the layout
3494 element's content to provide size hints.
3495*/
3497{
3498 return {QWIDGETSIZE_MAX, QWIDGETSIZE_MAX};
3499}
3500
3501/*!
3502 Returns a list of all child elements in this layout element. If \a recursive is true, all
3503 sub-child elements are included in the list, too.
3504
3505 \warning There may be \c nullptr entries in the returned list. For example, QCPLayoutGrid may
3506 have empty cells which yield \c nullptr at the respective index.
3507*/
3509{
3510 Q_UNUSED(recursive)
3511 return QList<QCPLayoutElement*>();
3512}
3513
3514/*!
3515 Layout elements are sensitive to events inside their outer rect. If \a pos is within the outer
3516 rect, this method returns a value corresponding to 0.99 times the parent plot's selection
3517 tolerance. However, layout elements are not selectable by default. So if \a onlySelectable is
3518 true, -1.0 is returned.
3519
3520 See \ref QCPLayerable::selectTest for a general explanation of this virtual method.
3521
3522 QCPLayoutElement subclasses may reimplement this method to provide more specific selection test
3523 behaviour.
3524*/
3525double QCPLayoutElement::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
3526{
3527 Q_UNUSED(details)
3528
3529 if (onlySelectable)
3530 return -1;
3531
3532 if (QRectF(mOuterRect).contains(pos))
3533 {
3534 if (mParentPlot)
3535 return mParentPlot->selectionTolerance()*0.99;
3536 else
3537 {
3538 qDebug() << Q_FUNC_INFO << "parent plot not defined";
3539 return -1;
3540 }
3541 } else
3542 return -1;
3543}
3544
3545/*! \internal
3546
3547 propagates the parent plot initialization to all child elements, by calling \ref
3548 QCPLayerable::initializeParentPlot on them.
3549*/
3551{
3552 foreach (QCPLayoutElement *el, elements(false))
3553 {
3554 if (!el->parentPlot())
3555 el->initializeParentPlot(parentPlot);
3556 }
3557}
3558
3559/*! \internal
3560
3561 Returns the margin size for this \a side. It is used if automatic margins is enabled for this \a
3562 side (see \ref setAutoMargins). If a minimum margin was set with \ref setMinimumMargins, the
3563 returned value will not be smaller than the specified minimum margin.
3564
3565 The default implementation just returns the respective manual margin (\ref setMargins) or the
3566 minimum margin, whichever is larger.
3567*/
3569{
3570 return qMax(QCP::getMarginValue(mMargins, side), QCP::getMarginValue(mMinimumMargins, side));
3571}
3572
3573/*! \internal
3574
3575 This virtual method is called when this layout element was moved to a different QCPLayout, or
3576 when this layout element has changed its logical position (e.g. row and/or column) within the
3577 same QCPLayout. Subclasses may use this to react accordingly.
3578
3579 Since this method is called after the completion of the move, you can access the new parent
3580 layout via \ref layout().
3581
3582 The default implementation does nothing.
3583*/
3587
3588////////////////////////////////////////////////////////////////////////////////////////////////////
3589//////////////////// QCPLayout
3590////////////////////////////////////////////////////////////////////////////////////////////////////
3591
3592/*! \class QCPLayout
3593 \brief The abstract base class for layouts
3594
3595 This is an abstract base class for layout elements whose main purpose is to define the position
3596 and size of other child layout elements. In most cases, layouts don't draw anything themselves
3597 (but there are exceptions to this, e.g. QCPLegend).
3598
3599 QCPLayout derives from QCPLayoutElement, and thus can itself be nested in other layouts.
3600
3601 QCPLayout introduces a common interface for accessing and manipulating the child elements. Those
3602 functions are most notably \ref elementCount, \ref elementAt, \ref takeAt, \ref take, \ref
3603 simplify, \ref removeAt, \ref remove and \ref clear. Individual subclasses may add more functions
3604 to this interface which are more specialized to the form of the layout. For example, \ref
3605 QCPLayoutGrid adds functions that take row and column indices to access cells of the layout grid
3606 more conveniently.
3607
3608 Since this is an abstract base class, you can't instantiate it directly. Rather use one of its
3609 subclasses like QCPLayoutGrid or QCPLayoutInset.
3610
3611 For a general introduction to the layout system, see the dedicated documentation page \ref
3612 thelayoutsystem "The Layout System".
3613*/
3614
3615/* start documentation of pure virtual functions */
3616
3617/*! \fn virtual int QCPLayout::elementCount() const = 0
3618
3619 Returns the number of elements/cells in the layout.
3620
3621 \see elements, elementAt
3622*/
3623
3624/*! \fn virtual QCPLayoutElement* QCPLayout::elementAt(int index) const = 0
3625
3626 Returns the element in the cell with the given \a index. If \a index is invalid, returns \c
3627 nullptr.
3628
3629 Note that even if \a index is valid, the respective cell may be empty in some layouts (e.g.
3630 QCPLayoutGrid), so this function may return \c nullptr in those cases. You may use this function
3631 to check whether a cell is empty or not.
3632
3633 \see elements, elementCount, takeAt
3634*/
3635
3636/*! \fn virtual QCPLayoutElement* QCPLayout::takeAt(int index) = 0
3637
3638 Removes the element with the given \a index from the layout and returns it.
3639
3640 If the \a index is invalid or the cell with that index is empty, returns \c nullptr.
3641
3642 Note that some layouts don't remove the respective cell right away but leave an empty cell after
3643 successful removal of the layout element. To collapse empty cells, use \ref simplify.
3644
3645 \see elementAt, take
3646*/
3647
3648/*! \fn virtual bool QCPLayout::take(QCPLayoutElement* element) = 0
3649
3650 Removes the specified \a element from the layout and returns true on success.
3651
3652 If the \a element isn't in this layout, returns false.
3653
3654 Note that some layouts don't remove the respective cell right away but leave an empty cell after
3655 successful removal of the layout element. To collapse empty cells, use \ref simplify.
3656
3657 \see takeAt
3658*/
3659
3660/* end documentation of pure virtual functions */
3661
3662/*!
3663 Creates an instance of QCPLayout and sets default values. Note that since QCPLayout
3664 is an abstract base class, it can't be instantiated directly.
3665*/
3669
3670/*!
3671 If \a phase is \ref upLayout, calls \ref updateLayout, which subclasses may reimplement to
3672 reposition and resize their cells.
3673
3674 Finally, the call is propagated down to all child \ref QCPLayoutElement "QCPLayoutElements".
3675
3676 For details about this method and the update phases, see the documentation of \ref
3677 QCPLayoutElement::update.
3678*/
3680{
3682
3683 // set child element rects according to layout:
3684 if (phase == upLayout)
3685 updateLayout();
3686
3687 // propagate update call to child elements:
3688 const int elCount = elementCount();
3689 for (int i=0; i<elCount; ++i)
3690 {
3691 if (QCPLayoutElement *el = elementAt(i))
3692 el->update(phase);
3693 }
3694}
3695
3696/* inherits documentation from base class */
3698{
3699 const int c = elementCount();
3701#if QT_VERSION >= QT_VERSION_CHECK(4, 7, 0)
3702 result.reserve(c);
3703#endif
3704 for (int i=0; i<c; ++i)
3705 result.append(elementAt(i));
3706 if (recursive)
3707 {
3708 for (int i=0; i<c; ++i)
3709 {
3710 if (result.at(i))
3711 result << result.at(i)->elements(recursive);
3712 }
3713 }
3714 return result;
3715}
3716
3717/*!
3718 Simplifies the layout by collapsing empty cells. The exact behavior depends on subclasses, the
3719 default implementation does nothing.
3720
3721 Not all layouts need simplification. For example, QCPLayoutInset doesn't use explicit
3722 simplification while QCPLayoutGrid does.
3723*/
3725{
3726}
3727
3728/*!
3729 Removes and deletes the element at the provided \a index. Returns true on success. If \a index is
3730 invalid or points to an empty cell, returns false.
3731
3732 This function internally uses \ref takeAt to remove the element from the layout and then deletes
3733 the returned element. Note that some layouts don't remove the respective cell right away but leave an
3734 empty cell after successful removal of the layout element. To collapse empty cells, use \ref
3735 simplify.
3736
3737 \see remove, takeAt
3738*/
3739bool QCPLayout::removeAt(int index)
3740{
3741 if (QCPLayoutElement *el = takeAt(index))
3742 {
3743 delete el;
3744 return true;
3745 } else
3746 return false;
3747}
3748
3749/*!
3750 Removes and deletes the provided \a element. Returns true on success. If \a element is not in the
3751 layout, returns false.
3752
3753 This function internally uses \ref takeAt to remove the element from the layout and then deletes
3754 the element. Note that some layouts don't remove the respective cell right away but leave an
3755 empty cell after successful removal of the layout element. To collapse empty cells, use \ref
3756 simplify.
3757
3758 \see removeAt, take
3759*/
3761{
3762 if (take(element))
3763 {
3764 delete element;
3765 return true;
3766 } else
3767 return false;
3768}
3769
3770/*!
3771 Removes and deletes all layout elements in this layout. Finally calls \ref simplify to make sure
3772 all empty cells are collapsed.
3773
3774 \see remove, removeAt
3775*/
3777{
3778 for (int i=elementCount()-1; i>=0; --i)
3779 {
3780 if (elementAt(i))
3781 removeAt(i);
3782 }
3783 simplify();
3784}
3785
3786/*!
3787 Subclasses call this method to report changed (minimum/maximum) size constraints.
3788
3789 If the parent of this layout is again a QCPLayout, forwards the call to the parent's \ref
3790 sizeConstraintsChanged. If the parent is a QWidget (i.e. is the \ref QCustomPlot::plotLayout of
3791 QCustomPlot), calls QWidget::updateGeometry, so if the QCustomPlot widget is inside a Qt QLayout,
3792 it may update itself and resize cells accordingly.
3793*/
3795{
3797 w->updateGeometry();
3798 else if (QCPLayout *l = qobject_cast<QCPLayout*>(parent()))
3799 l->sizeConstraintsChanged();
3800}
3801
3802/*! \internal
3803
3804 Subclasses reimplement this method to update the position and sizes of the child elements/cells
3805 via calling their \ref QCPLayoutElement::setOuterRect. The default implementation does nothing.
3806
3807 The geometry used as a reference is the inner \ref rect of this layout. Child elements should stay
3808 within that rect.
3809
3810 \ref getSectionSizes may help with the reimplementation of this function.
3811
3812 \see update
3813*/
3815{
3816}
3817
3818
3819/*! \internal
3820
3821 Associates \a el with this layout. This is done by setting the \ref QCPLayoutElement::layout, the
3822 \ref QCPLayerable::parentLayerable and the QObject parent to this layout.
3823
3824 Further, if \a el didn't previously have a parent plot, calls \ref
3825 QCPLayerable::initializeParentPlot on \a el to set the paret plot.
3826
3827 This method is used by subclass specific methods that add elements to the layout. Note that this
3828 method only changes properties in \a el. The removal from the old layout and the insertion into
3829 the new layout must be done additionally.
3830*/
3832{
3833 if (el)
3834 {
3835 el->mParentLayout = this;
3836 el->setParentLayerable(this);
3837 el->setParent(this);
3838 if (!el->parentPlot())
3839 el->initializeParentPlot(mParentPlot);
3840 el->layoutChanged();
3841 } else
3842 qDebug() << Q_FUNC_INFO << "Null element passed";
3843}
3844
3845/*! \internal
3846
3847 Disassociates \a el from this layout. This is done by setting the \ref QCPLayoutElement::layout
3848 and the \ref QCPLayerable::parentLayerable to zero. The QObject parent is set to the parent
3849 QCustomPlot.
3850
3851 This method is used by subclass specific methods that remove elements from the layout (e.g. \ref
3852 take or \ref takeAt). Note that this method only changes properties in \a el. The removal from
3853 the old layout must be done additionally.
3854*/
3856{
3857 if (el)
3858 {
3859 el->mParentLayout = nullptr;
3860 el->setParentLayerable(nullptr);
3861 el->setParent(mParentPlot);
3862 // Note: Don't initializeParentPlot(0) here, because layout element will stay in same parent plot
3863 } else
3864 qDebug() << Q_FUNC_INFO << "Null element passed";
3865}
3866
3867/*! \internal
3868
3869 This is a helper function for the implementation of \ref updateLayout in subclasses.
3870
3871 It calculates the sizes of one-dimensional sections with provided constraints on maximum section
3872 sizes, minimum section sizes, relative stretch factors and the final total size of all sections.
3873
3874 The QVector entries refer to the sections. Thus all QVectors must have the same size.
3875
3876 \a maxSizes gives the maximum allowed size of each section. If there shall be no maximum size
3877 imposed, set all vector values to Qt's QWIDGETSIZE_MAX.
3878
3879 \a minSizes gives the minimum allowed size of each section. If there shall be no minimum size
3880 imposed, set all vector values to zero. If the \a minSizes entries add up to a value greater than
3881 \a totalSize, sections will be scaled smaller than the proposed minimum sizes. (In other words,
3882 not exceeding the allowed total size is taken to be more important than not going below minimum
3883 section sizes.)
3884
3885 \a stretchFactors give the relative proportions of the sections to each other. If all sections
3886 shall be scaled equally, set all values equal. If the first section shall be double the size of
3887 each individual other section, set the first number of \a stretchFactors to double the value of
3888 the other individual values (e.g. {2, 1, 1, 1}).
3889
3890 \a totalSize is the value that the final section sizes will add up to. Due to rounding, the
3891 actual sum may differ slightly. If you want the section sizes to sum up to exactly that value,
3892 you could distribute the remaining difference on the sections.
3893
3894 The return value is a QVector containing the section sizes.
3895*/
3896QVector<int> QCPLayout::getSectionSizes(QVector<int> maxSizes, QVector<int> minSizes, QVector<double> stretchFactors, int totalSize) const
3897{
3898 if (maxSizes.size() != minSizes.size() || minSizes.size() != stretchFactors.size())
3899 {
3900 qDebug() << Q_FUNC_INFO << "Passed vector sizes aren't equal:" << maxSizes << minSizes << stretchFactors;
3901 return QVector<int>();
3902 }
3903 if (stretchFactors.isEmpty())
3904 return QVector<int>();
3905 int sectionCount = stretchFactors.size();
3906 QVector<double> sectionSizes(sectionCount);
3907 // if provided total size is forced smaller than total minimum size, ignore minimum sizes (squeeze sections):
3908 int minSizeSum = 0;
3909 for (int i=0; i<sectionCount; ++i)
3910 minSizeSum += minSizes.at(i);
3911 if (totalSize < minSizeSum)
3912 {
3913 // new stretch factors are minimum sizes and minimum sizes are set to zero:
3914 for (int i=0; i<sectionCount; ++i)
3915 {
3916 stretchFactors[i] = minSizes.at(i);
3917 minSizes[i] = 0;
3918 }
3919 }
3920
3921 QList<int> minimumLockedSections;
3922 QList<int> unfinishedSections;
3923 for (int i=0; i<sectionCount; ++i)
3924 unfinishedSections.append(i);
3925 double freeSize = totalSize;
3926
3927 int outerIterations = 0;
3928 while (!unfinishedSections.isEmpty() && outerIterations < sectionCount*2) // the iteration check ist just a failsafe in case something really strange happens
3929 {
3930 ++outerIterations;
3931 int innerIterations = 0;
3932 while (!unfinishedSections.isEmpty() && innerIterations < sectionCount*2) // the iteration check ist just a failsafe in case something really strange happens
3933 {
3934 ++innerIterations;
3935 // find section that hits its maximum next:
3936 int nextId = -1;
3937 double nextMax = 1e12;
3938 foreach (int secId, unfinishedSections)
3939 {
3940 double hitsMaxAt = (maxSizes.at(secId)-sectionSizes.at(secId))/stretchFactors.at(secId);
3941 if (hitsMaxAt < nextMax)
3942 {
3943 nextMax = hitsMaxAt;
3944 nextId = secId;
3945 }
3946 }
3947 // check if that maximum is actually within the bounds of the total size (i.e. can we stretch all remaining sections so far that the found section
3948 // actually hits its maximum, without exceeding the total size when we add up all sections)
3949 double stretchFactorSum = 0;
3950 foreach (int secId, unfinishedSections)
3951 stretchFactorSum += stretchFactors.at(secId);
3952 double nextMaxLimit = freeSize/stretchFactorSum;
3953 if (nextMax < nextMaxLimit) // next maximum is actually hit, move forward to that point and fix the size of that section
3954 {
3955 foreach (int secId, unfinishedSections)
3956 {
3957 sectionSizes[secId] += nextMax*stretchFactors.at(secId); // increment all sections
3958 freeSize -= nextMax*stretchFactors.at(secId);
3959 }
3960 unfinishedSections.removeOne(nextId); // exclude the section that is now at maximum from further changes
3961 } else // next maximum isn't hit, just distribute rest of free space on remaining sections
3962 {
3963 foreach (int secId, unfinishedSections)
3964 sectionSizes[secId] += nextMaxLimit*stretchFactors.at(secId); // increment all sections
3965 unfinishedSections.clear();
3966 }
3967 }
3968 if (innerIterations == sectionCount*2)
3969 qDebug() << Q_FUNC_INFO << "Exceeded maximum expected inner iteration count, layouting aborted. Input was:" << maxSizes << minSizes << stretchFactors << totalSize;
3970
3971 // now check whether the resulting section sizes violate minimum restrictions:
3972 bool foundMinimumViolation = false;
3973 for (int i=0; i<sectionSizes.size(); ++i)
3974 {
3975 if (minimumLockedSections.contains(i))
3976 continue;
3977 if (sectionSizes.at(i) < minSizes.at(i)) // section violates minimum
3978 {
3979 sectionSizes[i] = minSizes.at(i); // set it to minimum
3980 foundMinimumViolation = true; // make sure we repeat the whole optimization process
3981 minimumLockedSections.append(i);
3982 }
3983 }
3984 if (foundMinimumViolation)
3985 {
3986 freeSize = totalSize;
3987 for (int i=0; i<sectionCount; ++i)
3988 {
3989 if (!minimumLockedSections.contains(i)) // only put sections that haven't hit their minimum back into the pool
3990 unfinishedSections.append(i);
3991 else
3992 freeSize -= sectionSizes.at(i); // remove size of minimum locked sections from available space in next round
3993 }
3994 // reset all section sizes to zero that are in unfinished sections (all others have been set to their minimum):
3995 foreach (int secId, unfinishedSections)
3996 sectionSizes[secId] = 0;
3997 }
3998 }
3999 if (outerIterations == sectionCount*2)
4000 qDebug() << Q_FUNC_INFO << "Exceeded maximum expected outer iteration count, layouting aborted. Input was:" << maxSizes << minSizes << stretchFactors << totalSize;
4001
4002 QVector<int> result(sectionCount);
4003 for (int i=0; i<sectionCount; ++i)
4004 result[i] = qRound(sectionSizes.at(i));
4005 return result;
4006}
4007
4008/*! \internal
4009
4010 This is a helper function for the implementation of subclasses.
4011
4012 It returns the minimum size that should finally be used for the outer rect of the passed layout
4013 element \a el.
4014
4015 It takes into account whether a manual minimum size is set (\ref
4016 QCPLayoutElement::setMinimumSize), which size constraint is set (\ref
4017 QCPLayoutElement::setSizeConstraintRect), as well as the minimum size hint, if no manual minimum
4018 size was set (\ref QCPLayoutElement::minimumOuterSizeHint).
4019*/
4021{
4022 QSize minOuterHint = el->minimumOuterSizeHint();
4023 QSize minOuter = el->minimumSize(); // depending on sizeConstraitRect this might be with respect to inner rect, so possibly add margins in next four lines (preserving unset minimum of 0)
4024 if (minOuter.width() > 0 && el->sizeConstraintRect() == QCPLayoutElement::scrInnerRect)
4025 minOuter.rwidth() += el->margins().left() + el->margins().right();
4026 if (minOuter.height() > 0 && el->sizeConstraintRect() == QCPLayoutElement::scrInnerRect)
4027 minOuter.rheight() += el->margins().top() + el->margins().bottom();
4028
4029 return {minOuter.width() > 0 ? minOuter.width() : minOuterHint.width(),
4030 minOuter.height() > 0 ? minOuter.height() : minOuterHint.height()};
4031}
4032
4033/*! \internal
4034
4035 This is a helper function for the implementation of subclasses.
4036
4037 It returns the maximum size that should finally be used for the outer rect of the passed layout
4038 element \a el.
4039
4040 It takes into account whether a manual maximum size is set (\ref
4041 QCPLayoutElement::setMaximumSize), which size constraint is set (\ref
4042 QCPLayoutElement::setSizeConstraintRect), as well as the maximum size hint, if no manual maximum
4043 size was set (\ref QCPLayoutElement::maximumOuterSizeHint).
4044*/
4046{
4047 QSize maxOuterHint = el->maximumOuterSizeHint();
4048 QSize maxOuter = el->maximumSize(); // depending on sizeConstraitRect this might be with respect to inner rect, so possibly add margins in next four lines (preserving unset maximum of QWIDGETSIZE_MAX)
4049 if (maxOuter.width() < QWIDGETSIZE_MAX && el->sizeConstraintRect() == QCPLayoutElement::scrInnerRect)
4050 maxOuter.rwidth() += el->margins().left() + el->margins().right();
4051 if (maxOuter.height() < QWIDGETSIZE_MAX && el->sizeConstraintRect() == QCPLayoutElement::scrInnerRect)
4052 maxOuter.rheight() += el->margins().top() + el->margins().bottom();
4053
4054 return {maxOuter.width() < QWIDGETSIZE_MAX ? maxOuter.width() : maxOuterHint.width(),
4055 maxOuter.height() < QWIDGETSIZE_MAX ? maxOuter.height() : maxOuterHint.height()};
4056}
4057
4058
4059////////////////////////////////////////////////////////////////////////////////////////////////////
4060//////////////////// QCPLayoutGrid
4061////////////////////////////////////////////////////////////////////////////////////////////////////
4062
4063/*! \class QCPLayoutGrid
4064 \brief A layout that arranges child elements in a grid
4065
4066 Elements are laid out in a grid with configurable stretch factors (\ref setColumnStretchFactor,
4067 \ref setRowStretchFactor) and spacing (\ref setColumnSpacing, \ref setRowSpacing).
4068
4069 Elements can be added to cells via \ref addElement. The grid is expanded if the specified row or
4070 column doesn't exist yet. Whether a cell contains a valid layout element can be checked with \ref
4071 hasElement, that element can be retrieved with \ref element. If rows and columns that only have
4072 empty cells shall be removed, call \ref simplify. Removal of elements is either done by just
4073 adding the element to a different layout or by using the QCPLayout interface \ref take or \ref
4074 remove.
4075
4076 If you use \ref addElement(QCPLayoutElement*) without explicit parameters for \a row and \a
4077 column, the grid layout will choose the position according to the current \ref setFillOrder and
4078 the wrapping (\ref setWrap).
4079
4080 Row and column insertion can be performed with \ref insertRow and \ref insertColumn.
4081*/
4082
4083/* start documentation of inline functions */
4084
4085/*! \fn int QCPLayoutGrid::rowCount() const
4086
4087 Returns the number of rows in the layout.
4088
4089 \see columnCount
4090*/
4091
4092/*! \fn int QCPLayoutGrid::columnCount() const
4093
4094 Returns the number of columns in the layout.
4095
4096 \see rowCount
4097*/
4098
4099/* end documentation of inline functions */
4100
4101/*!
4102 Creates an instance of QCPLayoutGrid and sets default values.
4103*/
4105 mColumnSpacing(5),
4106 mRowSpacing(5),
4107 mWrap(0),
4108 mFillOrder(foColumnsFirst)
4109{
4110}
4111
4112QCPLayoutGrid::~QCPLayoutGrid()
4113{
4114 // clear all child layout elements. This is important because only the specific layouts know how
4115 // to handle removing elements (clear calls virtual removeAt method to do that).
4116 clear();
4117}
4118
4119/*!
4120 Returns the element in the cell in \a row and \a column.
4121
4122 Returns \c nullptr if either the row/column is invalid or if the cell is empty. In those cases, a
4123 qDebug message is printed. To check whether a cell exists and isn't empty, use \ref hasElement.
4124
4125 \see addElement, hasElement
4126*/
4127QCPLayoutElement *QCPLayoutGrid::element(int row, int column) const
4128{
4129 if (row >= 0 && row < mElements.size())
4130 {
4131 if (column >= 0 && column < mElements.first().size())
4132 {
4133 if (QCPLayoutElement *result = mElements.at(row).at(column))
4134 return result;
4135 else
4136 qDebug() << Q_FUNC_INFO << "Requested cell is empty. Row:" << row << "Column:" << column;
4137 } else
4138 qDebug() << Q_FUNC_INFO << "Invalid column. Row:" << row << "Column:" << column;
4139 } else
4140 qDebug() << Q_FUNC_INFO << "Invalid row. Row:" << row << "Column:" << column;
4141 return nullptr;
4142}
4143
4144
4145/*! \overload
4146
4147 Adds the \a element to cell with \a row and \a column. If \a element is already in a layout, it
4148 is first removed from there. If \a row or \a column don't exist yet, the layout is expanded
4149 accordingly.
4150
4151 Returns true if the element was added successfully, i.e. if the cell at \a row and \a column
4152 didn't already have an element.
4153
4154 Use the overload of this method without explicit row/column index to place the element according
4155 to the configured fill order and wrapping settings.
4156
4157 \see element, hasElement, take, remove
4158*/
4159bool QCPLayoutGrid::addElement(int row, int column, QCPLayoutElement *element)
4160{
4161 if (!hasElement(row, column))
4162 {
4163 if (element && element->layout()) // remove from old layout first
4165 expandTo(row+1, column+1);
4166 mElements[row][column] = element;
4167 if (element)
4169 return true;
4170 } else
4171 qDebug() << Q_FUNC_INFO << "There is already an element in the specified row/column:" << row << column;
4172 return false;
4173}
4174
4175/*! \overload
4176
4177 Adds the \a element to the next empty cell according to the current fill order (\ref
4178 setFillOrder) and wrapping (\ref setWrap). If \a element is already in a layout, it is first
4179 removed from there. If necessary, the layout is expanded to hold the new element.
4180
4181 Returns true if the element was added successfully.
4182
4183 \see setFillOrder, setWrap, element, hasElement, take, remove
4184*/
4186{
4187 int rowIndex = 0;
4188 int colIndex = 0;
4189 if (mFillOrder == foColumnsFirst)
4190 {
4191 while (hasElement(rowIndex, colIndex))
4192 {
4193 ++colIndex;
4194 if (colIndex >= mWrap && mWrap > 0)
4195 {
4196 colIndex = 0;
4197 ++rowIndex;
4198 }
4199 }
4200 } else
4201 {
4202 while (hasElement(rowIndex, colIndex))
4203 {
4204 ++rowIndex;
4205 if (rowIndex >= mWrap && mWrap > 0)
4206 {
4207 rowIndex = 0;
4208 ++colIndex;
4209 }
4210 }
4211 }
4212 return addElement(rowIndex, colIndex, element);
4213}
4214
4215/*!
4216 Returns whether the cell at \a row and \a column exists and contains a valid element, i.e. isn't
4217 empty.
4218
4219 \see element
4220*/
4221bool QCPLayoutGrid::hasElement(int row, int column)
4222{
4223 if (row >= 0 && row < rowCount() && column >= 0 && column < columnCount())
4224 return mElements.at(row).at(column);
4225 else
4226 return false;
4227}
4228
4229/*!
4230 Sets the stretch \a factor of \a column.
4231
4232 Stretch factors control the relative sizes of rows and columns. Cells will not be resized beyond
4233 their minimum and maximum widths/heights, regardless of the stretch factor. (see \ref
4234 QCPLayoutElement::setMinimumSize, \ref QCPLayoutElement::setMaximumSize, \ref
4235 QCPLayoutElement::setSizeConstraintRect.)
4236
4237 The default stretch factor of newly created rows/columns is 1.
4238
4239 \see setColumnStretchFactors, setRowStretchFactor
4240*/
4241void QCPLayoutGrid::setColumnStretchFactor(int column, double factor)
4242{
4243 if (column >= 0 && column < columnCount())
4244 {
4245 if (factor > 0)
4246 mColumnStretchFactors[column] = factor;
4247 else
4248 qDebug() << Q_FUNC_INFO << "Invalid stretch factor, must be positive:" << factor;
4249 } else
4250 qDebug() << Q_FUNC_INFO << "Invalid column:" << column;
4251}
4252
4253/*!
4254 Sets the stretch \a factors of all columns. \a factors must have the size \ref columnCount.
4255
4256 Stretch factors control the relative sizes of rows and columns. Cells will not be resized beyond
4257 their minimum and maximum widths/heights, regardless of the stretch factor. (see \ref
4258 QCPLayoutElement::setMinimumSize, \ref QCPLayoutElement::setMaximumSize, \ref
4259 QCPLayoutElement::setSizeConstraintRect.)
4260
4261 The default stretch factor of newly created rows/columns is 1.
4262
4263 \see setColumnStretchFactor, setRowStretchFactors
4264*/
4266{
4267 if (factors.size() == mColumnStretchFactors.size())
4268 {
4269 mColumnStretchFactors = factors;
4270 for (int i=0; i<mColumnStretchFactors.size(); ++i)
4271 {
4272 if (mColumnStretchFactors.at(i) <= 0)
4273 {
4274 qDebug() << Q_FUNC_INFO << "Invalid stretch factor, must be positive:" << mColumnStretchFactors.at(i);
4275 mColumnStretchFactors[i] = 1;
4276 }
4277 }
4278 } else
4279 qDebug() << Q_FUNC_INFO << "Column count not equal to passed stretch factor count:" << factors;
4280}
4281
4282/*!
4283 Sets the stretch \a factor of \a row.
4284
4285 Stretch factors control the relative sizes of rows and columns. Cells will not be resized beyond
4286 their minimum and maximum widths/heights, regardless of the stretch factor. (see \ref
4287 QCPLayoutElement::setMinimumSize, \ref QCPLayoutElement::setMaximumSize, \ref
4288 QCPLayoutElement::setSizeConstraintRect.)
4289
4290 The default stretch factor of newly created rows/columns is 1.
4291
4292 \see setColumnStretchFactors, setRowStretchFactor
4293*/
4294void QCPLayoutGrid::setRowStretchFactor(int row, double factor)
4295{
4296 if (row >= 0 && row < rowCount())
4297 {
4298 if (factor > 0)
4299 mRowStretchFactors[row] = factor;
4300 else
4301 qDebug() << Q_FUNC_INFO << "Invalid stretch factor, must be positive:" << factor;
4302 } else
4303 qDebug() << Q_FUNC_INFO << "Invalid row:" << row;
4304}
4305
4306/*!
4307 Sets the stretch \a factors of all rows. \a factors must have the size \ref rowCount.
4308
4309 Stretch factors control the relative sizes of rows and columns. Cells will not be resized beyond
4310 their minimum and maximum widths/heights, regardless of the stretch factor. (see \ref
4311 QCPLayoutElement::setMinimumSize, \ref QCPLayoutElement::setMaximumSize, \ref
4312 QCPLayoutElement::setSizeConstraintRect.)
4313
4314 The default stretch factor of newly created rows/columns is 1.
4315
4316 \see setRowStretchFactor, setColumnStretchFactors
4317*/
4319{
4320 if (factors.size() == mRowStretchFactors.size())
4321 {
4322 mRowStretchFactors = factors;
4323 for (int i=0; i<mRowStretchFactors.size(); ++i)
4324 {
4325 if (mRowStretchFactors.at(i) <= 0)
4326 {
4327 qDebug() << Q_FUNC_INFO << "Invalid stretch factor, must be positive:" << mRowStretchFactors.at(i);
4328 mRowStretchFactors[i] = 1;
4329 }
4330 }
4331 } else
4332 qDebug() << Q_FUNC_INFO << "Row count not equal to passed stretch factor count:" << factors;
4333}
4334
4335/*!
4336 Sets the gap that is left blank between columns to \a pixels.
4337
4338 \see setRowSpacing
4339*/
4341{
4342 mColumnSpacing = pixels;
4343}
4344
4345/*!
4346 Sets the gap that is left blank between rows to \a pixels.
4347
4348 \see setColumnSpacing
4349*/
4351{
4352 mRowSpacing = pixels;
4353}
4354
4355/*!
4356 Sets the maximum number of columns or rows that are used, before new elements added with \ref
4357 addElement(QCPLayoutElement*) will start to fill the next row or column, respectively. It depends
4358 on \ref setFillOrder, whether rows or columns are wrapped.
4359
4360 If \a count is set to zero, no wrapping will ever occur.
4361
4362 If you wish to re-wrap the elements currently in the layout, call \ref setFillOrder with \a
4363 rearrange set to true (the actual fill order doesn't need to be changed for the rearranging to be
4364 done).
4365
4366 Note that the method \ref addElement(int row, int column, QCPLayoutElement *element) with
4367 explicitly stated row and column is not subject to wrapping and can place elements even beyond
4368 the specified wrapping point.
4369
4370 \see setFillOrder
4371*/
4373{
4374 mWrap = qMax(0, count);
4375}
4376
4377/*!
4378 Sets the filling order and wrapping behaviour that is used when adding new elements with the
4379 method \ref addElement(QCPLayoutElement*).
4380
4381 The specified \a order defines whether rows or columns are filled first. Using \ref setWrap, you
4382 can control at which row/column count wrapping into the next column/row will occur. If you set it
4383 to zero, no wrapping will ever occur. Changing the fill order also changes the meaning of the
4384 linear index used e.g. in \ref elementAt and \ref takeAt. The default fill order for \ref
4385 QCPLayoutGrid is \ref foColumnsFirst.
4386
4387 If you want to have all current elements arranged in the new order, set \a rearrange to true. The
4388 elements will be rearranged in a way that tries to preserve their linear index. However, empty
4389 cells are skipped during build-up of the new cell order, which shifts the succeeding element's
4390 index. The rearranging is performed even if the specified \a order is already the current fill
4391 order. Thus this method can be used to re-wrap the current elements.
4392
4393 If \a rearrange is false, the current element arrangement is not changed, which means the
4394 linear indexes change (because the linear index is dependent on the fill order).
4395
4396 Note that the method \ref addElement(int row, int column, QCPLayoutElement *element) with
4397 explicitly stated row and column is not subject to wrapping and can place elements even beyond
4398 the specified wrapping point.
4399
4400 \see setWrap, addElement(QCPLayoutElement*)
4401*/
4402void QCPLayoutGrid::setFillOrder(FillOrder order, bool rearrange)
4403{
4404 // if rearranging, take all elements via linear index of old fill order:
4405 const int elCount = elementCount();
4406 QVector<QCPLayoutElement*> tempElements;
4407 if (rearrange)
4408 {
4409 tempElements.reserve(elCount);
4410 for (int i=0; i<elCount; ++i)
4411 {
4412 if (elementAt(i))
4413 tempElements.append(takeAt(i));
4414 }
4415 simplify();
4416 }
4417 // change fill order as requested:
4418 mFillOrder = order;
4419 // if rearranging, re-insert via linear index according to new fill order:
4420 if (rearrange)
4421 {
4422 foreach (QCPLayoutElement *tempElement, tempElements)
4423 addElement(tempElement);
4424 }
4425}
4426
4427/*!
4428 Expands the layout to have \a newRowCount rows and \a newColumnCount columns. So the last valid
4429 row index will be \a newRowCount-1, the last valid column index will be \a newColumnCount-1.
4430
4431 If the current column/row count is already larger or equal to \a newColumnCount/\a newRowCount,
4432 this function does nothing in that dimension.
4433
4434 Newly created cells are empty, new rows and columns have the stretch factor 1.
4435
4436 Note that upon a call to \ref addElement, the layout is expanded automatically to contain the
4437 specified row and column, using this function.
4438
4439 \see simplify
4440*/
4441void QCPLayoutGrid::expandTo(int newRowCount, int newColumnCount)
4442{
4443 // add rows as necessary:
4444 while (rowCount() < newRowCount)
4445 {
4446 mElements.append(QList<QCPLayoutElement*>());
4447 mRowStretchFactors.append(1);
4448 }
4449 // go through rows and expand columns as necessary:
4450 int newColCount = qMax(columnCount(), newColumnCount);
4451 for (int i=0; i<rowCount(); ++i)
4452 {
4453 while (mElements.at(i).size() < newColCount)
4454 mElements[i].append(nullptr);
4455 }
4456 while (mColumnStretchFactors.size() < newColCount)
4457 mColumnStretchFactors.append(1);
4458}
4459
4460/*!
4461 Inserts a new row with empty cells at the row index \a newIndex. Valid values for \a newIndex
4462 range from 0 (inserts a row at the top) to \a rowCount (appends a row at the bottom).
4463
4464 \see insertColumn
4465*/
4467{
4468 if (mElements.isEmpty() || mElements.first().isEmpty()) // if grid is completely empty, add first cell
4469 {
4470 expandTo(1, 1);
4471 return;
4472 }
4473
4474 if (newIndex < 0)
4475 newIndex = 0;
4476 if (newIndex > rowCount())
4477 newIndex = rowCount();
4478
4479 mRowStretchFactors.insert(newIndex, 1);
4481 for (int col=0; col<columnCount(); ++col)
4482 newRow.append(nullptr);
4483 mElements.insert(newIndex, newRow);
4484}
4485
4486/*!
4487 Inserts a new column with empty cells at the column index \a newIndex. Valid values for \a
4488 newIndex range from 0 (inserts a column at the left) to \a columnCount (appends a column at the
4489 right).
4490
4491 \see insertRow
4492*/
4494{
4495 if (mElements.isEmpty() || mElements.first().isEmpty()) // if grid is completely empty, add first cell
4496 {
4497 expandTo(1, 1);
4498 return;
4499 }
4500
4501 if (newIndex < 0)
4502 newIndex = 0;
4503 if (newIndex > columnCount())
4504 newIndex = columnCount();
4505
4506 mColumnStretchFactors.insert(newIndex, 1);
4507 for (int row=0; row<rowCount(); ++row)
4508 mElements[row].insert(newIndex, nullptr);
4509}
4510
4511/*!
4512 Converts the given \a row and \a column to the linear index used by some methods of \ref
4513 QCPLayoutGrid and \ref QCPLayout.
4514
4515 The way the cells are indexed depends on \ref setFillOrder. If it is \ref foRowsFirst, the
4516 indices increase left to right and then top to bottom. If it is \ref foColumnsFirst, the indices
4517 increase top to bottom and then left to right.
4518
4519 For the returned index to be valid, \a row and \a column must be valid indices themselves, i.e.
4520 greater or equal to zero and smaller than the current \ref rowCount/\ref columnCount.
4521
4522 \see indexToRowCol
4523*/
4524int QCPLayoutGrid::rowColToIndex(int row, int column) const
4525{
4526 if (row >= 0 && row < rowCount())
4527 {
4528 if (column >= 0 && column < columnCount())
4529 {
4530 switch (mFillOrder)
4531 {
4532 case foRowsFirst: return column*rowCount() + row;
4533 case foColumnsFirst: return row*columnCount() + column;
4534 }
4535 } else
4536 qDebug() << Q_FUNC_INFO << "row index out of bounds:" << row;
4537 } else
4538 qDebug() << Q_FUNC_INFO << "column index out of bounds:" << column;
4539 return 0;
4540}
4541
4542/*!
4543 Converts the linear index to row and column indices and writes the result to \a row and \a
4544 column.
4545
4546 The way the cells are indexed depends on \ref setFillOrder. If it is \ref foRowsFirst, the
4547 indices increase left to right and then top to bottom. If it is \ref foColumnsFirst, the indices
4548 increase top to bottom and then left to right.
4549
4550 If there are no cells (i.e. column or row count is zero), sets \a row and \a column to -1.
4551
4552 For the retrieved \a row and \a column to be valid, the passed \a index must be valid itself,
4553 i.e. greater or equal to zero and smaller than the current \ref elementCount.
4554
4555 \see rowColToIndex
4556*/
4557void QCPLayoutGrid::indexToRowCol(int index, int &row, int &column) const
4558{
4559 row = -1;
4560 column = -1;
4561 const int nCols = columnCount();
4562 const int nRows = rowCount();
4563 if (nCols == 0 || nRows == 0)
4564 return;
4565 if (index < 0 || index >= elementCount())
4566 {
4567 qDebug() << Q_FUNC_INFO << "index out of bounds:" << index;
4568 return;
4569 }
4570
4571 switch (mFillOrder)
4572 {
4573 case foRowsFirst:
4574 {
4575 column = index / nRows;
4576 row = index % nRows;
4577 break;
4578 }
4579 case foColumnsFirst:
4580 {
4581 row = index / nCols;
4582 column = index % nCols;
4583 break;
4584 }
4585 }
4586}
4587
4588/* inherits documentation from base class */
4590{
4591 QVector<int> minColWidths, minRowHeights, maxColWidths, maxRowHeights;
4592 getMinimumRowColSizes(&minColWidths, &minRowHeights);
4593 getMaximumRowColSizes(&maxColWidths, &maxRowHeights);
4594
4595 int totalRowSpacing = (rowCount()-1) * mRowSpacing;
4596 int totalColSpacing = (columnCount()-1) * mColumnSpacing;
4597 QVector<int> colWidths = getSectionSizes(maxColWidths, minColWidths, mColumnStretchFactors.toVector(), mRect.width()-totalColSpacing);
4598 QVector<int> rowHeights = getSectionSizes(maxRowHeights, minRowHeights, mRowStretchFactors.toVector(), mRect.height()-totalRowSpacing);
4599
4600 // go through cells and set rects accordingly:
4601 int yOffset = mRect.top();
4602 for (int row=0; row<rowCount(); ++row)
4603 {
4604 if (row > 0)
4605 yOffset += rowHeights.at(row-1)+mRowSpacing;
4606 int xOffset = mRect.left();
4607 for (int col=0; col<columnCount(); ++col)
4608 {
4609 if (col > 0)
4610 xOffset += colWidths.at(col-1)+mColumnSpacing;
4611 if (mElements.at(row).at(col))
4612 mElements.at(row).at(col)->setOuterRect(QRect(xOffset, yOffset, colWidths.at(col), rowHeights.at(row)));
4613 }
4614 }
4615}
4616
4617/*!
4618 \seebaseclassmethod
4619
4620 Note that the association of the linear \a index to the row/column based cells depends on the
4621 current setting of \ref setFillOrder.
4622
4623 \see rowColToIndex
4624*/
4626{
4627 if (index >= 0 && index < elementCount())
4628 {
4629 int row, col;
4630 indexToRowCol(index, row, col);
4631 return mElements.at(row).at(col);
4632 } else
4633 return nullptr;
4634}
4635
4636/*!
4637 \seebaseclassmethod
4638
4639 Note that the association of the linear \a index to the row/column based cells depends on the
4640 current setting of \ref setFillOrder.
4641
4642 \see rowColToIndex
4643*/
4645{
4646 if (QCPLayoutElement *el = elementAt(index))
4647 {
4648 releaseElement(el);
4649 int row, col;
4650 indexToRowCol(index, row, col);
4651 mElements[row][col] = nullptr;
4652 return el;
4653 } else
4654 {
4655 qDebug() << Q_FUNC_INFO << "Attempt to take invalid index:" << index;
4656 return nullptr;
4657 }
4658}
4659
4660/* inherits documentation from base class */
4662{
4663 if (element)
4664 {
4665 for (int i=0; i<elementCount(); ++i)
4666 {
4667 if (elementAt(i) == element)
4668 {
4669 takeAt(i);
4670 return true;
4671 }
4672 }
4673 qDebug() << Q_FUNC_INFO << "Element not in this layout, couldn't take";
4674 } else
4675 qDebug() << Q_FUNC_INFO << "Can't take nullptr element";
4676 return false;
4677}
4678
4679/* inherits documentation from base class */
4681{
4683 const int elCount = elementCount();
4684#if QT_VERSION >= QT_VERSION_CHECK(4, 7, 0)
4685 result.reserve(elCount);
4686#endif
4687 for (int i=0; i<elCount; ++i)
4688 result.append(elementAt(i));
4689 if (recursive)
4690 {
4691 for (int i=0; i<elCount; ++i)
4692 {
4693 if (result.at(i))
4694 result << result.at(i)->elements(recursive);
4695 }
4696 }
4697 return result;
4698}
4699
4700/*!
4701 Simplifies the layout by collapsing rows and columns which only contain empty cells.
4702*/
4704{
4705 // remove rows with only empty cells:
4706 for (int row=rowCount()-1; row>=0; --row)
4707 {
4708 bool hasElements = false;
4709 for (int col=0; col<columnCount(); ++col)
4710 {
4711 if (mElements.at(row).at(col))
4712 {
4713 hasElements = true;
4714 break;
4715 }
4716 }
4717 if (!hasElements)
4718 {
4719 mRowStretchFactors.removeAt(row);
4720 mElements.removeAt(row);
4721 if (mElements.isEmpty()) // removed last element, also remove stretch factor (wouldn't happen below because also columnCount changed to 0 now)
4722 mColumnStretchFactors.clear();
4723 }
4724 }
4725
4726 // remove columns with only empty cells:
4727 for (int col=columnCount()-1; col>=0; --col)
4728 {
4729 bool hasElements = false;
4730 for (int row=0; row<rowCount(); ++row)
4731 {
4732 if (mElements.at(row).at(col))
4733 {
4734 hasElements = true;
4735 break;
4736 }
4737 }
4738 if (!hasElements)
4739 {
4740 mColumnStretchFactors.removeAt(col);
4741 for (int row=0; row<rowCount(); ++row)
4742 mElements[row].removeAt(col);
4743 }
4744 }
4745}
4746
4747/* inherits documentation from base class */
4749{
4750 QVector<int> minColWidths, minRowHeights;
4751 getMinimumRowColSizes(&minColWidths, &minRowHeights);
4752 QSize result(0, 0);
4753 foreach (int w, minColWidths)
4754 result.rwidth() += w;
4755 foreach (int h, minRowHeights)
4756 result.rheight() += h;
4757 result.rwidth() += qMax(0, columnCount()-1) * mColumnSpacing;
4758 result.rheight() += qMax(0, rowCount()-1) * mRowSpacing;
4759 result.rwidth() += mMargins.left()+mMargins.right();
4760 result.rheight() += mMargins.top()+mMargins.bottom();
4761 return result;
4762}
4763
4764/* inherits documentation from base class */
4766{
4767 QVector<int> maxColWidths, maxRowHeights;
4768 getMaximumRowColSizes(&maxColWidths, &maxRowHeights);
4769
4770 QSize result(0, 0);
4771 foreach (int w, maxColWidths)
4772 result.setWidth(qMin(result.width()+w, QWIDGETSIZE_MAX));
4773 foreach (int h, maxRowHeights)
4774 result.setHeight(qMin(result.height()+h, QWIDGETSIZE_MAX));
4775 result.rwidth() += qMax(0, columnCount()-1) * mColumnSpacing;
4776 result.rheight() += qMax(0, rowCount()-1) * mRowSpacing;
4777 result.rwidth() += mMargins.left()+mMargins.right();
4778 result.rheight() += mMargins.top()+mMargins.bottom();
4779 if (result.height() > QWIDGETSIZE_MAX)
4780 result.setHeight(QWIDGETSIZE_MAX);
4781 if (result.width() > QWIDGETSIZE_MAX)
4782 result.setWidth(QWIDGETSIZE_MAX);
4783 return result;
4784}
4785
4786/*! \internal
4787
4788 Places the minimum column widths and row heights into \a minColWidths and \a minRowHeights
4789 respectively.
4790
4791 The minimum height of a row is the largest minimum height of any element's outer rect in that
4792 row. The minimum width of a column is the largest minimum width of any element's outer rect in
4793 that column.
4794
4795 This is a helper function for \ref updateLayout.
4796
4797 \see getMaximumRowColSizes
4798*/
4799void QCPLayoutGrid::getMinimumRowColSizes(QVector<int> *minColWidths, QVector<int> *minRowHeights) const
4800{
4801 *minColWidths = QVector<int>(columnCount(), 0);
4802 *minRowHeights = QVector<int>(rowCount(), 0);
4803 for (int row=0; row<rowCount(); ++row)
4804 {
4805 for (int col=0; col<columnCount(); ++col)
4806 {
4807 if (QCPLayoutElement *el = mElements.at(row).at(col))
4808 {
4809 QSize minSize = getFinalMinimumOuterSize(el);
4810 if (minColWidths->at(col) < minSize.width())
4811 (*minColWidths)[col] = minSize.width();
4812 if (minRowHeights->at(row) < minSize.height())
4813 (*minRowHeights)[row] = minSize.height();
4814 }
4815 }
4816 }
4817}
4818
4819/*! \internal
4820
4821 Places the maximum column widths and row heights into \a maxColWidths and \a maxRowHeights
4822 respectively.
4823
4824 The maximum height of a row is the smallest maximum height of any element's outer rect in that
4825 row. The maximum width of a column is the smallest maximum width of any element's outer rect in
4826 that column.
4827
4828 This is a helper function for \ref updateLayout.
4829
4830 \see getMinimumRowColSizes
4831*/
4832void QCPLayoutGrid::getMaximumRowColSizes(QVector<int> *maxColWidths, QVector<int> *maxRowHeights) const
4833{
4834 *maxColWidths = QVector<int>(columnCount(), QWIDGETSIZE_MAX);
4835 *maxRowHeights = QVector<int>(rowCount(), QWIDGETSIZE_MAX);
4836 for (int row=0; row<rowCount(); ++row)
4837 {
4838 for (int col=0; col<columnCount(); ++col)
4839 {
4840 if (QCPLayoutElement *el = mElements.at(row).at(col))
4841 {
4842 QSize maxSize = getFinalMaximumOuterSize(el);
4843 if (maxColWidths->at(col) > maxSize.width())
4844 (*maxColWidths)[col] = maxSize.width();
4845 if (maxRowHeights->at(row) > maxSize.height())
4846 (*maxRowHeights)[row] = maxSize.height();
4847 }
4848 }
4849 }
4850}
4851
4852
4853////////////////////////////////////////////////////////////////////////////////////////////////////
4854//////////////////// QCPLayoutInset
4855////////////////////////////////////////////////////////////////////////////////////////////////////
4856/*! \class QCPLayoutInset
4857 \brief A layout that places child elements aligned to the border or arbitrarily positioned
4858
4859 Elements are placed either aligned to the border or at arbitrary position in the area of the
4860 layout. Which placement applies is controlled with the \ref InsetPlacement (\ref
4861 setInsetPlacement).
4862
4863 Elements are added via \ref addElement(QCPLayoutElement *element, Qt::Alignment alignment) or
4864 addElement(QCPLayoutElement *element, const QRectF &rect). If the first method is used, the inset
4865 placement will default to \ref ipBorderAligned and the element will be aligned according to the
4866 \a alignment parameter. The second method defaults to \ref ipFree and allows placing elements at
4867 arbitrary position and size, defined by \a rect.
4868
4869 The alignment or rect can be set via \ref setInsetAlignment or \ref setInsetRect, respectively.
4870
4871 This is the layout that every QCPAxisRect has as \ref QCPAxisRect::insetLayout.
4872*/
4873
4874/* start documentation of inline functions */
4875
4876/*! \fn virtual void QCPLayoutInset::simplify()
4877
4878 The QCPInsetLayout does not need simplification since it can never have empty cells due to its
4879 linear index structure. This method does nothing.
4880*/
4881
4882/* end documentation of inline functions */
4883
4884/*!
4885 Creates an instance of QCPLayoutInset and sets default values.
4886*/
4890
4891QCPLayoutInset::~QCPLayoutInset()
4892{
4893 // clear all child layout elements. This is important because only the specific layouts know how
4894 // to handle removing elements (clear calls virtual removeAt method to do that).
4895 clear();
4896}
4897
4898/*!
4899 Returns the placement type of the element with the specified \a index.
4900*/
4902{
4903 if (elementAt(index))
4904 return mInsetPlacement.at(index);
4905 else
4906 {
4907 qDebug() << Q_FUNC_INFO << "Invalid element index:" << index;
4908 return ipFree;
4909 }
4910}
4911
4912/*!
4913 Returns the alignment of the element with the specified \a index. The alignment only has a
4914 meaning, if the inset placement (\ref setInsetPlacement) is \ref ipBorderAligned.
4915*/
4917{
4918 if (elementAt(index))
4919 return mInsetAlignment.at(index);
4920 else
4921 {
4922 qDebug() << Q_FUNC_INFO << "Invalid element index:" << index;
4923#if QT_VERSION < QT_VERSION_CHECK(5, 2, 0)
4924 return nullptr;
4925#else
4926 return {};
4927#endif
4928 }
4929}
4930
4931/*!
4932 Returns the rect of the element with the specified \a index. The rect only has a
4933 meaning, if the inset placement (\ref setInsetPlacement) is \ref ipFree.
4934*/
4936{
4937 if (elementAt(index))
4938 return mInsetRect.at(index);
4939 else
4940 {
4941 qDebug() << Q_FUNC_INFO << "Invalid element index:" << index;
4942 return {};
4943 }
4944}
4945
4946/*!
4947 Sets the inset placement type of the element with the specified \a index to \a placement.
4948
4949 \see InsetPlacement
4950*/
4952{
4953 if (elementAt(index))
4954 mInsetPlacement[index] = placement;
4955 else
4956 qDebug() << Q_FUNC_INFO << "Invalid element index:" << index;
4957}
4958
4959/*!
4960 If the inset placement (\ref setInsetPlacement) is \ref ipBorderAligned, this function
4961 is used to set the alignment of the element with the specified \a index to \a alignment.
4962
4963 \a alignment is an or combination of the following alignment flags: Qt::AlignLeft,
4964 Qt::AlignHCenter, Qt::AlighRight, Qt::AlignTop, Qt::AlignVCenter, Qt::AlignBottom. Any other
4965 alignment flags will be ignored.
4966*/
4968{
4969 if (elementAt(index))
4970 mInsetAlignment[index] = alignment;
4971 else
4972 qDebug() << Q_FUNC_INFO << "Invalid element index:" << index;
4973}
4974
4975/*!
4976 If the inset placement (\ref setInsetPlacement) is \ref ipFree, this function is used to set the
4977 position and size of the element with the specified \a index to \a rect.
4978
4979 \a rect is given in fractions of the whole inset layout rect. So an inset with rect (0, 0, 1, 1)
4980 will span the entire layout. An inset with rect (0.6, 0.1, 0.35, 0.35) will be in the top right
4981 corner of the layout, with 35% width and height of the parent layout.
4982
4983 Note that the minimum and maximum sizes of the embedded element (\ref
4984 QCPLayoutElement::setMinimumSize, \ref QCPLayoutElement::setMaximumSize) are enforced.
4985*/
4986void QCPLayoutInset::setInsetRect(int index, const QRectF &rect)
4987{
4988 if (elementAt(index))
4989 mInsetRect[index] = rect;
4990 else
4991 qDebug() << Q_FUNC_INFO << "Invalid element index:" << index;
4992}
4993
4994/* inherits documentation from base class */
4996{
4997 for (int i=0; i<mElements.size(); ++i)
4998 {
4999 QCPLayoutElement *el = mElements.at(i);
5001 QSize finalMinSize = getFinalMinimumOuterSize(el);
5002 QSize finalMaxSize = getFinalMaximumOuterSize(el);
5003 if (mInsetPlacement.at(i) == ipFree)
5004 {
5005 insetRect = QRect(int( rect().x()+rect().width()*mInsetRect.at(i).x() ),
5006 int( rect().y()+rect().height()*mInsetRect.at(i).y() ),
5007 int( rect().width()*mInsetRect.at(i).width() ),
5008 int( rect().height()*mInsetRect.at(i).height() ));
5009 if (insetRect.size().width() < finalMinSize.width())
5010 insetRect.setWidth(finalMinSize.width());
5011 if (insetRect.size().height() < finalMinSize.height())
5012 insetRect.setHeight(finalMinSize.height());
5013 if (insetRect.size().width() > finalMaxSize.width())
5014 insetRect.setWidth(finalMaxSize.width());
5015 if (insetRect.size().height() > finalMaxSize.height())
5016 insetRect.setHeight(finalMaxSize.height());
5017 } else if (mInsetPlacement.at(i) == ipBorderAligned)
5018 {
5019 insetRect.setSize(finalMinSize);
5020 Qt::Alignment al = mInsetAlignment.at(i);
5021 if (al.testFlag(Qt::AlignLeft)) insetRect.moveLeft(rect().x());
5022 else if (al.testFlag(Qt::AlignRight)) insetRect.moveRight(rect().x()+rect().width());
5023 else insetRect.moveLeft(int( rect().x()+rect().width()*0.5-finalMinSize.width()*0.5 )); // default to Qt::AlignHCenter
5024 if (al.testFlag(Qt::AlignTop)) insetRect.moveTop(rect().y());
5025 else if (al.testFlag(Qt::AlignBottom)) insetRect.moveBottom(rect().y()+rect().height());
5026 else insetRect.moveTop(int( rect().y()+rect().height()*0.5-finalMinSize.height()*0.5 )); // default to Qt::AlignVCenter
5027 }
5028 mElements.at(i)->setOuterRect(insetRect);
5032/* inherits documentation from base class */
5034{
5035 return mElements.size();
5036}
5037
5038/* inherits documentation from base class */
5040{
5041 if (index >= 0 && index < mElements.size())
5042 return mElements.at(index);
5043 else
5044 return nullptr;
5045}
5046
5047/* inherits documentation from base class */
5049{
5050 if (QCPLayoutElement *el = elementAt(index))
5051 {
5052 releaseElement(el);
5053 mElements.removeAt(index);
5054 mInsetPlacement.removeAt(index);
5055 mInsetAlignment.removeAt(index);
5056 mInsetRect.removeAt(index);
5057 return el;
5058 } else
5059 {
5060 qDebug() << Q_FUNC_INFO << "Attempt to take invalid index:" << index;
5061 return nullptr;
5062 }
5063}
5064
5065/* inherits documentation from base class */
5067{
5068 if (element)
5069 {
5070 for (int i=0; i<elementCount(); ++i)
5071 {
5072 if (elementAt(i) == element)
5073 {
5074 takeAt(i);
5075 return true;
5076 }
5077 }
5078 qDebug() << Q_FUNC_INFO << "Element not in this layout, couldn't take";
5079 } else
5080 qDebug() << Q_FUNC_INFO << "Can't take nullptr element";
5081 return false;
5082}
5083
5084/*!
5085 The inset layout is sensitive to events only at areas where its (visible) child elements are
5086 sensitive. If the selectTest method of any of the child elements returns a positive number for \a
5087 pos, this method returns a value corresponding to 0.99 times the parent plot's selection
5088 tolerance. The inset layout is not selectable itself by default. So if \a onlySelectable is true,
5089 -1.0 is returned.
5090
5091 See \ref QCPLayerable::selectTest for a general explanation of this virtual method.
5092*/
5093double QCPLayoutInset::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
5094{
5095 Q_UNUSED(details)
5096 if (onlySelectable)
5097 return -1;
5098
5099 foreach (QCPLayoutElement *el, mElements)
5100 {
5101 // inset layout shall only return positive selectTest, if actually an inset object is at pos
5102 // else it would block the entire underlying QCPAxisRect with its surface.
5103 if (el->realVisibility() && el->selectTest(pos, onlySelectable) >= 0)
5104 return mParentPlot->selectionTolerance()*0.99;
5105 }
5106 return -1;
5107}
5108
5109/*!
5110 Adds the specified \a element to the layout as an inset aligned at the border (\ref
5111 setInsetAlignment is initialized with \ref ipBorderAligned). The alignment is set to \a
5112 alignment.
5113
5114 \a alignment is an or combination of the following alignment flags: Qt::AlignLeft,
5115 Qt::AlignHCenter, Qt::AlighRight, Qt::AlignTop, Qt::AlignVCenter, Qt::AlignBottom. Any other
5116 alignment flags will be ignored.
5117
5118 \see addElement(QCPLayoutElement *element, const QRectF &rect)
5119*/
5121{
5122 if (element)
5123 {
5124 if (element->layout()) // remove from old layout first
5125 element->layout()->take(element);
5126 mElements.append(element);
5127 mInsetPlacement.append(ipBorderAligned);
5128 mInsetAlignment.append(alignment);
5129 mInsetRect.append(QRectF(0.6, 0.6, 0.4, 0.4));
5130 adoptElement(element);
5131 } else
5132 qDebug() << Q_FUNC_INFO << "Can't add nullptr element";
5133}
5134
5135/*!
5136 Adds the specified \a element to the layout as an inset with free positioning/sizing (\ref
5137 setInsetAlignment is initialized with \ref ipFree). The position and size is set to \a
5138 rect.
5139
5140 \a rect is given in fractions of the whole inset layout rect. So an inset with rect (0, 0, 1, 1)
5141 will span the entire layout. An inset with rect (0.6, 0.1, 0.35, 0.35) will be in the top right
5142 corner of the layout, with 35% width and height of the parent layout.
5143
5144 \see addElement(QCPLayoutElement *element, Qt::Alignment alignment)
5145*/
5147{
5148 if (element)
5149 {
5150 if (element->layout()) // remove from old layout first
5151 element->layout()->take(element);
5152 mElements.append(element);
5153 mInsetPlacement.append(ipFree);
5154 mInsetAlignment.append(Qt::AlignRight|Qt::AlignTop);
5155 mInsetRect.append(rect);
5156 adoptElement(element);
5157 } else
5158 qDebug() << Q_FUNC_INFO << "Can't add nullptr element";
5159}
5160/* end of 'src/layout.cpp' */
5161
5162
5163/* including file 'src/lineending.cpp' */
5164/* modified 2022-11-06T12:45:56, size 11189 */
5165
5166////////////////////////////////////////////////////////////////////////////////////////////////////
5167//////////////////// QCPLineEnding
5168////////////////////////////////////////////////////////////////////////////////////////////////////
5169
5170/*! \class QCPLineEnding
5171 \brief Handles the different ending decorations for line-like items
5172
5173 \image html QCPLineEnding.png "The various ending styles currently supported"
5174
5175 For every ending a line-like item has, an instance of this class exists. For example, QCPItemLine
5176 has two endings which can be set with QCPItemLine::setHead and QCPItemLine::setTail.
5177
5178 The styles themselves are defined via the enum QCPLineEnding::EndingStyle. Most decorations can
5179 be modified regarding width and length, see \ref setWidth and \ref setLength. The direction of
5180 the ending decoration (e.g. direction an arrow is pointing) is controlled by the line-like item.
5181 For example, when both endings of a QCPItemLine are set to be arrows, they will point to opposite
5182 directions, e.g. "outward". This can be changed by \ref setInverted, which would make the
5183 respective arrow point inward.
5184
5185 Note that due to the overloaded QCPLineEnding constructor, you may directly specify a
5186 QCPLineEnding::EndingStyle where actually a QCPLineEnding is expected, e.g.
5187 \snippet documentation/doc-code-snippets/mainwindow.cpp qcplineending-sethead
5188*/
5189
5190/*!
5191 Creates a QCPLineEnding instance with default values (style \ref esNone).
5192*/
5194 mStyle(esNone),
5195 mWidth(8),
5196 mLength(10),
5197 mInverted(false)
5198{
5199}
5200
5201/*!
5202 Creates a QCPLineEnding instance with the specified values.
5203*/
5204QCPLineEnding::QCPLineEnding(QCPLineEnding::EndingStyle style, double width, double length, bool inverted) :
5205 mStyle(style),
5206 mWidth(width),
5207 mLength(length),
5208 mInverted(inverted)
5209{
5210}
5211
5212/*!
5213 Sets the style of the ending decoration.
5214*/
5216{
5217 mStyle = style;
5218}
5219
5220/*!
5221 Sets the width of the ending decoration, if the style supports it. On arrows, for example, the
5222 width defines the size perpendicular to the arrow's pointing direction.
5223
5224 \see setLength
5225*/
5226void QCPLineEnding::setWidth(double width)
5227{
5228 mWidth = width;
5229}
5230
5231/*!
5232 Sets the length of the ending decoration, if the style supports it. On arrows, for example, the
5233 length defines the size in pointing direction.
5234
5235 \see setWidth
5236*/
5237void QCPLineEnding::setLength(double length)
5238{
5239 mLength = length;
5240}
5241
5242/*!
5243 Sets whether the ending decoration shall be inverted. For example, an arrow decoration will point
5244 inward when \a inverted is set to true.
5245
5246 Note that also the \a width direction is inverted. For symmetrical ending styles like arrows or
5247 discs, this doesn't make a difference. However, asymmetric styles like \ref esHalfBar are
5248 affected by it, which can be used to control to which side the half bar points to.
5249*/
5251{
5252 mInverted = inverted;
5253}
5254
5255/*! \internal
5256
5257 Returns the maximum pixel radius the ending decoration might cover, starting from the position
5258 the decoration is drawn at (typically a line ending/\ref QCPItemPosition of an item).
5259
5260 This is relevant for clipping. Only omit painting of the decoration when the position where the
5261 decoration is supposed to be drawn is farther away from the clipping rect than the returned
5262 distance.
5263*/
5265{
5266 switch (mStyle)
5267 {
5268 case esNone:
5269 return 0;
5270
5271 case esFlatArrow:
5272 case esSpikeArrow:
5273 case esLineArrow:
5274 case esSkewedBar:
5275 return qSqrt(mWidth*mWidth+mLength*mLength); // items that have width and length
5276
5277 case esDisc:
5278 case esSquare:
5279 case esDiamond:
5280 case esBar:
5281 case esHalfBar:
5282 return mWidth*1.42; // items that only have a width -> width*sqrt(2)
5283
5284 }
5285 return 0;
5286}
5287
5288/*!
5289 Starting from the origin of this line ending (which is style specific), returns the length
5290 covered by the line ending symbol, in backward direction.
5291
5292 For example, the \ref esSpikeArrow has a shorter real length than a \ref esFlatArrow, even if
5293 both have the same \ref setLength value, because the spike arrow has an inward curved back, which
5294 reduces the length along its center axis (the drawing origin for arrows is at the tip).
5295
5296 This function is used for precise, style specific placement of line endings, for example in
5297 QCPAxes.
5298*/
5300{
5301 switch (mStyle)
5302 {
5303 case esNone:
5304 case esLineArrow:
5305 case esSkewedBar:
5306 case esBar:
5307 case esHalfBar:
5308 return 0;
5309
5310 case esFlatArrow:
5311 return mLength;
5312
5313 case esDisc:
5314 case esSquare:
5315 case esDiamond:
5316 return mWidth*0.5;
5317
5318 case esSpikeArrow:
5319 return mLength*0.8;
5320 }
5321 return 0;
5322}
5323
5324/*! \internal
5325
5326 Draws the line ending with the specified \a painter at the position \a pos. The direction of the
5327 line ending is controlled with \a dir.
5328*/
5329void QCPLineEnding::draw(QCPPainter *painter, const QCPVector2D &pos, const QCPVector2D &dir) const
5330{
5331 if (mStyle == esNone)
5332 return;
5333
5334 QCPVector2D lengthVec = dir.normalized() * mLength*(mInverted ? -1 : 1);
5335 if (lengthVec.isNull())
5336 lengthVec = QCPVector2D(1, 0);
5337 QCPVector2D widthVec = dir.normalized().perpendicular() * mWidth*0.5*(mInverted ? -1 : 1);
5338
5339 QPen penBackup = painter->pen();
5340 QBrush brushBackup = painter->brush();
5341 QPen miterPen = penBackup;
5342 miterPen.setJoinStyle(Qt::MiterJoin); // to make arrow heads spikey
5343 QBrush brush(painter->pen().color(), Qt::SolidPattern);
5344 switch (mStyle)
5345 {
5346 case esNone: break;
5347 case esFlatArrow:
5348 {
5349 QPointF points[3] = {pos.toPointF(),
5350 (pos-lengthVec+widthVec).toPointF(),
5351 (pos-lengthVec-widthVec).toPointF()
5352 };
5353 painter->setPen(miterPen);
5354 painter->setBrush(brush);
5355 painter->drawConvexPolygon(points, 3);
5356 painter->setBrush(brushBackup);
5357 painter->setPen(penBackup);
5358 break;
5359 }
5360 case esSpikeArrow:
5361 {
5362 QPointF points[4] = {pos.toPointF(),
5363 (pos-lengthVec+widthVec).toPointF(),
5364 (pos-lengthVec*0.8).toPointF(),
5365 (pos-lengthVec-widthVec).toPointF()
5366 };
5367 painter->setPen(miterPen);
5368 painter->setBrush(brush);
5369 painter->drawConvexPolygon(points, 4);
5370 painter->setBrush(brushBackup);
5371 painter->setPen(penBackup);
5372 break;
5373 }
5374 case esLineArrow:
5375 {
5376 QPointF points[3] = {(pos-lengthVec+widthVec).toPointF(),
5377 pos.toPointF(),
5378 (pos-lengthVec-widthVec).toPointF()
5379 };
5380 painter->setPen(miterPen);
5381 painter->drawPolyline(points, 3);
5382 painter->setPen(penBackup);
5383 break;
5384 }
5385 case esDisc:
5386 {
5387 painter->setBrush(brush);
5388 painter->drawEllipse(pos.toPointF(), mWidth*0.5, mWidth*0.5);
5389 painter->setBrush(brushBackup);
5390 break;
5391 }
5392 case esSquare:
5393 {
5394 QCPVector2D widthVecPerp = widthVec.perpendicular();
5395 QPointF points[4] = {(pos-widthVecPerp+widthVec).toPointF(),
5396 (pos-widthVecPerp-widthVec).toPointF(),
5397 (pos+widthVecPerp-widthVec).toPointF(),
5398 (pos+widthVecPerp+widthVec).toPointF()
5399 };
5400 painter->setPen(miterPen);
5401 painter->setBrush(brush);
5402 painter->drawConvexPolygon(points, 4);
5403 painter->setBrush(brushBackup);
5404 painter->setPen(penBackup);
5405 break;
5406 }
5407 case esDiamond:
5408 {
5409 QCPVector2D widthVecPerp = widthVec.perpendicular();
5410 QPointF points[4] = {(pos-widthVecPerp).toPointF(),
5411 (pos-widthVec).toPointF(),
5412 (pos+widthVecPerp).toPointF(),
5413 (pos+widthVec).toPointF()
5414 };
5415 painter->setPen(miterPen);
5416 painter->setBrush(brush);
5417 painter->drawConvexPolygon(points, 4);
5418 painter->setBrush(brushBackup);
5419 painter->setPen(penBackup);
5420 break;
5421 }
5422 case esBar:
5423 {
5424 painter->drawLine((pos+widthVec).toPointF(), (pos-widthVec).toPointF());
5425 break;
5426 }
5427 case esHalfBar:
5428 {
5429 painter->drawLine((pos+widthVec).toPointF(), pos.toPointF());
5430 break;
5431 }
5432 case esSkewedBar:
5433 {
5434 QCPVector2D shift;
5435 if (!qFuzzyIsNull(painter->pen().widthF()) || painter->modes().testFlag(QCPPainter::pmNonCosmetic))
5436 shift = dir.normalized()*qMax(qreal(1.0), painter->pen().widthF())*qreal(0.5);
5437 // if drawing with thick (non-cosmetic) pen, shift bar a little in line direction to prevent line from sticking through bar slightly
5438 painter->drawLine((pos+widthVec+lengthVec*0.2*(mInverted?-1:1)+shift).toPointF(),
5439 (pos-widthVec-lengthVec*0.2*(mInverted?-1:1)+shift).toPointF());
5440 break;
5441 }
5442 }
5443}
5444
5445/*! \internal
5446 \overload
5447
5448 Draws the line ending. The direction is controlled with the \a angle parameter in radians.
5449*/
5450void QCPLineEnding::draw(QCPPainter *painter, const QCPVector2D &pos, double angle) const
5451{
5452 draw(painter, pos, QCPVector2D(qCos(angle), qSin(angle)));
5453}
5454/* end of 'src/lineending.cpp' */
5455
5456
5457/* including file 'src/axis/labelpainter.cpp' */
5458/* modified 2022-11-06T12:45:56, size 27519 */
5459
5460
5461////////////////////////////////////////////////////////////////////////////////////////////////////
5462//////////////////// QCPLabelPainterPrivate
5463////////////////////////////////////////////////////////////////////////////////////////////////////
5464
5465/*! \class QCPLabelPainterPrivate
5466
5467 \internal
5468 \brief (Private)
5469
5470 This is a private class and not part of the public QCustomPlot interface.
5471
5472*/
5473
5474const QChar QCPLabelPainterPrivate::SymbolDot(183);
5475const QChar QCPLabelPainterPrivate::SymbolCross(215);
5476
5477/*!
5478 Constructs a QCPLabelPainterPrivate instance. Make sure to not create a new
5479 instance on every redraw, to utilize the caching mechanisms.
5480
5481 the \a parentPlot does not take ownership of the label painter. Make sure
5482 to delete it appropriately.
5483*/
5485 mAnchorMode(amRectangular),
5486 mAnchorSide(asLeft),
5487 mAnchorReferenceType(artNormal),
5488 mColor(Qt::black),
5489 mPadding(0),
5490 mRotation(0),
5491 mSubstituteExponent(true),
5492 mMultiplicationSymbol(QChar(215)),
5493 mAbbreviateDecimalPowers(false),
5494 mParentPlot(parentPlot),
5495 mLabelCache(16)
5496{
5497 analyzeFontMetrics();
5498}
5499
5500QCPLabelPainterPrivate::~QCPLabelPainterPrivate()
5501{
5502}
5503
5504void QCPLabelPainterPrivate::setAnchorSide(AnchorSide side)
5505{
5506 mAnchorSide = side;
5507}
5508
5509void QCPLabelPainterPrivate::setAnchorMode(AnchorMode mode)
5510{
5511 mAnchorMode = mode;
5512}
5513
5514void QCPLabelPainterPrivate::setAnchorReference(const QPointF &pixelPoint)
5515{
5516 mAnchorReference = pixelPoint;
5517}
5518
5519void QCPLabelPainterPrivate::setAnchorReferenceType(AnchorReferenceType type)
5520{
5521 mAnchorReferenceType = type;
5522}
5523
5524void QCPLabelPainterPrivate::setFont(const QFont &font)
5525{
5526 if (mFont != font)
5527 {
5528 mFont = font;
5529 analyzeFontMetrics();
5530 }
5531}
5532
5533void QCPLabelPainterPrivate::setColor(const QColor &color)
5534{
5535 mColor = color;
5536}
5537
5538void QCPLabelPainterPrivate::setPadding(int padding)
5539{
5540 mPadding = padding;
5541}
5542
5543void QCPLabelPainterPrivate::setRotation(double rotation)
5544{
5545 mRotation = qBound(-90.0, rotation, 90.0);
5546}
5547
5548void QCPLabelPainterPrivate::setSubstituteExponent(bool enabled)
5549{
5550 mSubstituteExponent = enabled;
5551}
5552
5553void QCPLabelPainterPrivate::setMultiplicationSymbol(QChar symbol)
5554{
5555 mMultiplicationSymbol = symbol;
5556}
5557
5558void QCPLabelPainterPrivate::setAbbreviateDecimalPowers(bool enabled)
5559{
5560 mAbbreviateDecimalPowers = enabled;
5561}
5562
5563void QCPLabelPainterPrivate::setCacheSize(int labelCount)
5564{
5565 mLabelCache.setMaxCost(labelCount);
5566}
5567
5568int QCPLabelPainterPrivate::cacheSize() const
5569{
5570 return mLabelCache.maxCost();
5571}
5572
5573void QCPLabelPainterPrivate::drawTickLabel(QCPPainter *painter, const QPointF &tickPos, const QString &text)
5574{
5575 double realRotation = mRotation;
5576
5577 AnchorSide realSide = mAnchorSide;
5578 // for circular axes, the anchor side is determined depending on the quadrant of tickPos with respect to mCircularReference
5579 if (mAnchorMode == amSkewedUpright)
5580 {
5581 realSide = skewedAnchorSide(tickPos, 0.2, 0.3);
5582 } else if (mAnchorMode == amSkewedRotated) // in this mode every label is individually rotated to match circle tangent
5583 {
5584 realSide = skewedAnchorSide(tickPos, 0, 0);
5585 realRotation += QCPVector2D(tickPos-mAnchorReference).angle()/M_PI*180.0;
5586 if (realRotation > 90) realRotation -= 180;
5587 else if (realRotation < -90) realRotation += 180;
5588 }
5589
5590 realSide = rotationCorrectedSide(realSide, realRotation); // rotation angles may change the true anchor side of the label
5591 drawLabelMaybeCached(painter, mFont, mColor, getAnchorPos(tickPos), realSide, realRotation, text);
5592}
5593
5594/*! \internal
5595
5596 Returns the size ("margin" in QCPAxisRect context, so measured perpendicular to the axis backbone
5597 direction) needed to fit the axis.
5598*/
5599/* TODO: needed?
5600int QCPLabelPainterPrivate::size() const
5601{
5602 int result = 0;
5603 // get length of tick marks pointing outwards:
5604 if (!tickPositions.isEmpty())
5605 result += qMax(0, qMax(tickLengthOut, subTickLengthOut));
5606
5607 // calculate size of tick labels:
5608 if (tickLabelSide == QCPAxis::lsOutside)
5609 {
5610 QSize tickLabelsSize(0, 0);
5611 if (!tickLabels.isEmpty())
5612 {
5613 for (int i=0; i<tickLabels.size(); ++i)
5614 getMaxTickLabelSize(tickLabelFont, tickLabels.at(i), &tickLabelsSize);
5615 result += QCPAxis::orientation(type) == Qt::Horizontal ? tickLabelsSize.height() : tickLabelsSize.width();
5616 result += tickLabelPadding;
5617 }
5618 }
5619
5620 // calculate size of axis label (only height needed, because left/right labels are rotated by 90 degrees):
5621 if (!label.isEmpty())
5622 {
5623 QFontMetrics fontMetrics(labelFont);
5624 QRect bounds;
5625 bounds = fontMetrics.boundingRect(0, 0, 0, 0, Qt::TextDontClip | Qt::AlignHCenter | Qt::AlignVCenter, label);
5626 result += bounds.height() + labelPadding;
5627 }
5628
5629 return result;
5630}
5631*/
5632
5633/*! \internal
5634
5635 Clears the internal label cache. Upon the next \ref draw, all labels will be created new. This
5636 method is called automatically if any parameters have changed that invalidate the cached labels,
5637 such as font, color, etc. Usually you won't need to call this method manually.
5638*/
5640{
5641 mLabelCache.clear();
5642}
5643
5644/*! \internal
5645
5646 Returns a hash that allows uniquely identifying whether the label parameters have changed such
5647 that the cached labels must be refreshed (\ref clearCache). It is used in \ref draw. If the
5648 return value of this method hasn't changed since the last redraw, the respective label parameters
5649 haven't changed and cached labels may be used.
5650*/
5652{
5653 QByteArray result;
5654 result.append(QByteArray::number(mParentPlot->bufferDevicePixelRatio()));
5655 result.append(QByteArray::number(mRotation));
5656 //result.append(QByteArray::number(int(tickLabelSide))); TODO: check whether this is really a cache-invalidating property
5657 result.append(QByteArray::number(int(mSubstituteExponent)));
5658 result.append(QString(mMultiplicationSymbol).toUtf8());
5659 result.append(mColor.name().toLatin1()+QByteArray::number(mColor.alpha(), 16));
5660 result.append(mFont.toString().toLatin1());
5661 return result;
5662}
5663
5664/*! \internal
5665
5666 Draws a single tick label with the provided \a painter, utilizing the internal label cache to
5667 significantly speed up drawing of labels that were drawn in previous calls. The tick label is
5668 always bound to an axis, the distance to the axis is controllable via \a distanceToAxis in
5669 pixels. The pixel position in the axis direction is passed in the \a position parameter. Hence
5670 for the bottom axis, \a position would indicate the horizontal pixel position (not coordinate),
5671 at which the label should be drawn.
5672
5673 In order to later draw the axis label in a place that doesn't overlap with the tick labels, the
5674 largest tick label size is needed. This is acquired by passing a \a tickLabelsSize to the \ref
5675 drawTickLabel calls during the process of drawing all tick labels of one axis. In every call, \a
5676 tickLabelsSize is expanded, if the drawn label exceeds the value \a tickLabelsSize currently
5677 holds.
5678
5679 The label is drawn with the font and pen that are currently set on the \a painter. To draw
5680 superscripted powers, the font is temporarily made smaller by a fixed factor (see \ref
5681 getTickLabelData).
5682*/
5683void QCPLabelPainterPrivate::drawLabelMaybeCached(QCPPainter *painter, const QFont &font, const QColor &color, const QPointF &pos, AnchorSide side, double rotation, const QString &text)
5684{
5685 // warning: if you change anything here, also adapt getMaxTickLabelSize() accordingly!
5686 if (text.isEmpty()) return;
5687 QSize finalSize;
5688
5689 if (mParentPlot->plottingHints().testFlag(QCP::phCacheLabels) && !painter->modes().testFlag(QCPPainter::pmNoCaching)) // label caching enabled
5690 {
5691 QByteArray key = cacheKey(text, color, rotation, side);
5692 CachedLabel *cachedLabel = mLabelCache.take(QString::fromUtf8(key)); // attempt to take label from cache (don't use object() because we want ownership/prevent deletion during our operations, we re-insert it afterwards)
5693 if (!cachedLabel) // no cached label existed, create it
5694 {
5695 LabelData labelData = getTickLabelData(font, color, rotation, side, text);
5696 cachedLabel = createCachedLabel(labelData);
5697 }
5698 // if label would be partly clipped by widget border on sides, don't draw it (only for outside tick labels):
5699 bool labelClippedByBorder = false;
5700 /*
5701 if (tickLabelSide == QCPAxis::lsOutside)
5702 {
5703 if (QCPAxis::orientation(type) == Qt::Horizontal)
5704 labelClippedByBorder = labelAnchor.x()+cachedLabel->offset.x()+cachedLabel->pixmap.width()/mParentPlot->bufferDevicePixelRatio() > viewportRect.right() || labelAnchor.x()+cachedLabel->offset.x() < viewportRect.left();
5705 else
5706 labelClippedByBorder = labelAnchor.y()+cachedLabel->offset.y()+cachedLabel->pixmap.height()/mParentPlot->bufferDevicePixelRatio() > viewportRect.bottom() || labelAnchor.y()+cachedLabel->offset.y() < viewportRect.top();
5707 }
5708 */
5709 if (!labelClippedByBorder)
5710 {
5711 painter->drawPixmap(pos+cachedLabel->offset, cachedLabel->pixmap);
5712 finalSize = cachedLabel->pixmap.size()/mParentPlot->bufferDevicePixelRatio(); // TODO: collect this in a member rect list?
5713 }
5714 mLabelCache.insert(QString::fromUtf8(key), cachedLabel);
5715 } else // label caching disabled, draw text directly on surface:
5716 {
5717 LabelData labelData = getTickLabelData(font, color, rotation, side, text);
5718 // if label would be partly clipped by widget border on sides, don't draw it (only for outside tick labels):
5719 bool labelClippedByBorder = false;
5720 /*
5721 if (tickLabelSide == QCPAxis::lsOutside)
5722 {
5723 if (QCPAxis::orientation(type) == Qt::Horizontal)
5724 labelClippedByBorder = finalPosition.x()+(labelData.rotatedTotalBounds.width()+labelData.rotatedTotalBounds.left()) > viewportRect.right() || finalPosition.x()+labelData.rotatedTotalBounds.left() < viewportRect.left();
5725 else
5726 labelClippedByBorder = finalPosition.y()+(labelData.rotatedTotalBounds.height()+labelData.rotatedTotalBounds.top()) > viewportRect.bottom() || finalPosition.y()+labelData.rotatedTotalBounds.top() < viewportRect.top();
5727 }
5728 */
5729 if (!labelClippedByBorder)
5730 {
5731 drawText(painter, pos, labelData);
5732 finalSize = labelData.rotatedTotalBounds.size();
5733 }
5734 }
5735 /*
5736 // expand passed tickLabelsSize if current tick label is larger:
5737 if (finalSize.width() > tickLabelsSize->width())
5738 tickLabelsSize->setWidth(finalSize.width());
5739 if (finalSize.height() > tickLabelsSize->height())
5740 tickLabelsSize->setHeight(finalSize.height());
5741 */
5742}
5743
5744QPointF QCPLabelPainterPrivate::getAnchorPos(const QPointF &tickPos)
5745{
5746 switch (mAnchorMode)
5747 {
5748 case amRectangular:
5749 {
5750 switch (mAnchorSide)
5751 {
5752 case asLeft: return tickPos+QPointF(mPadding, 0);
5753 case asRight: return tickPos+QPointF(-mPadding, 0);
5754 case asTop: return tickPos+QPointF(0, mPadding);
5755 case asBottom: return tickPos+QPointF(0, -mPadding);
5756 case asTopLeft: return tickPos+QPointF(mPadding*M_SQRT1_2, mPadding*M_SQRT1_2);
5757 case asTopRight: return tickPos+QPointF(-mPadding*M_SQRT1_2, mPadding*M_SQRT1_2);
5758 case asBottomRight: return tickPos+QPointF(-mPadding*M_SQRT1_2, -mPadding*M_SQRT1_2);
5759 case asBottomLeft: return tickPos+QPointF(mPadding*M_SQRT1_2, -mPadding*M_SQRT1_2);
5760 default: qDebug() << Q_FUNC_INFO << "invalid mode for anchor side: " << mAnchorSide; break;
5761 }
5762 break;
5763 }
5764 case amSkewedUpright:
5765 // fall through
5766 case amSkewedRotated:
5767 {
5768 QCPVector2D anchorNormal(tickPos-mAnchorReference);
5769 if (mAnchorReferenceType == artTangent)
5770 anchorNormal = anchorNormal.perpendicular();
5771 anchorNormal.normalize();
5772 return tickPos+(anchorNormal*mPadding).toPointF();
5773 }
5774 default: qDebug() << Q_FUNC_INFO << "invalid mode for anchor mode: " << mAnchorMode; break;
5775 }
5776 return tickPos;
5777}
5778
5779/*! \internal
5780
5781 This is a \ref placeTickLabel helper function.
5782
5783 Draws the tick label specified in \a labelData with \a painter at the pixel positions \a x and \a
5784 y. This function is used by \ref placeTickLabel to create new tick labels for the cache, or to
5785 directly draw the labels on the QCustomPlot surface when label caching is disabled, i.e. when
5786 QCP::phCacheLabels plotting hint is not set.
5787*/
5788void QCPLabelPainterPrivate::drawText(QCPPainter *painter, const QPointF &pos, const LabelData &labelData) const
5789{
5790 // backup painter settings that we're about to change:
5791 QTransform oldTransform = painter->transform();
5792 QFont oldFont = painter->font();
5793 QPen oldPen = painter->pen();
5794
5795 // transform painter to position/rotation:
5796 painter->translate(pos);
5797 painter->setTransform(labelData.transform, true);
5798
5799 // draw text:
5800 painter->setFont(labelData.baseFont);
5801 painter->setPen(QPen(labelData.color));
5802 if (!labelData.expPart.isEmpty()) // use superscripted exponent typesetting
5803 {
5804 painter->drawText(0, 0, 0, 0, Qt::TextDontClip, labelData.basePart);
5805 if (!labelData.suffixPart.isEmpty())
5806 painter->drawText(labelData.baseBounds.width()+1+labelData.expBounds.width(), 0, 0, 0, Qt::TextDontClip, labelData.suffixPart);
5807 painter->setFont(labelData.expFont);
5808 painter->drawText(labelData.baseBounds.width()+1, 0, labelData.expBounds.width(), labelData.expBounds.height(), Qt::TextDontClip, labelData.expPart);
5809 } else
5810 {
5811 painter->drawText(0, 0, labelData.totalBounds.width(), labelData.totalBounds.height(), Qt::TextDontClip | Qt::AlignHCenter, labelData.basePart);
5812 }
5813
5814 /* Debug code to draw label bounding boxes, baseline, and capheight
5815 painter->save();
5816 painter->setPen(QPen(QColor(0, 0, 0, 150)));
5817 painter->drawRect(labelData.totalBounds);
5818 const int baseline = labelData.totalBounds.height()-mLetterDescent;
5819 painter->setPen(QPen(QColor(255, 0, 0, 150)));
5820 painter->drawLine(QLineF(0, baseline, labelData.totalBounds.width(), baseline));
5821 painter->setPen(QPen(QColor(0, 0, 255, 150)));
5822 painter->drawLine(QLineF(0, baseline-mLetterCapHeight, labelData.totalBounds.width(), baseline-mLetterCapHeight));
5823 painter->restore();
5824 */
5825
5826 // reset painter settings to what it was before:
5827 painter->setTransform(oldTransform);
5828 painter->setFont(oldFont);
5829 painter->setPen(oldPen);
5830}
5831
5832/*! \internal
5833
5834 This is a \ref placeTickLabel helper function.
5835
5836 Transforms the passed \a text and \a font to a tickLabelData structure that can then be further
5837 processed by \ref getTickLabelDrawOffset and \ref drawTickLabel. It splits the text into base and
5838 exponent if necessary (member substituteExponent) and calculates appropriate bounding boxes.
5839*/
5840QCPLabelPainterPrivate::LabelData QCPLabelPainterPrivate::getTickLabelData(const QFont &font, const QColor &color, double rotation, AnchorSide side, const QString &text) const
5841{
5842 LabelData result;
5843 result.rotation = rotation;
5844 result.side = side;
5845 result.color = color;
5846
5847 // determine whether beautiful decimal powers should be used
5848 bool useBeautifulPowers = false;
5849 int ePos = -1; // first index of exponent part, text before that will be basePart, text until eLast will be expPart
5850 int eLast = -1; // last index of exponent part, rest of text after this will be suffixPart
5851 if (mSubstituteExponent)
5852 {
5853 ePos = text.indexOf(QLatin1Char('e'));
5854 if (ePos > 0 && text.at(ePos-1).isDigit())
5855 {
5856 eLast = ePos;
5857 while (eLast+1 < text.size() && (text.at(eLast+1) == QLatin1Char('+') || text.at(eLast+1) == QLatin1Char('-') || text.at(eLast+1).isDigit()))
5858 ++eLast;
5859 if (eLast > ePos) // only if also to right of 'e' is a digit/+/- interpret it as beautifiable power
5860 useBeautifulPowers = true;
5861 }
5862 }
5863
5864 // calculate text bounding rects and do string preparation for beautiful decimal powers:
5865 result.baseFont = font;
5866 if (result.baseFont.pointSizeF() > 0) // might return -1 if specified with setPixelSize, in that case we can't do correction in next line
5867 result.baseFont.setPointSizeF(result.baseFont.pointSizeF()+0.05); // QFontMetrics.boundingRect has a bug for exact point sizes that make the results oscillate due to internal rounding
5868
5869 QFontMetrics baseFontMetrics(result.baseFont);
5870 if (useBeautifulPowers)
5871 {
5872 // split text into parts of number/symbol that will be drawn normally and part that will be drawn as exponent:
5873 result.basePart = text.left(ePos);
5874 result.suffixPart = text.mid(eLast+1); // also drawn normally but after exponent
5875 // in log scaling, we want to turn "1*10^n" into "10^n", else add multiplication sign and decimal base:
5876 if (mAbbreviateDecimalPowers && result.basePart == QLatin1String("1"))
5877 result.basePart = QLatin1String("10");
5878 else
5879 result.basePart += QString(mMultiplicationSymbol) + QLatin1String("10");
5880 result.expPart = text.mid(ePos+1, eLast-ePos);
5881 // clip "+" and leading zeros off expPart:
5882 while (result.expPart.length() > 2 && result.expPart.at(1) == QLatin1Char('0')) // length > 2 so we leave one zero when numberFormatChar is 'e'
5883 result.expPart.remove(1, 1);
5884 if (!result.expPart.isEmpty() && result.expPart.at(0) == QLatin1Char('+'))
5885 result.expPart.remove(0, 1);
5886 // prepare smaller font for exponent:
5887 result.expFont = font;
5888 if (result.expFont.pointSize() > 0)
5889 result.expFont.setPointSize(result.expFont.pointSize()*0.75);
5890 else
5891 result.expFont.setPixelSize(result.expFont.pixelSize()*0.75);
5892 // calculate bounding rects of base part(s), exponent part and total one:
5893 result.baseBounds = baseFontMetrics.boundingRect(0, 0, 0, 0, Qt::TextDontClip, result.basePart);
5894 result.expBounds = QFontMetrics(result.expFont).boundingRect(0, 0, 0, 0, Qt::TextDontClip, result.expPart);
5895 if (!result.suffixPart.isEmpty())
5896 result.suffixBounds = QFontMetrics(result.baseFont).boundingRect(0, 0, 0, 0, Qt::TextDontClip, result.suffixPart);
5897 result.totalBounds = result.baseBounds.adjusted(0, 0, result.expBounds.width()+result.suffixBounds.width()+2, 0); // +2 consists of the 1 pixel spacing between base and exponent (see drawTickLabel) and an extra pixel to include AA
5898 } else // useBeautifulPowers == false
5899 {
5900 result.basePart = text;
5901 result.totalBounds = baseFontMetrics.boundingRect(0, 0, 0, 0, Qt::TextDontClip | Qt::AlignHCenter, result.basePart);
5902 }
5903 result.totalBounds.moveTopLeft(QPoint(0, 0));
5904 applyAnchorTransform(result);
5905 result.rotatedTotalBounds = result.transform.mapRect(result.totalBounds);
5906
5907 return result;
5908}
5909
5910void QCPLabelPainterPrivate::applyAnchorTransform(LabelData &labelData) const
5911{
5912 if (!qFuzzyIsNull(labelData.rotation))
5913 labelData.transform.rotate(labelData.rotation); // rotates effectively clockwise (due to flipped y axis of painter vs widget coordinate system)
5914
5915 // from now on we translate in rotated label-local coordinate system.
5916 // shift origin of coordinate system to appropriate point on label:
5917 labelData.transform.translate(0, -labelData.totalBounds.height()+mLetterDescent+mLetterCapHeight); // shifts origin to true top of capital (or number) characters
5918
5919 if (labelData.side == asLeft || labelData.side == asRight) // anchor is centered vertically
5920 labelData.transform.translate(0, -mLetterCapHeight/2.0);
5921 else if (labelData.side == asTop || labelData.side == asBottom) // anchor is centered horizontally
5922 labelData.transform.translate(-labelData.totalBounds.width()/2.0, 0);
5923
5924 if (labelData.side == asTopRight || labelData.side == asRight || labelData.side == asBottomRight) // anchor is at right
5925 labelData.transform.translate(-labelData.totalBounds.width(), 0);
5926 if (labelData.side == asBottomLeft || labelData.side == asBottom || labelData.side == asBottomRight) // anchor is at bottom (no elseif!)
5927 labelData.transform.translate(0, -mLetterCapHeight);
5928}
5929
5930/*! \internal
5931
5932 Simulates the steps done by \ref placeTickLabel by calculating bounding boxes of the text label
5933 to be drawn, depending on number format etc. Since only the largest tick label is wanted for the
5934 margin calculation, the passed \a tickLabelsSize is only expanded, if it's currently set to a
5935 smaller width/height.
5936*/
5937/*
5938void QCPLabelPainterPrivate::getMaxTickLabelSize(const QFont &font, const QString &text, QSize *tickLabelsSize) const
5939{
5940 // note: this function must return the same tick label sizes as the placeTickLabel function.
5941 QSize finalSize;
5942 if (mParentPlot->plottingHints().testFlag(QCP::phCacheLabels) && mLabelCache.contains(text)) // label caching enabled and have cached label
5943 {
5944 const CachedLabel *cachedLabel = mLabelCache.object(text);
5945 finalSize = cachedLabel->pixmap.size()/mParentPlot->bufferDevicePixelRatio();
5946 } else // label caching disabled or no label with this text cached:
5947 {
5948 // TODO: LabelData labelData = getTickLabelData(font, text);
5949 // TODO: finalSize = labelData.rotatedTotalBounds.size();
5950 }
5951
5952 // expand passed tickLabelsSize if current tick label is larger:
5953 if (finalSize.width() > tickLabelsSize->width())
5954 tickLabelsSize->setWidth(finalSize.width());
5955 if (finalSize.height() > tickLabelsSize->height())
5956 tickLabelsSize->setHeight(finalSize.height());
5957}
5958*/
5959
5960QCPLabelPainterPrivate::CachedLabel *QCPLabelPainterPrivate::createCachedLabel(const LabelData &labelData) const
5961{
5962 CachedLabel *result = new CachedLabel;
5963
5964 // allocate pixmap with the correct size and pixel ratio:
5965 if (!qFuzzyCompare(1.0, mParentPlot->bufferDevicePixelRatio()))
5966 {
5967 result->pixmap = QPixmap(labelData.rotatedTotalBounds.size()*mParentPlot->bufferDevicePixelRatio());
5968#ifdef QCP_DEVICEPIXELRATIO_SUPPORTED
5969# ifdef QCP_DEVICEPIXELRATIO_FLOAT
5970 result->pixmap.setDevicePixelRatio(mParentPlot->devicePixelRatioF());
5971# else
5972 result->pixmap.setDevicePixelRatio(mParentPlot->devicePixelRatio());
5973# endif
5974#endif
5975 } else
5976 result->pixmap = QPixmap(labelData.rotatedTotalBounds.size());
5977 result->pixmap.fill(Qt::transparent);
5978
5979 // draw the label into the pixmap
5980 // offset is between label anchor and topleft of cache pixmap, so pixmap can be drawn at pos+offset to make the label anchor appear at pos.
5981 // We use rotatedTotalBounds.topLeft() because rotatedTotalBounds is in a coordinate system where the label anchor is at (0, 0)
5982 result->offset = labelData.rotatedTotalBounds.topLeft();
5983 QCPPainter cachePainter(&result->pixmap);
5984 drawText(&cachePainter, -result->offset, labelData);
5985 return result;
5986}
5987
5988QByteArray QCPLabelPainterPrivate::cacheKey(const QString &text, const QColor &color, double rotation, AnchorSide side) const
5989{
5990 return text.toUtf8()+
5991 QByteArray::number(color.red()+256*color.green()+65536*color.blue(), 36)+
5992 QByteArray::number(color.alpha()+256*int(side), 36)+
5993 QByteArray::number(int(rotation*100), 36);
5994}
5995
5996QCPLabelPainterPrivate::AnchorSide QCPLabelPainterPrivate::skewedAnchorSide(const QPointF &tickPos, double sideExpandHorz, double sideExpandVert) const
5997{
5998 QCPVector2D anchorNormal = QCPVector2D(tickPos-mAnchorReference);
5999 if (mAnchorReferenceType == artTangent)
6000 anchorNormal = anchorNormal.perpendicular();
6001 const double radius = anchorNormal.length();
6002 const double sideHorz = sideExpandHorz*radius;
6003 const double sideVert = sideExpandVert*radius;
6004 if (anchorNormal.x() > sideHorz)
6005 {
6006 if (anchorNormal.y() > sideVert) return asTopLeft;
6007 else if (anchorNormal.y() < -sideVert) return asBottomLeft;
6008 else return asLeft;
6009 } else if (anchorNormal.x() < -sideHorz)
6010 {
6011 if (anchorNormal.y() > sideVert) return asTopRight;
6012 else if (anchorNormal.y() < -sideVert) return asBottomRight;
6013 else return asRight;
6014 } else
6015 {
6016 if (anchorNormal.y() > 0) return asTop;
6017 else return asBottom;
6018 }
6019 return asBottom; // should never be reached
6020}
6021
6022QCPLabelPainterPrivate::AnchorSide QCPLabelPainterPrivate::rotationCorrectedSide(AnchorSide side, double rotation) const
6023{
6024 AnchorSide result = side;
6025 const bool rotateClockwise = rotation > 0;
6026 if (!qFuzzyIsNull(rotation))
6027 {
6028 if (!qFuzzyCompare(qAbs(rotation), 90)) // avoid graphical collision with anchor tangent (e.g. axis line) when rotating, so change anchor side appropriately:
6029 {
6030 if (side == asTop) result = rotateClockwise ? asLeft : asRight;
6031 else if (side == asBottom) result = rotateClockwise ? asRight : asLeft;
6032 else if (side == asTopLeft) result = rotateClockwise ? asLeft : asTop;
6033 else if (side == asTopRight) result = rotateClockwise ? asTop : asRight;
6034 else if (side == asBottomLeft) result = rotateClockwise ? asBottom : asLeft;
6035 else if (side == asBottomRight) result = rotateClockwise ? asRight : asBottom;
6036 } else // for full rotation by +/-90 degrees, other sides are more appropriate for centering on anchor:
6037 {
6038 if (side == asLeft) result = rotateClockwise ? asBottom : asTop;
6039 else if (side == asRight) result = rotateClockwise ? asTop : asBottom;
6040 else if (side == asTop) result = rotateClockwise ? asLeft : asRight;
6041 else if (side == asBottom) result = rotateClockwise ? asRight : asLeft;
6042 else if (side == asTopLeft) result = rotateClockwise ? asBottomLeft : asTopRight;
6043 else if (side == asTopRight) result = rotateClockwise ? asTopLeft : asBottomRight;
6044 else if (side == asBottomLeft) result = rotateClockwise ? asBottomRight : asTopLeft;
6045 else if (side == asBottomRight) result = rotateClockwise ? asTopRight : asBottomLeft;
6046 }
6047 }
6048 return result;
6049}
6050
6051void QCPLabelPainterPrivate::analyzeFontMetrics()
6052{
6053 const QFontMetrics fm(mFont);
6054 mLetterCapHeight = fm.tightBoundingRect(QLatin1String("8")).height(); // this method is slow, that's why we query it only upon font change
6055 mLetterDescent = fm.descent();
6056}
6057/* end of 'src/axis/labelpainter.cpp' */
6058
6059
6060/* including file 'src/axis/axisticker.cpp' */
6061/* modified 2022-11-06T12:45:56, size 18693 */
6062
6063////////////////////////////////////////////////////////////////////////////////////////////////////
6064//////////////////// QCPAxisTicker
6065////////////////////////////////////////////////////////////////////////////////////////////////////
6066/*! \class QCPAxisTicker
6067 \brief The base class tick generator used by QCPAxis to create tick positions and tick labels
6068
6069 Each QCPAxis has an internal QCPAxisTicker (or a subclass) in order to generate tick positions
6070 and tick labels for the current axis range. The ticker of an axis can be set via \ref
6071 QCPAxis::setTicker. Since that method takes a <tt>QSharedPointer<QCPAxisTicker></tt>, multiple
6072 axes can share the same ticker instance.
6073
6074 This base class generates normal tick coordinates and numeric labels for linear axes. It picks a
6075 reasonable tick step (the separation between ticks) which results in readable tick labels. The
6076 number of ticks that should be approximately generated can be set via \ref setTickCount.
6077 Depending on the current tick step strategy (\ref setTickStepStrategy), the algorithm either
6078 sacrifices readability to better match the specified tick count (\ref
6079 QCPAxisTicker::tssMeetTickCount) or relaxes the tick count in favor of better tick steps (\ref
6080 QCPAxisTicker::tssReadability), which is the default.
6081
6082 The following more specialized axis ticker subclasses are available, see details in the
6083 respective class documentation:
6084
6085 <center>
6086 <table>
6087 <tr><td style="text-align:right; padding: 0 1em">QCPAxisTickerFixed</td><td>\image html axisticker-fixed.png</td></tr>
6088 <tr><td style="text-align:right; padding: 0 1em">QCPAxisTickerLog</td><td>\image html axisticker-log.png</td></tr>
6089 <tr><td style="text-align:right; padding: 0 1em">QCPAxisTickerPi</td><td>\image html axisticker-pi.png</td></tr>
6090 <tr><td style="text-align:right; padding: 0 1em">QCPAxisTickerText</td><td>\image html axisticker-text.png</td></tr>
6091 <tr><td style="text-align:right; padding: 0 1em">QCPAxisTickerDateTime</td><td>\image html axisticker-datetime.png</td></tr>
6092 <tr><td style="text-align:right; padding: 0 1em">QCPAxisTickerTime</td><td>\image html axisticker-time.png
6093 \image html axisticker-time2.png</td></tr>
6094 </table>
6095 </center>
6096
6097 \section axisticker-subclassing Creating own axis tickers
6098
6099 Creating own axis tickers can be achieved very easily by sublassing QCPAxisTicker and
6100 reimplementing some or all of the available virtual methods.
6101
6102 In the simplest case you might wish to just generate different tick steps than the other tickers,
6103 so you only reimplement the method \ref getTickStep. If you additionally want control over the
6104 string that will be shown as tick label, reimplement \ref getTickLabel.
6105
6106 If you wish to have complete control, you can generate the tick vectors and tick label vectors
6107 yourself by reimplementing \ref createTickVector and \ref createLabelVector. The default
6108 implementations use the previously mentioned virtual methods \ref getTickStep and \ref
6109 getTickLabel, but your reimplementations don't necessarily need to do so. For example in the case
6110 of unequal tick steps, the method \ref getTickStep loses its usefulness and can be ignored.
6111
6112 The sub tick count between major ticks can be controlled with \ref getSubTickCount. Full sub tick
6113 placement control is obtained by reimplementing \ref createSubTickVector.
6114
6115 See the documentation of all these virtual methods in QCPAxisTicker for detailed information
6116 about the parameters and expected return values.
6117*/
6118
6119/*!
6120 Constructs the ticker and sets reasonable default values. Axis tickers are commonly created
6121 managed by a QSharedPointer, which then can be passed to QCPAxis::setTicker.
6122*/
6124 mTickStepStrategy(tssReadability),
6125 mTickCount(5),
6126 mTickOrigin(0)
6127{
6128}
6129
6130QCPAxisTicker::~QCPAxisTicker()
6131{
6132
6133}
6134
6135/*!
6136 Sets which strategy the axis ticker follows when choosing the size of the tick step. For the
6137 available strategies, see \ref TickStepStrategy.
6138*/
6140{
6141 mTickStepStrategy = strategy;
6142}
6143
6144/*!
6145 Sets how many ticks this ticker shall aim to generate across the axis range. Note that \a count
6146 is not guaranteed to be matched exactly, as generating readable tick intervals may conflict with
6147 the requested number of ticks.
6148
6149 Whether the readability has priority over meeting the requested \a count can be specified with
6150 \ref setTickStepStrategy.
6151*/
6153{
6154 if (count > 0)
6155 mTickCount = count;
6156 else
6157 qDebug() << Q_FUNC_INFO << "tick count must be greater than zero:" << count;
6158}
6159
6160/*!
6161 Sets the mathematical coordinate (or "offset") of the zeroth tick. This tick coordinate is just a
6162 concept and doesn't need to be inside the currently visible axis range.
6163
6164 By default \a origin is zero, which for example yields ticks {-5, 0, 5, 10, 15,...} when the tick
6165 step is five. If \a origin is now set to 1 instead, the correspondingly generated ticks would be
6166 {-4, 1, 6, 11, 16,...}.
6167*/
6169{
6170 mTickOrigin = origin;
6171}
6172
6173/*!
6174 This is the method called by QCPAxis in order to actually generate tick coordinates (\a ticks),
6175 tick label strings (\a tickLabels) and sub tick coordinates (\a subTicks).
6176
6177 The ticks are generated for the specified \a range. The generated labels typically follow the
6178 specified \a locale, \a formatChar and number \a precision, however this might be different (or
6179 even irrelevant) for certain QCPAxisTicker subclasses.
6180
6181 The output parameter \a ticks is filled with the generated tick positions in axis coordinates.
6182 The output parameters \a subTicks and \a tickLabels are optional (set them to \c nullptr if not
6183 needed) and are respectively filled with sub tick coordinates, and tick label strings belonging
6184 to \a ticks by index.
6185*/
6186void QCPAxisTicker::generate(const QCPRange &range, const QLocale &locale, QChar formatChar, int precision, QVector<double> &ticks, QVector<double> *subTicks, QVector<QString> *tickLabels)
6187{
6188 // generate (major) ticks:
6189 double tickStep = getTickStep(range);
6190 ticks = createTickVector(tickStep, range);
6191 trimTicks(range, ticks, true); // trim ticks to visible range plus one outer tick on each side (incase a subclass createTickVector creates more)
6192
6193 // generate sub ticks between major ticks:
6194 if (subTicks)
6195 {
6196 if (!ticks.isEmpty())
6197 {
6198 *subTicks = createSubTickVector(getSubTickCount(tickStep), ticks);
6199 trimTicks(range, *subTicks, false);
6200 } else
6201 *subTicks = QVector<double>();
6202 }
6203
6204 // finally trim also outliers (no further clipping happens in axis drawing):
6205 trimTicks(range, ticks, false);
6206 // generate labels for visible ticks if requested:
6207 if (tickLabels)
6208 *tickLabels = createLabelVector(ticks, locale, formatChar, precision);
6209}
6210
6211/*! \internal
6212
6213 Takes the entire currently visible axis range and returns a sensible tick step in
6214 order to provide readable tick labels as well as a reasonable number of tick counts (see \ref
6215 setTickCount, \ref setTickStepStrategy).
6216
6217 If a QCPAxisTicker subclass only wants a different tick step behaviour than the default
6218 implementation, it should reimplement this method. See \ref cleanMantissa for a possible helper
6219 function.
6220*/
6222{
6223 double exactStep = range.size()/double(mTickCount+1e-10); // mTickCount ticks on average, the small addition is to prevent jitter on exact integers
6224 return cleanMantissa(exactStep);
6225}
6226
6227/*! \internal
6228
6229 Takes the \a tickStep, i.e. the distance between two consecutive ticks, and returns
6230 an appropriate number of sub ticks for that specific tick step.
6231
6232 Note that a returned sub tick count of e.g. 4 will split each tick interval into 5 sections.
6233*/
6235{
6236 int result = 1; // default to 1, if no proper value can be found
6237
6238 // separate integer and fractional part of mantissa:
6239 double epsilon = 0.01;
6240 double intPartf;
6241 int intPart;
6242 double fracPart = modf(getMantissa(tickStep), &intPartf);
6243 intPart = int(intPartf);
6244
6245 // handle cases with (almost) integer mantissa:
6246 if (fracPart < epsilon || 1.0-fracPart < epsilon)
6247 {
6248 if (1.0-fracPart < epsilon)
6249 ++intPart;
6250 switch (intPart)
6251 {
6252 case 1: result = 4; break; // 1.0 -> 0.2 substep
6253 case 2: result = 3; break; // 2.0 -> 0.5 substep
6254 case 3: result = 2; break; // 3.0 -> 1.0 substep
6255 case 4: result = 3; break; // 4.0 -> 1.0 substep
6256 case 5: result = 4; break; // 5.0 -> 1.0 substep
6257 case 6: result = 2; break; // 6.0 -> 2.0 substep
6258 case 7: result = 6; break; // 7.0 -> 1.0 substep
6259 case 8: result = 3; break; // 8.0 -> 2.0 substep
6260 case 9: result = 2; break; // 9.0 -> 3.0 substep
6261 }
6262 } else
6263 {
6264 // handle cases with significantly fractional mantissa:
6265 if (qAbs(fracPart-0.5) < epsilon) // *.5 mantissa
6266 {
6267 switch (intPart)
6268 {
6269 case 1: result = 2; break; // 1.5 -> 0.5 substep
6270 case 2: result = 4; break; // 2.5 -> 0.5 substep
6271 case 3: result = 4; break; // 3.5 -> 0.7 substep
6272 case 4: result = 2; break; // 4.5 -> 1.5 substep
6273 case 5: result = 4; break; // 5.5 -> 1.1 substep (won't occur with default getTickStep from here on)
6274 case 6: result = 4; break; // 6.5 -> 1.3 substep
6275 case 7: result = 2; break; // 7.5 -> 2.5 substep
6276 case 8: result = 4; break; // 8.5 -> 1.7 substep
6277 case 9: result = 4; break; // 9.5 -> 1.9 substep
6278 }
6279 }
6280 // if mantissa fraction isn't 0.0 or 0.5, don't bother finding good sub tick marks, leave default
6281 }
6282
6283 return result;
6284}
6285
6286/*! \internal
6287
6288 This method returns the tick label string as it should be printed under the \a tick coordinate.
6289 If a textual number is returned, it should respect the provided \a locale, \a formatChar and \a
6290 precision.
6291
6292 If the returned value contains exponentials of the form "2e5" and beautifully typeset powers is
6293 enabled in the QCPAxis number format (\ref QCPAxis::setNumberFormat), the exponential part will
6294 be formatted accordingly using multiplication symbol and superscript during rendering of the
6295 label automatically.
6296*/
6297QString QCPAxisTicker::getTickLabel(double tick, const QLocale &locale, QChar formatChar, int precision)
6298{
6299 return locale.toString(tick, formatChar.toLatin1(), precision);
6300}
6301
6302/*! \internal
6303
6304 Returns a vector containing all coordinates of sub ticks that should be drawn. It generates \a
6305 subTickCount sub ticks between each tick pair given in \a ticks.
6306
6307 If a QCPAxisTicker subclass needs maximal control over the generated sub ticks, it should
6308 reimplement this method. Depending on the purpose of the subclass it doesn't necessarily need to
6309 base its result on \a subTickCount or \a ticks.
6310*/
6312{
6313 QVector<double> result;
6314 if (subTickCount <= 0 || ticks.size() < 2)
6315 return result;
6316
6317 result.reserve((ticks.size()-1)*subTickCount);
6318 for (int i=1; i<ticks.size(); ++i)
6319 {
6320 double subTickStep = (ticks.at(i)-ticks.at(i-1))/double(subTickCount+1);
6321 for (int k=1; k<=subTickCount; ++k)
6322 result.append(ticks.at(i-1) + k*subTickStep);
6323 }
6324 return result;
6325}
6326
6327/*! \internal
6328
6329 Returns a vector containing all coordinates of ticks that should be drawn. The default
6330 implementation generates ticks with a spacing of \a tickStep (mathematically starting at the tick
6331 step origin, see \ref setTickOrigin) distributed over the passed \a range.
6332
6333 In order for the axis ticker to generate proper sub ticks, it is necessary that the first and
6334 last tick coordinates returned by this method are just below/above the provided \a range.
6335 Otherwise the outer intervals won't contain any sub ticks.
6336
6337 If a QCPAxisTicker subclass needs maximal control over the generated ticks, it should reimplement
6338 this method. Depending on the purpose of the subclass it doesn't necessarily need to base its
6339 result on \a tickStep, e.g. when the ticks are spaced unequally like in the case of
6340 QCPAxisTickerLog.
6341*/
6343{
6344 QVector<double> result;
6345 // Generate tick positions according to tickStep:
6346 qint64 firstStep = qint64(floor((range.lower-mTickOrigin)/tickStep)); // do not use qFloor here, or we'll lose 64 bit precision
6347 qint64 lastStep = qint64(ceil((range.upper-mTickOrigin)/tickStep)); // do not use qCeil here, or we'll lose 64 bit precision
6348 int tickcount = int(lastStep-firstStep+1);
6349 if (tickcount < 0) tickcount = 0;
6350 result.resize(tickcount);
6351 for (int i=0; i<tickcount; ++i)
6352 result[i] = mTickOrigin + (firstStep+i)*tickStep;
6353 return result;
6354}
6355
6356/*! \internal
6357
6358 Returns a vector containing all tick label strings corresponding to the tick coordinates provided
6359 in \a ticks. The default implementation calls \ref getTickLabel to generate the respective
6360 strings.
6361
6362 It is possible but uncommon for QCPAxisTicker subclasses to reimplement this method, as
6363 reimplementing \ref getTickLabel often achieves the intended result easier.
6364*/
6365QVector<QString> QCPAxisTicker::createLabelVector(const QVector<double> &ticks, const QLocale &locale, QChar formatChar, int precision)
6366{
6367 QVector<QString> result;
6368 result.reserve(ticks.size());
6369 foreach (double tickCoord, ticks)
6370 result.append(getTickLabel(tickCoord, locale, formatChar, precision));
6371 return result;
6372}
6373
6374/*! \internal
6375
6376 Removes tick coordinates from \a ticks which lie outside the specified \a range. If \a
6377 keepOneOutlier is true, it preserves one tick just outside the range on both sides, if present.
6378
6379 The passed \a ticks must be sorted in ascending order.
6380*/
6381void QCPAxisTicker::trimTicks(const QCPRange &range, QVector<double> &ticks, bool keepOneOutlier) const
6382{
6383 bool lowFound = false;
6384 bool highFound = false;
6385 int lowIndex = 0;
6386 int highIndex = -1;
6387
6388 for (int i=0; i < ticks.size(); ++i)
6389 {
6390 if (ticks.at(i) >= range.lower)
6391 {
6392 lowFound = true;
6393 lowIndex = i;
6394 break;
6395 }
6396 }
6397 for (int i=ticks.size()-1; i >= 0; --i)
6398 {
6399 if (ticks.at(i) <= range.upper)
6400 {
6401 highFound = true;
6402 highIndex = i;
6403 break;
6404 }
6405 }
6406
6407 if (highFound && lowFound)
6408 {
6409 int trimFront = qMax(0, lowIndex-(keepOneOutlier ? 1 : 0));
6410 int trimBack = qMax(0, ticks.size()-(keepOneOutlier ? 2 : 1)-highIndex);
6411 if (trimFront > 0 || trimBack > 0)
6412 ticks = ticks.mid(trimFront, ticks.size()-trimFront-trimBack);
6413 } else // all ticks are either all below or all above the range
6414 ticks.clear();
6415}
6416
6417/*! \internal
6418
6419 Returns the coordinate contained in \a candidates which is closest to the provided \a target.
6420
6421 This method assumes \a candidates is not empty and sorted in ascending order.
6422*/
6423double QCPAxisTicker::pickClosest(double target, const QVector<double> &candidates) const
6424{
6425 if (candidates.size() == 1)
6426 return candidates.first();
6427 QVector<double>::const_iterator it = std::lower_bound(candidates.constBegin(), candidates.constEnd(), target);
6428 if (it == candidates.constEnd())
6429 return *(it-1);
6430 else if (it == candidates.constBegin())
6431 return *it;
6432 else
6433 return target-*(it-1) < *it-target ? *(it-1) : *it;
6434}
6435
6436/*! \internal
6437
6438 Returns the decimal mantissa of \a input. Optionally, if \a magnitude is not set to zero, it also
6439 returns the magnitude of \a input as a power of 10.
6440
6441 For example, an input of 142.6 will return a mantissa of 1.426 and a magnitude of 100.
6442*/
6443double QCPAxisTicker::getMantissa(double input, double *magnitude) const
6444{
6445 const double mag = std::pow(10.0, std::floor(std::log10(input)));
6446 if (magnitude) *magnitude = mag;
6447 return input/mag;
6448}
6449
6450/*! \internal
6451
6452 Returns a number that is close to \a input but has a clean, easier human readable mantissa. How
6453 strongly the mantissa is altered, and thus how strong the result deviates from the original \a
6454 input, depends on the current tick step strategy (see \ref setTickStepStrategy).
6455*/
6456double QCPAxisTicker::cleanMantissa(double input) const
6457{
6458 double magnitude;
6459 const double mantissa = getMantissa(input, &magnitude);
6460 switch (mTickStepStrategy)
6461 {
6462 case tssReadability:
6463 {
6464 return pickClosest(mantissa, QVector<double>() << 1.0 << 2.0 << 2.5 << 5.0 << 10.0)*magnitude;
6465 }
6466 case tssMeetTickCount:
6467 {
6468 // this gives effectively a mantissa of 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0, 6.0, 8.0, 10.0
6469 if (mantissa <= 5.0)
6470 return int(mantissa*2)/2.0*magnitude; // round digit after decimal point to 0.5
6471 else
6472 return int(mantissa/2.0)*2.0*magnitude; // round to first digit in multiples of 2
6473 }
6474 }
6475 return input;
6476}
6477/* end of 'src/axis/axisticker.cpp' */
6478
6479
6480/* including file 'src/axis/axistickerdatetime.cpp' */
6481/* modified 2022-11-06T12:45:56, size 18829 */
6482
6483////////////////////////////////////////////////////////////////////////////////////////////////////
6484//////////////////// QCPAxisTickerDateTime
6485////////////////////////////////////////////////////////////////////////////////////////////////////
6486/*! \class QCPAxisTickerDateTime
6487 \brief Specialized axis ticker for calendar dates and times as axis ticks
6488
6489 \image html axisticker-datetime.png
6490
6491 This QCPAxisTicker subclass generates ticks that correspond to real calendar dates and times. The
6492 plot axis coordinate is interpreted as Unix Time, so seconds since Epoch (January 1, 1970, 00:00
6493 UTC). This is also used for example by QDateTime in the <tt>toTime_t()/setTime_t()</tt> methods
6494 with a precision of one second. Since Qt 4.7, millisecond accuracy can be obtained from QDateTime
6495 by using <tt>QDateTime::fromMSecsSinceEpoch()/1000.0</tt>. The static methods \ref dateTimeToKey
6496 and \ref keyToDateTime conveniently perform this conversion achieving a precision of one
6497 millisecond on all Qt versions.
6498
6499 The format of the date/time display in the tick labels is controlled with \ref setDateTimeFormat.
6500 If a different time spec or time zone shall be used for the tick label appearance, see \ref
6501 setDateTimeSpec or \ref setTimeZone, respectively.
6502
6503 This ticker produces unequal tick spacing in order to provide intuitive date and time-of-day
6504 ticks. For example, if the axis range spans a few years such that there is one tick per year,
6505 ticks will be positioned on 1. January of every year. This is intuitive but, due to leap years,
6506 will result in slightly unequal tick intervals (visually unnoticeable). The same can be seen in
6507 the image above: even though the number of days varies month by month, this ticker generates
6508 ticks on the same day of each month.
6509
6510 If you would like to change the date/time that is used as a (mathematical) starting date for the
6511 ticks, use the \ref setTickOrigin(const QDateTime &origin) method overload, which takes a
6512 QDateTime. If you pass 15. July, 9:45 to this method, the yearly ticks will end up on 15. July at
6513 9:45 of every year.
6514
6515 The ticker can be created and assigned to an axis like this:
6516 \snippet documentation/doc-image-generator/mainwindow.cpp axistickerdatetime-creation
6517
6518 \note If you rather wish to display relative times in terms of days, hours, minutes, seconds and
6519 milliseconds, and are not interested in the intricacies of real calendar dates with months and
6520 (leap) years, have a look at QCPAxisTickerTime instead.
6521*/
6522
6523/*!
6524 Constructs the ticker and sets reasonable default values. Axis tickers are commonly created
6525 managed by a QSharedPointer, which then can be passed to QCPAxis::setTicker.
6526*/
6528 mDateTimeFormat(QLatin1String("hh:mm:ss\ndd.MM.yy")),
6529 mDateTimeSpec(Qt::LocalTime),
6530 mDateStrategy(dsNone)
6531{
6532 setTickCount(4);
6533}
6534
6535/*!
6536 Sets the format in which dates and times are displayed as tick labels. For details about the \a
6537 format string, see the documentation of QDateTime::toString().
6538
6539 Typical expressions are
6540 <table>
6541 <tr><td>\c d</td><td>The day as a number without a leading zero (1 to 31)</td></tr>
6542 <tr><td>\c dd</td><td>The day as a number with a leading zero (01 to 31)</td></tr>
6543 <tr><td>\c ddd</td><td>The abbreviated localized day name (e.g. 'Mon' to 'Sun'). Uses the system locale to localize the name, i.e. QLocale::system().</td></tr>
6544 <tr><td>\c dddd</td><td>The long localized day name (e.g. 'Monday' to 'Sunday'). Uses the system locale to localize the name, i.e. QLocale::system().</td></tr>
6545 <tr><td>\c M</td><td>The month as a number without a leading zero (1 to 12)</td></tr>
6546 <tr><td>\c MM</td><td>The month as a number with a leading zero (01 to 12)</td></tr>
6547 <tr><td>\c MMM</td><td>The abbreviated localized month name (e.g. 'Jan' to 'Dec'). Uses the system locale to localize the name, i.e. QLocale::system().</td></tr>
6548 <tr><td>\c MMMM</td><td>The long localized month name (e.g. 'January' to 'December'). Uses the system locale to localize the name, i.e. QLocale::system().</td></tr>
6549 <tr><td>\c yy</td><td>The year as a two digit number (00 to 99)</td></tr>
6550 <tr><td>\c yyyy</td><td>The year as a four digit number. If the year is negative, a minus sign is prepended, making five characters.</td></tr>
6551 <tr><td>\c h</td><td>The hour without a leading zero (0 to 23 or 1 to 12 if AM/PM display)</td></tr>
6552 <tr><td>\c hh</td><td>The hour with a leading zero (00 to 23 or 01 to 12 if AM/PM display)</td></tr>
6553 <tr><td>\c H</td><td>The hour without a leading zero (0 to 23, even with AM/PM display)</td></tr>
6554 <tr><td>\c HH</td><td>The hour with a leading zero (00 to 23, even with AM/PM display)</td></tr>
6555 <tr><td>\c m</td><td>The minute without a leading zero (0 to 59)</td></tr>
6556 <tr><td>\c mm</td><td>The minute with a leading zero (00 to 59)</td></tr>
6557 <tr><td>\c s</td><td>The whole second, without any leading zero (0 to 59)</td></tr>
6558 <tr><td>\c ss</td><td>The whole second, with a leading zero where applicable (00 to 59)</td></tr>
6559 <tr><td>\c z</td><td>The fractional part of the second, to go after a decimal point, without trailing zeroes (0 to 999). Thus "s.z" reports the seconds to full available (millisecond) precision without trailing zeroes.</td></tr>
6560 <tr><td>\c zzz</td><td>The fractional part of the second, to millisecond precision, including trailing zeroes where applicable (000 to 999).</td></tr>
6561 <tr><td>\c AP or \c A</td><td>Use AM/PM display. A/AP will be replaced by an upper-case version of either QLocale::amText() or QLocale::pmText().</td></tr>
6562 <tr><td>\c ap or \c a</td><td>Use am/pm display. a/ap will be replaced by a lower-case version of either QLocale::amText() or QLocale::pmText().</td></tr>
6563 <tr><td>\c t</td><td>The timezone (for example "CEST")</td></tr>
6564 </table>
6565
6566 Newlines can be inserted with \c "\n", literal strings (even when containing above expressions)
6567 by encapsulating them using single-quotes. A literal single quote can be generated by using two
6568 consecutive single quotes in the format.
6569
6570 \see setDateTimeSpec, setTimeZone
6571*/
6573{
6574 mDateTimeFormat = format;
6575}
6576
6577/*!
6578 Sets the time spec that is used for creating the tick labels from corresponding dates/times.
6579
6580 The default value of QDateTime objects (and also QCPAxisTickerDateTime) is
6581 <tt>Qt::LocalTime</tt>. However, if the displayed tick labels shall be given in UTC, set \a spec
6582 to <tt>Qt::UTC</tt>.
6583
6584 Tick labels corresponding to other time zones can be achieved with \ref setTimeZone (which sets
6585 \a spec to \c Qt::TimeZone internally). Note that if \a spec is afterwards set to not be \c
6586 Qt::TimeZone again, the \ref setTimeZone setting will be ignored accordingly.
6587
6588 \see setDateTimeFormat, setTimeZone
6589*/
6591{
6592 mDateTimeSpec = spec;
6593}
6594
6595# if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)
6596/*!
6597 Sets the time zone that is used for creating the tick labels from corresponding dates/times. The
6598 time spec (\ref setDateTimeSpec) is set to \c Qt::TimeZone.
6599
6600 \see setDateTimeFormat, setTimeZone
6601*/
6603{
6604 mTimeZone = zone;
6605 mDateTimeSpec = Qt::TimeZone;
6606}
6607#endif
6608
6609/*!
6610 Sets the tick origin (see \ref QCPAxisTicker::setTickOrigin) in seconds since Epoch (1. Jan 1970,
6611 00:00 UTC). For the date time ticker it might be more intuitive to use the overload which
6612 directly takes a QDateTime, see \ref setTickOrigin(const QDateTime &origin).
6613
6614 This is useful to define the month/day/time recurring at greater tick interval steps. For
6615 example, If you pass 15. July, 9:45 to this method and the tick interval happens to be one tick
6616 per year, the ticks will end up on 15. July at 9:45 of every year.
6617*/
6619{
6621}
6622
6623/*!
6624 Sets the tick origin (see \ref QCPAxisTicker::setTickOrigin) as a QDateTime \a origin.
6625
6626 This is useful to define the month/day/time recurring at greater tick interval steps. For
6627 example, If you pass 15. July, 9:45 to this method and the tick interval happens to be one tick
6628 per year, the ticks will end up on 15. July at 9:45 of every year.
6629*/
6631{
6633}
6634
6635/*! \internal
6636
6637 Returns a sensible tick step with intervals appropriate for a date-time-display, such as weekly,
6638 monthly, bi-monthly, etc.
6639
6640 Note that this tick step isn't used exactly when generating the tick vector in \ref
6641 createTickVector, but only as a guiding value requiring some correction for each individual tick
6642 interval. Otherwise this would lead to unintuitive date displays, e.g. jumping between first day
6643 in the month to the last day in the previous month from tick to tick, due to the non-uniform
6644 length of months. The same problem arises with leap years.
6645
6646 \seebaseclassmethod
6647*/
6649{
6650 double result = range.size()/double(mTickCount+1e-10); // mTickCount ticks on average, the small addition is to prevent jitter on exact integers
6651
6652 mDateStrategy = dsNone; // leaving it at dsNone means tick coordinates will not be tuned in any special way in createTickVector
6653 if (result < 1) // ideal tick step is below 1 second -> use normal clean mantissa algorithm in units of seconds
6654 {
6655 result = cleanMantissa(result);
6656 } else if (result < 86400*30.4375*12) // below a year
6657 {
6658 result = pickClosest(result, QVector<double>()
6659 << 1 << 2.5 << 5 << 10 << 15 << 30 << 60 << 2.5*60 << 5*60 << 10*60 << 15*60 << 30*60 << 60*60 // second, minute, hour range
6660 << 3600*2 << 3600*3 << 3600*6 << 3600*12 << 3600*24 // hour to day range
6661 << 86400*2 << 86400*5 << 86400*7 << 86400*14 << 86400*30.4375 << 86400*30.4375*2 << 86400*30.4375*3 << 86400*30.4375*6 << 86400*30.4375*12); // day, week, month range (avg. days per month includes leap years)
6662 if (result > 86400*30.4375-1) // month tick intervals or larger
6663 mDateStrategy = dsUniformDayInMonth;
6664 else if (result > 3600*24-1) // day tick intervals or larger
6665 mDateStrategy = dsUniformTimeInDay;
6666 } else // more than a year, go back to normal clean mantissa algorithm but in units of years
6667 {
6668 const double secondsPerYear = 86400*30.4375*12; // average including leap years
6669 result = cleanMantissa(result/secondsPerYear)*secondsPerYear;
6670 mDateStrategy = dsUniformDayInMonth;
6671 }
6672 return result;
6673}
6674
6675/*! \internal
6676
6677 Returns a sensible sub tick count with intervals appropriate for a date-time-display, such as weekly,
6678 monthly, bi-monthly, etc.
6679
6680 \seebaseclassmethod
6681*/
6683{
6684 int result = QCPAxisTicker::getSubTickCount(tickStep);
6685 switch (qRound(tickStep)) // hand chosen subticks for specific minute/hour/day/week/month range (as specified in getTickStep)
6686 {
6687 case 5*60: result = 4; break;
6688 case 10*60: result = 1; break;
6689 case 15*60: result = 2; break;
6690 case 30*60: result = 1; break;
6691 case 60*60: result = 3; break;
6692 case 3600*2: result = 3; break;
6693 case 3600*3: result = 2; break;
6694 case 3600*6: result = 1; break;
6695 case 3600*12: result = 3; break;
6696 case 3600*24: result = 3; break;
6697 case 86400*2: result = 1; break;
6698 case 86400*5: result = 4; break;
6699 case 86400*7: result = 6; break;
6700 case 86400*14: result = 1; break;
6701 case int(86400*30.4375+0.5): result = 3; break;
6702 case int(86400*30.4375*2+0.5): result = 1; break;
6703 case int(86400*30.4375*3+0.5): result = 2; break;
6704 case int(86400*30.4375*6+0.5): result = 5; break;
6705 case int(86400*30.4375*12+0.5): result = 3; break;
6706 }
6707 return result;
6708}
6709
6710/*! \internal
6711
6712 Generates a date/time tick label for tick coordinate \a tick, based on the currently set format
6713 (\ref setDateTimeFormat), time spec (\ref setDateTimeSpec), and possibly time zone (\ref
6714 setTimeZone).
6715
6716 \seebaseclassmethod
6717*/
6718QString QCPAxisTickerDateTime::getTickLabel(double tick, const QLocale &locale, QChar formatChar, int precision)
6719{
6720 Q_UNUSED(precision)
6721 Q_UNUSED(formatChar)
6722# if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)
6723 if (mDateTimeSpec == Qt::TimeZone)
6724 return locale.toString(keyToDateTime(tick).toTimeZone(mTimeZone), mDateTimeFormat);
6725 else
6726 return locale.toString(keyToDateTime(tick).toTimeSpec(mDateTimeSpec), mDateTimeFormat);
6727# else
6728 return locale.toString(keyToDateTime(tick).toTimeSpec(mDateTimeSpec), mDateTimeFormat);
6729# endif
6730}
6731
6732/*! \internal
6733
6734 Uses the passed \a tickStep as a guiding value and applies corrections in order to obtain
6735 non-uniform tick intervals but intuitive tick labels, e.g. falling on the same day of each month.
6736
6737 \seebaseclassmethod
6738*/
6740{
6741 QVector<double> result = QCPAxisTicker::createTickVector(tickStep, range);
6742 if (!result.isEmpty())
6743 {
6744 if (mDateStrategy == dsUniformTimeInDay)
6745 {
6746 QDateTime uniformDateTime = keyToDateTime(mTickOrigin); // the time of this datetime will be set for all other ticks, if possible
6747 QDateTime tickDateTime;
6748 for (int i=0; i<result.size(); ++i)
6749 {
6750 tickDateTime = keyToDateTime(result.at(i));
6751 tickDateTime.setTime(uniformDateTime.time());
6752 result[i] = dateTimeToKey(tickDateTime);
6753 }
6754 } else if (mDateStrategy == dsUniformDayInMonth)
6755 {
6756 QDateTime uniformDateTime = keyToDateTime(mTickOrigin); // this day (in month) and time will be set for all other ticks, if possible
6757 QDateTime tickDateTime;
6758 for (int i=0; i<result.size(); ++i)
6759 {
6760 tickDateTime = keyToDateTime(result.at(i));
6761 tickDateTime.setTime(uniformDateTime.time());
6762 int thisUniformDay = uniformDateTime.date().day() <= tickDateTime.date().daysInMonth() ? uniformDateTime.date().day() : tickDateTime.date().daysInMonth(); // don't exceed month (e.g. try to set day 31 in February)
6763 if (thisUniformDay-tickDateTime.date().day() < -15) // with leap years involved, date month may jump backwards or forwards, and needs to be corrected before setting day
6764 tickDateTime = tickDateTime.addMonths(1);
6765 else if (thisUniformDay-tickDateTime.date().day() > 15) // with leap years involved, date month may jump backwards or forwards, and needs to be corrected before setting day
6766 tickDateTime = tickDateTime.addMonths(-1);
6767 tickDateTime.setDate(QDate(tickDateTime.date().year(), tickDateTime.date().month(), thisUniformDay));
6768 result[i] = dateTimeToKey(tickDateTime);
6769 }
6770 }
6771 }
6772 return result;
6773}
6774
6775/*!
6776 A convenience method which turns \a key (in seconds since Epoch 1. Jan 1970, 00:00 UTC) into a
6777 QDateTime object. This can be used to turn axis coordinates to actual QDateTimes.
6778
6779 The accuracy achieved by this method is one millisecond, irrespective of the used Qt version (it
6780 works around the lack of a QDateTime::fromMSecsSinceEpoch in Qt 4.6)
6781
6782 \see dateTimeToKey
6783*/
6785{
6786# if QT_VERSION < QT_VERSION_CHECK(4, 7, 0)
6787 return QDateTime::fromTime_t(key).addMSecs((key-(qint64)key)*1000);
6788# else
6789 return QDateTime::fromMSecsSinceEpoch(qint64(key*1000.0));
6790# endif
6791}
6792
6793/*! \overload
6794
6795 A convenience method which turns a QDateTime object into a double value that corresponds to
6796 seconds since Epoch (1. Jan 1970, 00:00 UTC). This is the format used as axis coordinates by
6797 QCPAxisTickerDateTime.
6798
6799 The accuracy achieved by this method is one millisecond, irrespective of the used Qt version (it
6800 works around the lack of a QDateTime::toMSecsSinceEpoch in Qt 4.6)
6801
6802 \see keyToDateTime
6803*/
6805{
6806# if QT_VERSION < QT_VERSION_CHECK(4, 7, 0)
6807 return dateTime.toTime_t()+dateTime.time().msec()/1000.0;
6808# else
6809 return dateTime.toMSecsSinceEpoch()/1000.0;
6810# endif
6811}
6812
6813/*! \overload
6814
6815 A convenience method which turns a QDate object into a double value that corresponds to seconds
6816 since Epoch (1. Jan 1970, 00:00 UTC). This is the format used
6817 as axis coordinates by QCPAxisTickerDateTime.
6818
6819 The returned value will be the start of the passed day of \a date, interpreted in the given \a
6820 timeSpec.
6821
6822 \see keyToDateTime
6823*/
6825{
6826# if QT_VERSION < QT_VERSION_CHECK(4, 7, 0)
6827 return QDateTime(date, QTime(0, 0), timeSpec).toTime_t();
6828# elif QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
6829 return QDateTime(date, QTime(0, 0), timeSpec).toMSecsSinceEpoch()/1000.0;
6830# else
6831 return date.startOfDay(timeSpec).toMSecsSinceEpoch()/1000.0;
6832# endif
6833}
6834/* end of 'src/axis/axistickerdatetime.cpp' */
6835
6836
6837/* including file 'src/axis/axistickertime.cpp' */
6838/* modified 2022-11-06T12:45:56, size 11745 */
6839
6840////////////////////////////////////////////////////////////////////////////////////////////////////
6841//////////////////// QCPAxisTickerTime
6842////////////////////////////////////////////////////////////////////////////////////////////////////
6843/*! \class QCPAxisTickerTime
6844 \brief Specialized axis ticker for time spans in units of milliseconds to days
6845
6846 \image html axisticker-time.png
6847
6848 This QCPAxisTicker subclass generates ticks that corresponds to time intervals.
6849
6850 The format of the time display in the tick labels is controlled with \ref setTimeFormat and \ref
6851 setFieldWidth. The time coordinate is in the unit of seconds with respect to the time coordinate
6852 zero. Unlike with QCPAxisTickerDateTime, the ticks don't correspond to a specific calendar date
6853 and time.
6854
6855 The time can be displayed in milliseconds, seconds, minutes, hours and days. Depending on the
6856 largest available unit in the format specified with \ref setTimeFormat, any time spans above will
6857 be carried in that largest unit. So for example if the format string is "%m:%s" and a tick at
6858 coordinate value 7815 (being 2 hours, 10 minutes and 15 seconds) is created, the resulting tick
6859 label will show "130:15" (130 minutes, 15 seconds). If the format string is "%h:%m:%s", the hour
6860 unit will be used and the label will thus be "02:10:15". Negative times with respect to the axis
6861 zero will carry a leading minus sign.
6862
6863 The ticker can be created and assigned to an axis like this:
6864 \snippet documentation/doc-image-generator/mainwindow.cpp axistickertime-creation
6865
6866 Here is an example of a time axis providing time information in days, hours and minutes. Due to
6867 the axis range spanning a few days and the wanted tick count (\ref setTickCount), the ticker
6868 decided to use tick steps of 12 hours:
6869
6870 \image html axisticker-time2.png
6871
6872 The format string for this example is
6873 \snippet documentation/doc-image-generator/mainwindow.cpp axistickertime-creation-2
6874
6875 \note If you rather wish to display calendar dates and times, have a look at QCPAxisTickerDateTime
6876 instead.
6877*/
6878
6879/*!
6880 Constructs the ticker and sets reasonable default values. Axis tickers are commonly created
6881 managed by a QSharedPointer, which then can be passed to QCPAxis::setTicker.
6882*/
6884 mTimeFormat(QLatin1String("%h:%m:%s")),
6885 mSmallestUnit(tuSeconds),
6886 mBiggestUnit(tuHours)
6887{
6888 setTickCount(4);
6889 mFieldWidth[tuMilliseconds] = 3;
6890 mFieldWidth[tuSeconds] = 2;
6891 mFieldWidth[tuMinutes] = 2;
6892 mFieldWidth[tuHours] = 2;
6893 mFieldWidth[tuDays] = 1;
6894
6895 mFormatPattern[tuMilliseconds] = QLatin1String("%z");
6896 mFormatPattern[tuSeconds] = QLatin1String("%s");
6897 mFormatPattern[tuMinutes] = QLatin1String("%m");
6898 mFormatPattern[tuHours] = QLatin1String("%h");
6899 mFormatPattern[tuDays] = QLatin1String("%d");
6900}
6901
6902/*!
6903 Sets the format that will be used to display time in the tick labels.
6904
6905 The available patterns are:
6906 - %%z for milliseconds
6907 - %%s for seconds
6908 - %%m for minutes
6909 - %%h for hours
6910 - %%d for days
6911
6912 The field width (zero padding) can be controlled for each unit with \ref setFieldWidth.
6913
6914 The largest unit that appears in \a format will carry all the remaining time of a certain tick
6915 coordinate, even if it overflows the natural limit of the unit. For example, if %%m is the
6916 largest unit it might become larger than 59 in order to consume larger time values. If on the
6917 other hand %%h is available, the minutes will wrap around to zero after 59 and the time will
6918 carry to the hour digit.
6919*/
6921{
6922 mTimeFormat = format;
6923
6924 // determine smallest and biggest unit in format, to optimize unit replacement and allow biggest
6925 // unit to consume remaining time of a tick value and grow beyond its modulo (e.g. min > 59)
6926 mSmallestUnit = tuMilliseconds;
6927 mBiggestUnit = tuMilliseconds;
6928 bool hasSmallest = false;
6929 for (int i = tuMilliseconds; i <= tuDays; ++i)
6930 {
6931 TimeUnit unit = static_cast<TimeUnit>(i);
6932 if (mTimeFormat.contains(mFormatPattern.value(unit)))
6933 {
6934 if (!hasSmallest)
6935 {
6936 mSmallestUnit = unit;
6937 hasSmallest = true;
6938 }
6939 mBiggestUnit = unit;
6940 }
6941 }
6942}
6943
6944/*!
6945 Sets the field widh of the specified \a unit to be \a width digits, when displayed in the tick
6946 label. If the number for the specific unit is shorter than \a width, it will be padded with an
6947 according number of zeros to the left in order to reach the field width.
6948
6949 \see setTimeFormat
6950*/
6952{
6953 mFieldWidth[unit] = qMax(width, 1);
6954}
6955
6956/*! \internal
6957
6958 Returns the tick step appropriate for time displays, depending on the provided \a range and the
6959 smallest available time unit in the current format (\ref setTimeFormat). For example if the unit
6960 of seconds isn't available in the format, this method will not generate steps (like 2.5 minutes)
6961 that require sub-minute precision to be displayed correctly.
6962
6963 \seebaseclassmethod
6964*/
6966{
6967 double result = range.size()/double(mTickCount+1e-10); // mTickCount ticks on average, the small addition is to prevent jitter on exact integers
6968
6969 if (result < 1) // ideal tick step is below 1 second -> use normal clean mantissa algorithm in units of seconds
6970 {
6971 if (mSmallestUnit == tuMilliseconds)
6972 result = qMax(cleanMantissa(result), 0.001); // smallest tick step is 1 millisecond
6973 else // have no milliseconds available in format, so stick with 1 second tickstep
6974 result = 1.0;
6975 } else if (result < 3600*24) // below a day
6976 {
6977 // the filling of availableSteps seems a bit contorted but it fills in a sorted fashion and thus saves a post-fill sorting run
6978 QVector<double> availableSteps;
6979 // seconds range:
6980 if (mSmallestUnit <= tuSeconds)
6981 availableSteps << 1;
6982 if (mSmallestUnit == tuMilliseconds)
6983 availableSteps << 2.5; // only allow half second steps if milliseconds are there to display it
6984 else if (mSmallestUnit == tuSeconds)
6985 availableSteps << 2;
6986 if (mSmallestUnit <= tuSeconds)
6987 availableSteps << 5 << 10 << 15 << 30;
6988 // minutes range:
6989 if (mSmallestUnit <= tuMinutes)
6990 availableSteps << 1*60;
6991 if (mSmallestUnit <= tuSeconds)
6992 availableSteps << 2.5*60; // only allow half minute steps if seconds are there to display it
6993 else if (mSmallestUnit == tuMinutes)
6994 availableSteps << 2*60;
6995 if (mSmallestUnit <= tuMinutes)
6996 availableSteps << 5*60 << 10*60 << 15*60 << 30*60;
6997 // hours range:
6998 if (mSmallestUnit <= tuHours)
6999 availableSteps << 1*3600 << 2*3600 << 3*3600 << 6*3600 << 12*3600 << 24*3600;
7000 // pick available step that is most appropriate to approximate ideal step:
7001 result = pickClosest(result, availableSteps);
7002 } else // more than a day, go back to normal clean mantissa algorithm but in units of days
7003 {
7004 const double secondsPerDay = 3600*24;
7005 result = cleanMantissa(result/secondsPerDay)*secondsPerDay;
7006 }
7007 return result;
7008}
7009
7010/*! \internal
7011
7012 Returns the sub tick count appropriate for the provided \a tickStep and time displays.
7013
7014 \seebaseclassmethod
7015*/
7017{
7018 int result = QCPAxisTicker::getSubTickCount(tickStep);
7019 switch (qRound(tickStep)) // hand chosen subticks for specific minute/hour/day range (as specified in getTickStep)
7020 {
7021 case 5*60: result = 4; break;
7022 case 10*60: result = 1; break;
7023 case 15*60: result = 2; break;
7024 case 30*60: result = 1; break;
7025 case 60*60: result = 3; break;
7026 case 3600*2: result = 3; break;
7027 case 3600*3: result = 2; break;
7028 case 3600*6: result = 1; break;
7029 case 3600*12: result = 3; break;
7030 case 3600*24: result = 3; break;
7031 }
7032 return result;
7033}
7034
7035/*! \internal
7036
7037 Returns the tick label corresponding to the provided \a tick and the configured format and field
7038 widths (\ref setTimeFormat, \ref setFieldWidth).
7039
7040 \seebaseclassmethod
7041*/
7042QString QCPAxisTickerTime::getTickLabel(double tick, const QLocale &locale, QChar formatChar, int precision)
7043{
7044 Q_UNUSED(precision)
7045 Q_UNUSED(formatChar)
7046 Q_UNUSED(locale)
7047 bool negative = tick < 0;
7048 if (negative) tick *= -1;
7049 double values[tuDays+1]; // contains the msec/sec/min/... value with its respective modulo (e.g. minute 0..59)
7050 double restValues[tuDays+1]; // contains the msec/sec/min/... value as if it's the largest available unit and thus consumes the remaining time
7051
7052 restValues[tuMilliseconds] = tick*1000;
7053 values[tuMilliseconds] = modf(restValues[tuMilliseconds]/1000, &restValues[tuSeconds])*1000;
7054 values[tuSeconds] = modf(restValues[tuSeconds]/60, &restValues[tuMinutes])*60;
7055 values[tuMinutes] = modf(restValues[tuMinutes]/60, &restValues[tuHours])*60;
7056 values[tuHours] = modf(restValues[tuHours]/24, &restValues[tuDays])*24;
7057 // no need to set values[tuDays] because days are always a rest value (there is no higher unit so it consumes all remaining time)
7058
7059 QString result = mTimeFormat;
7060 for (int i = mSmallestUnit; i <= mBiggestUnit; ++i)
7061 {
7062 TimeUnit iUnit = static_cast<TimeUnit>(i);
7063 replaceUnit(result, iUnit, qRound(iUnit == mBiggestUnit ? restValues[iUnit] : values[iUnit]));
7064 }
7065 if (negative)
7066 result.prepend(QLatin1Char('-'));
7067 return result;
7068}
7069
7070/*! \internal
7071
7072 Replaces all occurrences of the format pattern belonging to \a unit in \a text with the specified
7073 \a value, using the field width as specified with \ref setFieldWidth for the \a unit.
7074*/
7076{
7077 QString valueStr = QString::number(value);
7078 while (valueStr.size() < mFieldWidth.value(unit))
7079 valueStr.prepend(QLatin1Char('0'));
7080
7081 text.replace(mFormatPattern.value(unit), valueStr);
7082}
7083/* end of 'src/axis/axistickertime.cpp' */
7084
7085
7086/* including file 'src/axis/axistickerfixed.cpp' */
7087/* modified 2022-11-06T12:45:56, size 5575 */
7088
7089////////////////////////////////////////////////////////////////////////////////////////////////////
7090//////////////////// QCPAxisTickerFixed
7091////////////////////////////////////////////////////////////////////////////////////////////////////
7092/*! \class QCPAxisTickerFixed
7093 \brief Specialized axis ticker with a fixed tick step
7094
7095 \image html axisticker-fixed.png
7096
7097 This QCPAxisTicker subclass generates ticks with a fixed tick step set with \ref setTickStep. It
7098 is also possible to allow integer multiples and integer powers of the specified tick step with
7099 \ref setScaleStrategy.
7100
7101 A typical application of this ticker is to make an axis only display integers, by setting the
7102 tick step of the ticker to 1.0 and the scale strategy to \ref ssMultiples.
7103
7104 Another case is when a certain number has a special meaning and axis ticks should only appear at
7105 multiples of that value. In this case you might also want to consider \ref QCPAxisTickerPi
7106 because despite the name it is not limited to only pi symbols/values.
7107
7108 The ticker can be created and assigned to an axis like this:
7109 \snippet documentation/doc-image-generator/mainwindow.cpp axistickerfixed-creation
7110*/
7111
7112/*!
7113 Constructs the ticker and sets reasonable default values. Axis tickers are commonly created
7114 managed by a QSharedPointer, which then can be passed to QCPAxis::setTicker.
7115*/
7117 mTickStep(1.0),
7118 mScaleStrategy(ssNone)
7119{
7120}
7121
7122/*!
7123 Sets the fixed tick interval to \a step.
7124
7125 The axis ticker will only use this tick step when generating axis ticks. This might cause a very
7126 high tick density and overlapping labels if the axis range is zoomed out. Using \ref
7127 setScaleStrategy it is possible to relax the fixed step and also allow multiples or powers of \a
7128 step. This will enable the ticker to reduce the number of ticks to a reasonable amount (see \ref
7129 setTickCount).
7130*/
7132{
7133 if (step > 0)
7134 mTickStep = step;
7135 else
7136 qDebug() << Q_FUNC_INFO << "tick step must be greater than zero:" << step;
7137}
7138
7139/*!
7140 Sets whether the specified tick step (\ref setTickStep) is absolutely fixed or whether
7141 modifications may be applied to it before calculating the finally used tick step, such as
7142 permitting multiples or powers. See \ref ScaleStrategy for details.
7143
7144 The default strategy is \ref ssNone, which means the tick step is absolutely fixed.
7145*/
7147{
7148 mScaleStrategy = strategy;
7149}
7150
7151/*! \internal
7152
7153 Determines the actually used tick step from the specified tick step and scale strategy (\ref
7154 setTickStep, \ref setScaleStrategy).
7155
7156 This method either returns the specified tick step exactly, or, if the scale strategy is not \ref
7157 ssNone, a modification of it to allow varying the number of ticks in the current axis range.
7158
7159 \seebaseclassmethod
7160*/
7162{
7163 switch (mScaleStrategy)
7164 {
7165 case ssNone:
7166 {
7167 return mTickStep;
7168 }
7169 case ssMultiples:
7170 {
7171 double exactStep = range.size()/double(mTickCount+1e-10); // mTickCount ticks on average, the small addition is to prevent jitter on exact integers
7172 if (exactStep < mTickStep)
7173 return mTickStep;
7174 else
7175 return qint64(cleanMantissa(exactStep/mTickStep)+0.5)*mTickStep;
7176 }
7177 case ssPowers:
7178 {
7179 double exactStep = range.size()/double(mTickCount+1e-10); // mTickCount ticks on average, the small addition is to prevent jitter on exact integers
7180 return qPow(mTickStep, int(qLn(exactStep)/qLn(mTickStep)+0.5));
7181 }
7182 }
7183 return mTickStep;
7184}
7185/* end of 'src/axis/axistickerfixed.cpp' */
7186
7187
7188/* including file 'src/axis/axistickertext.cpp' */
7189/* modified 2022-11-06T12:45:56, size 8742 */
7190
7191////////////////////////////////////////////////////////////////////////////////////////////////////
7192//////////////////// QCPAxisTickerText
7193////////////////////////////////////////////////////////////////////////////////////////////////////
7194/*! \class QCPAxisTickerText
7195 \brief Specialized axis ticker which allows arbitrary labels at specified coordinates
7196
7197 \image html axisticker-text.png
7198
7199 This QCPAxisTicker subclass generates ticks which can be directly specified by the user as
7200 coordinates and associated strings. They can be passed as a whole with \ref setTicks or one at a
7201 time with \ref addTick. Alternatively you can directly access the internal storage via \ref ticks
7202 and modify the tick/label data there.
7203
7204 This is useful for cases where the axis represents categories rather than numerical values.
7205
7206 If you are updating the ticks of this ticker regularly and in a dynamic fasion (e.g. dependent on
7207 the axis range), it is a sign that you should probably create an own ticker by subclassing
7208 QCPAxisTicker, instead of using this one.
7209
7210 The ticker can be created and assigned to an axis like this:
7211 \snippet documentation/doc-image-generator/mainwindow.cpp axistickertext-creation
7212*/
7213
7214/* start of documentation of inline functions */
7215
7216/*! \fn QMap<double, QString> &QCPAxisTickerText::ticks()
7217
7218 Returns a non-const reference to the internal map which stores the tick coordinates and their
7219 labels.
7220
7221 You can access the map directly in order to add, remove or manipulate ticks, as an alternative to
7222 using the methods provided by QCPAxisTickerText, such as \ref setTicks and \ref addTick.
7223*/
7224
7225/* end of documentation of inline functions */
7226
7227/*!
7228 Constructs the ticker and sets reasonable default values. Axis tickers are commonly created
7229 managed by a QSharedPointer, which then can be passed to QCPAxis::setTicker.
7230*/
7232 mSubTickCount(0)
7233{
7234}
7235
7236/*! \overload
7237
7238 Sets the ticks that shall appear on the axis. The map key of \a ticks corresponds to the axis
7239 coordinate, and the map value is the string that will appear as tick label.
7240
7241 An alternative to manipulate ticks is to directly access the internal storage with the \ref ticks
7242 getter.
7243
7244 \see addTicks, addTick, clear
7245*/
7247{
7248 mTicks = ticks;
7249}
7250
7251/*! \overload
7252
7253 Sets the ticks that shall appear on the axis. The entries of \a positions correspond to the axis
7254 coordinates, and the entries of \a labels are the respective strings that will appear as tick
7255 labels.
7256
7257 \see addTicks, addTick, clear
7258*/
7260{
7261 clear();
7262 addTicks(positions, labels);
7263}
7264
7265/*!
7266 Sets the number of sub ticks that shall appear between ticks. For QCPAxisTickerText, there is no
7267 automatic sub tick count calculation. So if sub ticks are needed, they must be configured with this
7268 method.
7269*/
7271{
7272 if (subTicks >= 0)
7273 mSubTickCount = subTicks;
7274 else
7275 qDebug() << Q_FUNC_INFO << "sub tick count can't be negative:" << subTicks;
7276}
7277
7278/*!
7279 Clears all ticks.
7280
7281 An alternative to manipulate ticks is to directly access the internal storage with the \ref ticks
7282 getter.
7283
7284 \see setTicks, addTicks, addTick
7285*/
7287{
7288 mTicks.clear();
7289}
7290
7291/*!
7292 Adds a single tick to the axis at the given axis coordinate \a position, with the provided tick \a
7293 label.
7294
7295 \see addTicks, setTicks, clear
7296*/
7297void QCPAxisTickerText::addTick(double position, const QString &label)
7298{
7299 mTicks.insert(position, label);
7300}
7301
7302/*! \overload
7303
7304 Adds the provided \a ticks to the ones already existing. The map key of \a ticks corresponds to
7305 the axis coordinate, and the map value is the string that will appear as tick label.
7306
7307 An alternative to manipulate ticks is to directly access the internal storage with the \ref ticks
7308 getter.
7309
7310 \see addTick, setTicks, clear
7311*/
7313{
7314#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
7315 mTicks.unite(ticks);
7316#else
7317 mTicks.insert(ticks);
7318#endif
7319}
7320
7321/*! \overload
7322
7323 Adds the provided ticks to the ones already existing. The entries of \a positions correspond to
7324 the axis coordinates, and the entries of \a labels are the respective strings that will appear as
7325 tick labels.
7326
7327 An alternative to manipulate ticks is to directly access the internal storage with the \ref ticks
7328 getter.
7329
7330 \see addTick, setTicks, clear
7331*/
7333{
7334 if (positions.size() != labels.size())
7335 qDebug() << Q_FUNC_INFO << "passed unequal length vectors for positions and labels:" << positions.size() << labels.size();
7336 int n = qMin(positions.size(), labels.size());
7337 for (int i=0; i<n; ++i)
7338 mTicks.insert(positions.at(i), labels.at(i));
7339}
7340
7341/*!
7342 Since the tick coordinates are provided externally, this method implementation does nothing.
7343
7344 \seebaseclassmethod
7345*/
7347{
7348 // text axis ticker has manual tick positions, so doesn't need this method
7349 Q_UNUSED(range)
7350 return 1.0;
7351}
7352
7353/*!
7354 Returns the sub tick count that was configured with \ref setSubTickCount.
7355
7356 \seebaseclassmethod
7357*/
7359{
7360 Q_UNUSED(tickStep)
7361 return mSubTickCount;
7362}
7363
7364/*!
7365 Returns the tick label which corresponds to the key \a tick in the internal tick storage. Since
7366 the labels are provided externally, \a locale, \a formatChar, and \a precision are ignored.
7367
7368 \seebaseclassmethod
7369*/
7370QString QCPAxisTickerText::getTickLabel(double tick, const QLocale &locale, QChar formatChar, int precision)
7371{
7372 Q_UNUSED(locale)
7373 Q_UNUSED(formatChar)
7374 Q_UNUSED(precision)
7375 return mTicks.value(tick);
7376}
7377
7378/*!
7379 Returns the externally provided tick coordinates which are in the specified \a range. If
7380 available, one tick above and below the range is provided in addition, to allow possible sub tick
7381 calculation. The parameter \a tickStep is ignored.
7382
7383 \seebaseclassmethod
7384*/
7386{
7387 Q_UNUSED(tickStep)
7388 QVector<double> result;
7389 if (mTicks.isEmpty())
7390 return result;
7391
7392 const QMap<double, QString> constTicks(mTicks);
7393
7394 QMap<double, QString>::const_iterator start = constTicks.lowerBound(range.lower);
7395 QMap<double, QString>::const_iterator end = constTicks.upperBound(range.upper);
7396 // this method should try to give one tick outside of range so proper subticks can be generated:
7397 if (start != mTicks.constBegin()) --start;
7398 if (end != mTicks.constEnd()) ++end;
7399 for (QMap<double, QString>::const_iterator it = start; it != end; ++it)
7400 result.append(it.key());
7401
7402 return result;
7403}
7404/* end of 'src/axis/axistickertext.cpp' */
7405
7406
7407/* including file 'src/axis/axistickerpi.cpp' */
7408/* modified 2022-11-06T12:45:56, size 11177 */
7409
7410////////////////////////////////////////////////////////////////////////////////////////////////////
7411//////////////////// QCPAxisTickerPi
7412////////////////////////////////////////////////////////////////////////////////////////////////////
7413/*! \class QCPAxisTickerPi
7414 \brief Specialized axis ticker to display ticks in units of an arbitrary constant, for example pi
7415
7416 \image html axisticker-pi.png
7417
7418 This QCPAxisTicker subclass generates ticks that are expressed with respect to a given symbolic
7419 constant with a numerical value specified with \ref setPiValue and an appearance in the tick
7420 labels specified with \ref setPiSymbol.
7421
7422 Ticks may be generated at fractions of the symbolic constant. How these fractions appear in the
7423 tick label can be configured with \ref setFractionStyle.
7424
7425 The ticker can be created and assigned to an axis like this:
7426 \snippet documentation/doc-image-generator/mainwindow.cpp axistickerpi-creation
7427*/
7428
7429/*!
7430 Constructs the ticker and sets reasonable default values. Axis tickers are commonly created
7431 managed by a QSharedPointer, which then can be passed to QCPAxis::setTicker.
7432*/
7434 mPiSymbol(QLatin1String(" ")+QChar(0x03C0)),
7435 mPiValue(M_PI),
7436 mPeriodicity(0),
7437 mFractionStyle(fsUnicodeFractions),
7438 mPiTickStep(0)
7439{
7440 setTickCount(4);
7441}
7442
7443/*!
7444 Sets how the symbol part (which is always a suffix to the number) shall appear in the axis tick
7445 label.
7446
7447 If a space shall appear between the number and the symbol, make sure the space is contained in \a
7448 symbol.
7449*/
7451{
7452 mPiSymbol = symbol;
7453}
7454
7455/*!
7456 Sets the numerical value that the symbolic constant has.
7457
7458 This will be used to place the appropriate fractions of the symbol at the respective axis
7459 coordinates.
7460*/
7462{
7463 mPiValue = pi;
7464}
7465
7466/*!
7467 Sets whether the axis labels shall appear periodicly and if so, at which multiplicity of the
7468 symbolic constant.
7469
7470 To disable periodicity, set \a multiplesOfPi to zero.
7471
7472 For example, an axis that identifies 0 with 2pi would set \a multiplesOfPi to two.
7473*/
7474void QCPAxisTickerPi::setPeriodicity(int multiplesOfPi)
7475{
7476 mPeriodicity = qAbs(multiplesOfPi);
7477}
7478
7479/*!
7480 Sets how the numerical/fractional part preceding the symbolic constant is displayed in tick
7481 labels. See \ref FractionStyle for the various options.
7482*/
7484{
7485 mFractionStyle = style;
7486}
7487
7488/*! \internal
7489
7490 Returns the tick step, using the constant's value (\ref setPiValue) as base unit. In consequence
7491 the numerical/fractional part preceding the symbolic constant is made to have a readable
7492 mantissa.
7493
7494 \seebaseclassmethod
7495*/
7497{
7498 mPiTickStep = range.size()/mPiValue/double(mTickCount+1e-10); // mTickCount ticks on average, the small addition is to prevent jitter on exact integers
7499 mPiTickStep = cleanMantissa(mPiTickStep);
7500 return mPiTickStep*mPiValue;
7501}
7502
7503/*! \internal
7504
7505 Returns the sub tick count, using the constant's value (\ref setPiValue) as base unit. In
7506 consequence the sub ticks divide the numerical/fractional part preceding the symbolic constant
7507 reasonably, and not the total tick coordinate.
7508
7509 \seebaseclassmethod
7510*/
7512{
7513 return QCPAxisTicker::getSubTickCount(tickStep/mPiValue);
7514}
7515
7516/*! \internal
7517
7518 Returns the tick label as a fractional/numerical part and a symbolic string as suffix. The
7519 formatting of the fraction is done according to the specified \ref setFractionStyle. The appended
7520 symbol is specified with \ref setPiSymbol.
7521
7522 \seebaseclassmethod
7523*/
7524QString QCPAxisTickerPi::getTickLabel(double tick, const QLocale &locale, QChar formatChar, int precision)
7525{
7526 double tickInPis = tick/mPiValue;
7527 if (mPeriodicity > 0)
7528 tickInPis = fmod(tickInPis, mPeriodicity);
7529
7530 if (mFractionStyle != fsFloatingPoint && mPiTickStep > 0.09 && mPiTickStep < 50)
7531 {
7532 // simply construct fraction from decimal like 1.234 -> 1234/1000 and then simplify fraction, smaller digits are irrelevant due to mPiTickStep conditional above
7533 int denominator = 1000;
7534 int numerator = qRound(tickInPis*denominator);
7535 simplifyFraction(numerator, denominator);
7536 if (qAbs(numerator) == 1 && denominator == 1)
7537 return (numerator < 0 ? QLatin1String("-") : QLatin1String("")) + mPiSymbol.trimmed();
7538 else if (numerator == 0)
7539 return QLatin1String("0");
7540 else
7541 return fractionToString(numerator, denominator) + mPiSymbol;
7542 } else
7543 {
7544 if (qFuzzyIsNull(tickInPis))
7545 return QLatin1String("0");
7546 else if (qFuzzyCompare(qAbs(tickInPis), 1.0))
7547 return (tickInPis < 0 ? QLatin1String("-") : QLatin1String("")) + mPiSymbol.trimmed();
7548 else
7549 return QCPAxisTicker::getTickLabel(tickInPis, locale, formatChar, precision) + mPiSymbol;
7550 }
7551}
7552
7553/*! \internal
7554
7555 Takes the fraction given by \a numerator and \a denominator and modifies the values to make sure
7556 the fraction is in irreducible form, i.e. numerator and denominator don't share any common
7557 factors which could be cancelled.
7558*/
7559void QCPAxisTickerPi::simplifyFraction(int &numerator, int &denominator) const
7560{
7561 if (numerator == 0 || denominator == 0)
7562 return;
7563
7564 int num = numerator;
7565 int denom = denominator;
7566 while (denom != 0) // euclidean gcd algorithm
7567 {
7568 int oldDenom = denom;
7569 denom = num % denom;
7570 num = oldDenom;
7571 }
7572 // num is now gcd of numerator and denominator
7573 numerator /= num;
7574 denominator /= num;
7575}
7576
7577/*! \internal
7578
7579 Takes the fraction given by \a numerator and \a denominator and returns a string representation.
7580 The result depends on the configured fraction style (\ref setFractionStyle).
7581
7582 This method is used to format the numerical/fractional part when generating tick labels. It
7583 simplifies the passed fraction to an irreducible form using \ref simplifyFraction and factors out
7584 any integer parts of the fraction (e.g. "10/4" becomes "2 1/2").
7585*/
7586QString QCPAxisTickerPi::fractionToString(int numerator, int denominator) const
7587{
7588 if (denominator == 0)
7589 {
7590 qDebug() << Q_FUNC_INFO << "called with zero denominator";
7591 return QString();
7592 }
7593 if (mFractionStyle == fsFloatingPoint) // should never be the case when calling this function
7594 {
7595 qDebug() << Q_FUNC_INFO << "shouldn't be called with fraction style fsDecimal";
7596 return QString::number(numerator/double(denominator)); // failsafe
7597 }
7598 int sign = numerator*denominator < 0 ? -1 : 1;
7599 numerator = qAbs(numerator);
7600 denominator = qAbs(denominator);
7601
7602 if (denominator == 1)
7603 {
7604 return QString::number(sign*numerator);
7605 } else
7606 {
7607 int integerPart = numerator/denominator;
7608 int remainder = numerator%denominator;
7609 if (remainder == 0)
7610 {
7611 return QString::number(sign*integerPart);
7612 } else
7613 {
7614 if (mFractionStyle == fsAsciiFractions)
7615 {
7616 return QString(QLatin1String("%1%2%3/%4"))
7617 .arg(sign == -1 ? QLatin1String("-") : QLatin1String(""))
7618 .arg(integerPart > 0 ? QString::number(integerPart)+QLatin1String(" ") : QString(QLatin1String("")))
7619 .arg(remainder)
7620 .arg(denominator);
7621 } else if (mFractionStyle == fsUnicodeFractions)
7622 {
7623 return QString(QLatin1String("%1%2%3"))
7624 .arg(sign == -1 ? QLatin1String("-") : QLatin1String(""))
7625 .arg(integerPart > 0 ? QString::number(integerPart) : QLatin1String(""))
7626 .arg(unicodeFraction(remainder, denominator));
7627 }
7628 }
7629 }
7630 return QString();
7631}
7632
7633/*! \internal
7634
7635 Returns the unicode string representation of the fraction given by \a numerator and \a
7636 denominator. This is the representation used in \ref fractionToString when the fraction style
7637 (\ref setFractionStyle) is \ref fsUnicodeFractions.
7638
7639 This method doesn't use the single-character common fractions but builds each fraction from a
7640 superscript unicode number, the unicode fraction character, and a subscript unicode number.
7641*/
7642QString QCPAxisTickerPi::unicodeFraction(int numerator, int denominator) const
7643{
7644 return unicodeSuperscript(numerator)+QChar(0x2044)+unicodeSubscript(denominator);
7645}
7646
7647/*! \internal
7648
7649 Returns the unicode string representing \a number as superscript. This is used to build
7650 unicode fractions in \ref unicodeFraction.
7651*/
7653{
7654 if (number == 0)
7655 return QString(QChar(0x2070));
7656
7657 QString result;
7658 while (number > 0)
7659 {
7660 const int digit = number%10;
7661 switch (digit)
7662 {
7663 case 1: { result.prepend(QChar(0x00B9)); break; }
7664 case 2: { result.prepend(QChar(0x00B2)); break; }
7665 case 3: { result.prepend(QChar(0x00B3)); break; }
7666 default: { result.prepend(QChar(0x2070+digit)); break; }
7667 }
7668 number /= 10;
7669 }
7670 return result;
7671}
7672
7673/*! \internal
7674
7675 Returns the unicode string representing \a number as subscript. This is used to build unicode
7676 fractions in \ref unicodeFraction.
7677*/
7679{
7680 if (number == 0)
7681 return QString(QChar(0x2080));
7682
7683 QString result;
7684 while (number > 0)
7685 {
7686 result.prepend(QChar(0x2080+number%10));
7687 number /= 10;
7688 }
7689 return result;
7690}
7691/* end of 'src/axis/axistickerpi.cpp' */
7692
7693
7694/* including file 'src/axis/axistickerlog.cpp' */
7695/* modified 2022-11-06T12:45:56, size 7890 */
7696
7697////////////////////////////////////////////////////////////////////////////////////////////////////
7698//////////////////// QCPAxisTickerLog
7699////////////////////////////////////////////////////////////////////////////////////////////////////
7700/*! \class QCPAxisTickerLog
7701 \brief Specialized axis ticker suited for logarithmic axes
7702
7703 \image html axisticker-log.png
7704
7705 This QCPAxisTicker subclass generates ticks with unequal tick intervals suited for logarithmic
7706 axis scales. The ticks are placed at powers of the specified log base (\ref setLogBase).
7707
7708 Especially in the case of a log base equal to 10 (the default), it might be desirable to have
7709 tick labels in the form of powers of ten without mantissa display. To achieve this, set the
7710 number precision (\ref QCPAxis::setNumberPrecision) to zero and the number format (\ref
7711 QCPAxis::setNumberFormat) to scientific (exponential) display with beautifully typeset decimal
7712 powers, so a format string of <tt>"eb"</tt>. This will result in the following axis tick labels:
7713
7714 \image html axisticker-log-powers.png
7715
7716 The ticker can be created and assigned to an axis like this:
7717 \snippet documentation/doc-image-generator/mainwindow.cpp axistickerlog-creation
7718
7719 Note that the nature of logarithmic ticks imply that there exists a smallest possible tick step,
7720 corresponding to one multiplication by the log base. If the user zooms in further than that, no
7721 new ticks would appear, leading to very sparse or even no axis ticks on the axis. To prevent this
7722 situation, this ticker falls back to regular tick generation if the axis range would be covered
7723 by too few logarithmically placed ticks.
7724*/
7725
7726/*!
7727 Constructs the ticker and sets reasonable default values. Axis tickers are commonly created
7728 managed by a QSharedPointer, which then can be passed to QCPAxis::setTicker.
7729*/
7731 mLogBase(10.0),
7732 mSubTickCount(8), // generates 10 intervals
7733 mLogBaseLnInv(1.0/qLn(mLogBase))
7734{
7735}
7736
7737/*!
7738 Sets the logarithm base used for tick coordinate generation. The ticks will be placed at integer
7739 powers of \a base.
7740*/
7742{
7743 if (base > 0)
7744 {
7745 mLogBase = base;
7746 mLogBaseLnInv = 1.0/qLn(mLogBase);
7747 } else
7748 qDebug() << Q_FUNC_INFO << "log base has to be greater than zero:" << base;
7749}
7750
7751/*!
7752 Sets the number of sub ticks in a tick interval. Within each interval, the sub ticks are spaced
7753 linearly to provide a better visual guide, so the sub tick density increases toward the higher
7754 tick.
7755
7756 Note that \a subTicks is the number of sub ticks (not sub intervals) in one tick interval. So in
7757 the case of logarithm base 10 an intuitive sub tick spacing would be achieved with eight sub
7758 ticks (the default). This means e.g. between the ticks 10 and 100 there will be eight ticks,
7759 namely at 20, 30, 40, 50, 60, 70, 80 and 90.
7760*/
7762{
7763 if (subTicks >= 0)
7764 mSubTickCount = subTicks;
7765 else
7766 qDebug() << Q_FUNC_INFO << "sub tick count can't be negative:" << subTicks;
7767}
7768
7769/*! \internal
7770
7771 Returns the sub tick count specified in \ref setSubTickCount. For QCPAxisTickerLog, there is no
7772 automatic sub tick count calculation necessary.
7773
7774 \seebaseclassmethod
7775*/
7777{
7778 Q_UNUSED(tickStep)
7779 return mSubTickCount;
7780}
7781
7782/*! \internal
7783
7784 Creates ticks with a spacing given by the logarithm base and an increasing integer power in the
7785 provided \a range. The step in which the power increases tick by tick is chosen in order to keep
7786 the total number of ticks as close as possible to the tick count (\ref setTickCount).
7787
7788 The parameter \a tickStep is ignored for the normal logarithmic ticker generation. Only when
7789 zoomed in very far such that not enough logarithmically placed ticks would be visible, this
7790 function falls back to the regular QCPAxisTicker::createTickVector, which then uses \a tickStep.
7791
7792 \seebaseclassmethod
7793*/
7795{
7796 QVector<double> result;
7797 if (range.lower > 0 && range.upper > 0) // positive range
7798 {
7799 const double baseTickCount = qLn(range.upper/range.lower)*mLogBaseLnInv;
7800 if (baseTickCount < 1.6) // if too few log ticks would be visible in axis range, fall back to regular tick vector generation
7801 return QCPAxisTicker::createTickVector(tickStep, range);
7802 const double exactPowerStep = baseTickCount/double(mTickCount+1e-10);
7803 const double newLogBase = qPow(mLogBase, qMax(int(cleanMantissa(exactPowerStep)), 1));
7804 double currentTick = qPow(newLogBase, qFloor(qLn(range.lower)/qLn(newLogBase)));
7805 result.append(currentTick);
7806 while (currentTick < range.upper && currentTick > 0) // currentMag might be zero for ranges ~1e-300, just cancel in that case
7807 {
7808 currentTick *= newLogBase;
7809 result.append(currentTick);
7810 }
7811 } else if (range.lower < 0 && range.upper < 0) // negative range
7812 {
7813 const double baseTickCount = qLn(range.lower/range.upper)*mLogBaseLnInv;
7814 if (baseTickCount < 1.6) // if too few log ticks would be visible in axis range, fall back to regular tick vector generation
7815 return QCPAxisTicker::createTickVector(tickStep, range);
7816 const double exactPowerStep = baseTickCount/double(mTickCount+1e-10);
7817 const double newLogBase = qPow(mLogBase, qMax(int(cleanMantissa(exactPowerStep)), 1));
7818 double currentTick = -qPow(newLogBase, qCeil(qLn(-range.lower)/qLn(newLogBase)));
7819 result.append(currentTick);
7820 while (currentTick < range.upper && currentTick < 0) // currentMag might be zero for ranges ~1e-300, just cancel in that case
7821 {
7822 currentTick /= newLogBase;
7823 result.append(currentTick);
7824 }
7825 } else // invalid range for logarithmic scale, because lower and upper have different sign
7826 {
7827 qDebug() << Q_FUNC_INFO << "Invalid range for logarithmic plot: " << range.lower << ".." << range.upper;
7828 }
7829
7830 return result;
7831}
7832/* end of 'src/axis/axistickerlog.cpp' */
7833
7834
7835/* including file 'src/axis/axis.cpp' */
7836/* modified 2022-11-06T12:45:56, size 99911 */
7837
7838
7839////////////////////////////////////////////////////////////////////////////////////////////////////
7840//////////////////// QCPGrid
7841////////////////////////////////////////////////////////////////////////////////////////////////////
7842
7843/*! \class QCPGrid
7844 \brief Responsible for drawing the grid of a QCPAxis.
7845
7846 This class is tightly bound to QCPAxis. Every axis owns a grid instance and uses it to draw the
7847 grid lines, sub grid lines and zero-line. You can interact with the grid of an axis via \ref
7848 QCPAxis::grid. Normally, you don't need to create an instance of QCPGrid yourself.
7849
7850 The axis and grid drawing was split into two classes to allow them to be placed on different
7851 layers (both QCPAxis and QCPGrid inherit from QCPLayerable). Thus it is possible to have the grid
7852 in the background and the axes in the foreground, and any plottables/items in between. This
7853 described situation is the default setup, see the QCPLayer documentation.
7854*/
7855
7856/*!
7857 Creates a QCPGrid instance and sets default values.
7858
7859 You shouldn't instantiate grids on their own, since every QCPAxis brings its own QCPGrid.
7860*/
7862 QCPLayerable(parentAxis->parentPlot(), QString(), parentAxis),
7863 mSubGridVisible{},
7864 mAntialiasedSubGrid{},
7865 mAntialiasedZeroLine{},
7866 mParentAxis(parentAxis)
7867{
7868 // warning: this is called in QCPAxis constructor, so parentAxis members should not be accessed/called
7869 setParent(parentAxis);
7870 setPen(QPen(QColor(200,200,200), 0, Qt::DotLine));
7871 setSubGridPen(QPen(QColor(220,220,220), 0, Qt::DotLine));
7872 setZeroLinePen(QPen(QColor(200,200,200), 0, Qt::SolidLine));
7873 setSubGridVisible(false);
7874 setAntialiased(false);
7875 setAntialiasedSubGrid(false);
7877}
7878
7879/*!
7880 Sets whether grid lines at sub tick marks are drawn.
7881
7882 \see setSubGridPen
7883*/
7885{
7886 mSubGridVisible = visible;
7887}
7888
7889/*!
7890 Sets whether sub grid lines are drawn antialiased.
7891*/
7893{
7894 mAntialiasedSubGrid = enabled;
7895}
7896
7897/*!
7898 Sets whether zero lines are drawn antialiased.
7899*/
7901{
7902 mAntialiasedZeroLine = enabled;
7903}
7904
7905/*!
7906 Sets the pen with which (major) grid lines are drawn.
7907*/
7908void QCPGrid::setPen(const QPen &pen)
7909{
7910 mPen = pen;
7911}
7912
7913/*!
7914 Sets the pen with which sub grid lines are drawn.
7915*/
7917{
7918 mSubGridPen = pen;
7919}
7920
7921/*!
7922 Sets the pen with which zero lines are drawn.
7923
7924 Zero lines are lines at value coordinate 0 which may be drawn with a different pen than other grid
7925 lines. To disable zero lines and just draw normal grid lines at zero, set \a pen to Qt::NoPen.
7926*/
7928{
7929 mZeroLinePen = pen;
7930}
7931
7932/*! \internal
7933
7934 A convenience function to easily set the QPainter::Antialiased hint on the provided \a painter
7935 before drawing the major grid lines.
7936
7937 This is the antialiasing state the painter passed to the \ref draw method is in by default.
7938
7939 This function takes into account the local setting of the antialiasing flag as well as the
7940 overrides set with \ref QCustomPlot::setAntialiasedElements and \ref
7941 QCustomPlot::setNotAntialiasedElements.
7942
7943 \see setAntialiased
7944*/
7946{
7947 applyAntialiasingHint(painter, mAntialiased, QCP::aeGrid);
7948}
7949
7950/*! \internal
7951
7952 Draws grid lines and sub grid lines at the positions of (sub) ticks of the parent axis, spanning
7953 over the complete axis rect. Also draws the zero line, if appropriate (\ref setZeroLinePen).
7954*/
7956{
7957 if (!mParentAxis) { qDebug() << Q_FUNC_INFO << "invalid parent axis"; return; }
7958
7959 if (mParentAxis->subTicks() && mSubGridVisible)
7960 drawSubGridLines(painter);
7961 drawGridLines(painter);
7962}
7963
7964/*! \internal
7965
7966 Draws the main grid lines and possibly a zero line with the specified painter.
7967
7968 This is a helper function called by \ref draw.
7969*/
7971{
7972 if (!mParentAxis) { qDebug() << Q_FUNC_INFO << "invalid parent axis"; return; }
7973
7974 const int tickCount = mParentAxis->mTickVector.size();
7975 double t; // helper variable, result of coordinate-to-pixel transforms
7976 if (mParentAxis->orientation() == Qt::Horizontal)
7977 {
7978 // draw zeroline:
7979 int zeroLineIndex = -1;
7980 if (mZeroLinePen.style() != Qt::NoPen && mParentAxis->mRange.lower < 0 && mParentAxis->mRange.upper > 0)
7981 {
7982 applyAntialiasingHint(painter, mAntialiasedZeroLine, QCP::aeZeroLine);
7983 painter->setPen(mZeroLinePen);
7984 double epsilon = mParentAxis->range().size()*1E-6; // for comparing double to zero
7985 for (int i=0; i<tickCount; ++i)
7986 {
7987 if (qAbs(mParentAxis->mTickVector.at(i)) < epsilon)
7988 {
7989 zeroLineIndex = i;
7990 t = mParentAxis->coordToPixel(mParentAxis->mTickVector.at(i)); // x
7991 painter->drawLine(QLineF(t, mParentAxis->mAxisRect->bottom(), t, mParentAxis->mAxisRect->top()));
7992 break;
7993 }
7994 }
7995 }
7996 // draw grid lines:
7998 painter->setPen(mPen);
7999 for (int i=0; i<tickCount; ++i)
8000 {
8001 if (i == zeroLineIndex) continue; // don't draw a gridline on top of the zeroline
8002 t = mParentAxis->coordToPixel(mParentAxis->mTickVector.at(i)); // x
8003 painter->drawLine(QLineF(t, mParentAxis->mAxisRect->bottom(), t, mParentAxis->mAxisRect->top()));
8004 }
8005 } else
8006 {
8007 // draw zeroline:
8008 int zeroLineIndex = -1;
8009 if (mZeroLinePen.style() != Qt::NoPen && mParentAxis->mRange.lower < 0 && mParentAxis->mRange.upper > 0)
8010 {
8011 applyAntialiasingHint(painter, mAntialiasedZeroLine, QCP::aeZeroLine);
8012 painter->setPen(mZeroLinePen);
8013 double epsilon = mParentAxis->mRange.size()*1E-6; // for comparing double to zero
8014 for (int i=0; i<tickCount; ++i)
8015 {
8016 if (qAbs(mParentAxis->mTickVector.at(i)) < epsilon)
8017 {
8018 zeroLineIndex = i;
8019 t = mParentAxis->coordToPixel(mParentAxis->mTickVector.at(i)); // y
8020 painter->drawLine(QLineF(mParentAxis->mAxisRect->left(), t, mParentAxis->mAxisRect->right(), t));
8021 break;
8022 }
8023 }
8024 }
8025 // draw grid lines:
8027 painter->setPen(mPen);
8028 for (int i=0; i<tickCount; ++i)
8029 {
8030 if (i == zeroLineIndex) continue; // don't draw a gridline on top of the zeroline
8031 t = mParentAxis->coordToPixel(mParentAxis->mTickVector.at(i)); // y
8032 painter->drawLine(QLineF(mParentAxis->mAxisRect->left(), t, mParentAxis->mAxisRect->right(), t));
8033 }
8034 }
8035}
8036
8037/*! \internal
8038
8039 Draws the sub grid lines with the specified painter.
8040
8041 This is a helper function called by \ref draw.
8042*/
8044{
8045 if (!mParentAxis) { qDebug() << Q_FUNC_INFO << "invalid parent axis"; return; }
8046
8047 applyAntialiasingHint(painter, mAntialiasedSubGrid, QCP::aeSubGrid);
8048 double t; // helper variable, result of coordinate-to-pixel transforms
8049 painter->setPen(mSubGridPen);
8050 if (mParentAxis->orientation() == Qt::Horizontal)
8051 {
8052 foreach (double tickCoord, mParentAxis->mSubTickVector)
8053 {
8054 t = mParentAxis->coordToPixel(tickCoord); // x
8055 painter->drawLine(QLineF(t, mParentAxis->mAxisRect->bottom(), t, mParentAxis->mAxisRect->top()));
8056 }
8057 } else
8058 {
8059 foreach (double tickCoord, mParentAxis->mSubTickVector)
8060 {
8061 t = mParentAxis->coordToPixel(tickCoord); // y
8062 painter->drawLine(QLineF(mParentAxis->mAxisRect->left(), t, mParentAxis->mAxisRect->right(), t));
8063 }
8064 }
8065}
8066
8067
8068////////////////////////////////////////////////////////////////////////////////////////////////////
8069//////////////////// QCPAxis
8070////////////////////////////////////////////////////////////////////////////////////////////////////
8071
8072/*! \class QCPAxis
8073 \brief Manages a single axis inside a QCustomPlot.
8074
8075 Usually doesn't need to be instantiated externally. Access %QCustomPlot's default four axes via
8076 QCustomPlot::xAxis (bottom), QCustomPlot::yAxis (left), QCustomPlot::xAxis2 (top) and
8077 QCustomPlot::yAxis2 (right).
8078
8079 Axes are always part of an axis rect, see QCPAxisRect.
8080 \image html AxisNamesOverview.png
8081 <center>Naming convention of axis parts</center>
8082 \n
8083
8084 \image html AxisRectSpacingOverview.png
8085 <center>Overview of the spacings and paddings that define the geometry of an axis. The dashed gray line
8086 on the left represents the QCustomPlot widget border.</center>
8087
8088 Each axis holds an instance of QCPAxisTicker which is used to generate the tick coordinates and
8089 tick labels. You can access the currently installed \ref ticker or set a new one (possibly one of
8090 the specialized subclasses, or your own subclass) via \ref setTicker. For details, see the
8091 documentation of QCPAxisTicker.
8092*/
8093
8094/* start of documentation of inline functions */
8095
8096/*! \fn Qt::Orientation QCPAxis::orientation() const
8097
8098 Returns the orientation of this axis. The axis orientation (horizontal or vertical) is deduced
8099 from the axis type (left, top, right or bottom).
8100
8101 \see orientation(AxisType type), pixelOrientation
8102*/
8103
8104/*! \fn QCPGrid *QCPAxis::grid() const
8105
8106 Returns the \ref QCPGrid instance belonging to this axis. Access it to set details about the way the
8107 grid is displayed.
8108*/
8109
8110/*! \fn static Qt::Orientation QCPAxis::orientation(AxisType type)
8111
8112 Returns the orientation of the specified axis type
8113
8114 \see orientation(), pixelOrientation
8115*/
8116
8117/*! \fn int QCPAxis::pixelOrientation() const
8118
8119 Returns which direction points towards higher coordinate values/keys, in pixel space.
8120
8121 This method returns either 1 or -1. If it returns 1, then going in the positive direction along
8122 the orientation of the axis in pixels corresponds to going from lower to higher axis coordinates.
8123 On the other hand, if this method returns -1, going to smaller pixel values corresponds to going
8124 from lower to higher axis coordinates.
8125
8126 For example, this is useful to easily shift axis coordinates by a certain amount given in pixels,
8127 without having to care about reversed or vertically aligned axes:
8128
8129 \code
8130 double newKey = keyAxis->pixelToCoord(keyAxis->coordToPixel(oldKey)+10*keyAxis->pixelOrientation());
8131 \endcode
8132
8133 \a newKey will then contain a key that is ten pixels towards higher keys, starting from \a oldKey.
8134*/
8135
8136/*! \fn QSharedPointer<QCPAxisTicker> QCPAxis::ticker() const
8137
8138 Returns a modifiable shared pointer to the currently installed axis ticker. The axis ticker is
8139 responsible for generating the tick positions and tick labels of this axis. You can access the
8140 \ref QCPAxisTicker with this method and modify basic properties such as the approximate tick count
8141 (\ref QCPAxisTicker::setTickCount).
8142
8143 You can gain more control over the axis ticks by setting a different \ref QCPAxisTicker subclass, see
8144 the documentation there. A new axis ticker can be set with \ref setTicker.
8145
8146 Since the ticker is stored in the axis as a shared pointer, multiple axes may share the same axis
8147 ticker simply by passing the same shared pointer to multiple axes.
8148
8149 \see setTicker
8150*/
8151
8152/* end of documentation of inline functions */
8153/* start of documentation of signals */
8154
8155/*! \fn void QCPAxis::rangeChanged(const QCPRange &newRange)
8156
8157 This signal is emitted when the range of this axis has changed. You can connect it to the \ref
8158 setRange slot of another axis to communicate the new range to the other axis, in order for it to
8159 be synchronized.
8160
8161 You may also manipulate/correct the range with \ref setRange in a slot connected to this signal.
8162 This is useful if for example a maximum range span shall not be exceeded, or if the lower/upper
8163 range shouldn't go beyond certain values (see \ref QCPRange::bounded). For example, the following
8164 slot would limit the x axis to ranges between 0 and 10:
8165 \code
8166 customPlot->xAxis->setRange(newRange.bounded(0, 10))
8167 \endcode
8168*/
8169
8170/*! \fn void QCPAxis::rangeChanged(const QCPRange &newRange, const QCPRange &oldRange)
8171 \overload
8172
8173 Additionally to the new range, this signal also provides the previous range held by the axis as
8174 \a oldRange.
8175*/
8176
8177/*! \fn void QCPAxis::scaleTypeChanged(QCPAxis::ScaleType scaleType);
8178
8179 This signal is emitted when the scale type changes, by calls to \ref setScaleType
8180*/
8181
8182/*! \fn void QCPAxis::selectionChanged(QCPAxis::SelectableParts selection)
8183
8184 This signal is emitted when the selection state of this axis has changed, either by user interaction
8185 or by a direct call to \ref setSelectedParts.
8186*/
8187
8188/*! \fn void QCPAxis::selectableChanged(const QCPAxis::SelectableParts &parts);
8189
8190 This signal is emitted when the selectability changes, by calls to \ref setSelectableParts
8191*/
8192
8193/* end of documentation of signals */
8194
8195/*!
8196 Constructs an Axis instance of Type \a type for the axis rect \a parent.
8197
8198 Usually it isn't necessary to instantiate axes directly, because you can let QCustomPlot create
8199 them for you with \ref QCPAxisRect::addAxis. If you want to use own QCPAxis-subclasses however,
8200 create them manually and then inject them also via \ref QCPAxisRect::addAxis.
8201*/
8203 QCPLayerable(parent->parentPlot(), QString(), parent),
8204 // axis base:
8205 mAxisType(type),
8206 mAxisRect(parent),
8207 mPadding(5),
8208 mOrientation(orientation(type)),
8209 mSelectableParts(spAxis | spTickLabels | spAxisLabel),
8210 mSelectedParts(spNone),
8211 mBasePen(QPen(Qt::black, 0, Qt::SolidLine, Qt::SquareCap)),
8212 mSelectedBasePen(QPen(Qt::blue, 2)),
8213 // axis label:
8214 mLabel(),
8215 mLabelFont(mParentPlot->font()),
8216 mSelectedLabelFont(QFont(mLabelFont.family(), mLabelFont.pointSize(), QFont::Bold)),
8217 mLabelColor(Qt::black),
8218 mSelectedLabelColor(Qt::blue),
8219 // tick labels:
8220 mTickLabels(true),
8221 mTickLabelFont(mParentPlot->font()),
8222 mSelectedTickLabelFont(QFont(mTickLabelFont.family(), mTickLabelFont.pointSize(), QFont::Bold)),
8223 mTickLabelColor(Qt::black),
8224 mSelectedTickLabelColor(Qt::blue),
8225 mNumberPrecision(6),
8226 mNumberFormatChar('g'),
8227 mNumberBeautifulPowers(true),
8228 // ticks and subticks:
8229 mTicks(true),
8230 mSubTicks(true),
8231 mTickPen(QPen(Qt::black, 0, Qt::SolidLine, Qt::SquareCap)),
8232 mSelectedTickPen(QPen(Qt::blue, 2)),
8233 mSubTickPen(QPen(Qt::black, 0, Qt::SolidLine, Qt::SquareCap)),
8234 mSelectedSubTickPen(QPen(Qt::blue, 2)),
8235 // scale and range:
8236 mRange(0, 5),
8237 mRangeReversed(false),
8238 mScaleType(stLinear),
8239 // internal members:
8240 mGrid(new QCPGrid(this)),
8241 mAxisPainter(new QCPAxisPainterPrivate(parent->parentPlot())),
8242 mTicker(new QCPAxisTicker),
8243 mCachedMarginValid(false),
8244 mCachedMargin(0),
8245 mDragging(false)
8246{
8248 mGrid->setVisible(false);
8249 setAntialiased(false);
8250 setLayer(mParentPlot->currentLayer()); // it's actually on that layer already, but we want it in front of the grid, so we place it on there again
8251
8252 if (type == atTop)
8253 {
8255 setLabelPadding(6);
8256 } else if (type == atRight)
8257 {
8259 setLabelPadding(12);
8260 } else if (type == atBottom)
8261 {
8263 setLabelPadding(3);
8264 } else if (type == atLeft)
8265 {
8267 setLabelPadding(10);
8268 }
8269}
8270
8271QCPAxis::~QCPAxis()
8272{
8273 delete mAxisPainter;
8274 delete mGrid; // delete grid here instead of via parent ~QObject for better defined deletion order
8275}
8276
8277/* No documentation as it is a property getter */
8278int QCPAxis::tickLabelPadding() const
8279{
8280 return mAxisPainter->tickLabelPadding;
8281}
8282
8283/* No documentation as it is a property getter */
8284double QCPAxis::tickLabelRotation() const
8285{
8286 return mAxisPainter->tickLabelRotation;
8287}
8288
8289/* No documentation as it is a property getter */
8290QCPAxis::LabelSide QCPAxis::tickLabelSide() const
8291{
8292 return mAxisPainter->tickLabelSide;
8293}
8294
8295/* No documentation as it is a property getter */
8296QString QCPAxis::numberFormat() const
8297{
8298 QString result;
8299 result.append(mNumberFormatChar);
8300 if (mNumberBeautifulPowers)
8301 {
8302 result.append(QLatin1Char('b'));
8303 if (mAxisPainter->numberMultiplyCross)
8304 result.append(QLatin1Char('c'));
8305 }
8306 return result;
8307}
8308
8309/* No documentation as it is a property getter */
8310int QCPAxis::tickLengthIn() const
8311{
8312 return mAxisPainter->tickLengthIn;
8313}
8314
8315/* No documentation as it is a property getter */
8316int QCPAxis::tickLengthOut() const
8317{
8318 return mAxisPainter->tickLengthOut;
8319}
8320
8321/* No documentation as it is a property getter */
8322int QCPAxis::subTickLengthIn() const
8323{
8324 return mAxisPainter->subTickLengthIn;
8325}
8326
8327/* No documentation as it is a property getter */
8328int QCPAxis::subTickLengthOut() const
8329{
8330 return mAxisPainter->subTickLengthOut;
8331}
8332
8333/* No documentation as it is a property getter */
8334int QCPAxis::labelPadding() const
8335{
8336 return mAxisPainter->labelPadding;
8337}
8338
8339/* No documentation as it is a property getter */
8340int QCPAxis::offset() const
8341{
8342 return mAxisPainter->offset;
8343}
8344
8345/* No documentation as it is a property getter */
8346QCPLineEnding QCPAxis::lowerEnding() const
8347{
8348 return mAxisPainter->lowerEnding;
8349}
8350
8351/* No documentation as it is a property getter */
8352QCPLineEnding QCPAxis::upperEnding() const
8353{
8354 return mAxisPainter->upperEnding;
8355}
8356
8357/*!
8358 Sets whether the axis uses a linear scale or a logarithmic scale.
8359
8360 Note that this method controls the coordinate transformation. For logarithmic scales, you will
8361 likely also want to use a logarithmic tick spacing and labeling, which can be achieved by setting
8362 the axis ticker to an instance of \ref QCPAxisTickerLog :
8363
8364 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpaxisticker-log-creation
8365
8366 See the documentation of \ref QCPAxisTickerLog about the details of logarithmic axis tick
8367 creation.
8368
8369 \ref setNumberPrecision
8370*/
8372{
8373 if (mScaleType != type)
8374 {
8375 mScaleType = type;
8376 if (mScaleType == stLogarithmic)
8378 mCachedMarginValid = false;
8379 emit scaleTypeChanged(mScaleType);
8380 }
8381}
8382
8383/*!
8384 Sets the range of the axis.
8385
8386 This slot may be connected with the \ref rangeChanged signal of another axis so this axis
8387 is always synchronized with the other axis range, when it changes.
8388
8389 To invert the direction of an axis, use \ref setRangeReversed.
8390*/
8391void QCPAxis::setRange(const QCPRange &range)
8392{
8393 if (range.lower == mRange.lower && range.upper == mRange.upper)
8394 return;
8395
8396 if (!QCPRange::validRange(range)) return;
8397 QCPRange oldRange = mRange;
8398 if (mScaleType == stLogarithmic)
8399 {
8400 mRange = range.sanitizedForLogScale();
8401 } else
8402 {
8403 mRange = range.sanitizedForLinScale();
8404 }
8405 emit rangeChanged(mRange);
8406 emit rangeChanged(mRange, oldRange);
8407}
8408
8409/*!
8410 Sets whether the user can (de-)select the parts in \a selectable by clicking on the QCustomPlot surface.
8411 (When \ref QCustomPlot::setInteractions contains iSelectAxes.)
8412
8413 However, even when \a selectable is set to a value not allowing the selection of a specific part,
8414 it is still possible to set the selection of this part manually, by calling \ref setSelectedParts
8415 directly.
8416
8417 \see SelectablePart, setSelectedParts
8418*/
8420{
8421 if (mSelectableParts != selectable)
8422 {
8423 mSelectableParts = selectable;
8424 emit selectableChanged(mSelectableParts);
8425 }
8426}
8427
8428/*!
8429 Sets the selected state of the respective axis parts described by \ref SelectablePart. When a part
8430 is selected, it uses a different pen/font.
8431
8432 The entire selection mechanism for axes is handled automatically when \ref
8433 QCustomPlot::setInteractions contains iSelectAxes. You only need to call this function when you
8434 wish to change the selection state manually.
8435
8436 This function can change the selection state of a part, independent of the \ref setSelectableParts setting.
8437
8438 emits the \ref selectionChanged signal when \a selected is different from the previous selection state.
8439
8440 \see SelectablePart, setSelectableParts, selectTest, setSelectedBasePen, setSelectedTickPen, setSelectedSubTickPen,
8441 setSelectedTickLabelFont, setSelectedLabelFont, setSelectedTickLabelColor, setSelectedLabelColor
8442*/
8444{
8445 if (mSelectedParts != selected)
8446 {
8447 mSelectedParts = selected;
8448 emit selectionChanged(mSelectedParts);
8449 }
8450}
8451
8452/*!
8453 \overload
8454
8455 Sets the lower and upper bound of the axis range.
8456
8457 To invert the direction of an axis, use \ref setRangeReversed.
8458
8459 There is also a slot to set a range, see \ref setRange(const QCPRange &range).
8460*/
8461void QCPAxis::setRange(double lower, double upper)
8462{
8463 if (lower == mRange.lower && upper == mRange.upper)
8464 return;
8465
8466 if (!QCPRange::validRange(lower, upper)) return;
8467 QCPRange oldRange = mRange;
8468 mRange.lower = lower;
8469 mRange.upper = upper;
8470 if (mScaleType == stLogarithmic)
8471 {
8472 mRange = mRange.sanitizedForLogScale();
8473 } else
8474 {
8475 mRange = mRange.sanitizedForLinScale();
8476 }
8477 emit rangeChanged(mRange);
8478 emit rangeChanged(mRange, oldRange);
8479}
8480
8481/*!
8482 \overload
8483
8484 Sets the range of the axis.
8485
8486 The \a position coordinate indicates together with the \a alignment parameter, where the new
8487 range will be positioned. \a size defines the size of the new axis range. \a alignment may be
8488 Qt::AlignLeft, Qt::AlignRight or Qt::AlignCenter. This will cause the left border, right border,
8489 or center of the range to be aligned with \a position. Any other values of \a alignment will
8490 default to Qt::AlignCenter.
8491*/
8492void QCPAxis::setRange(double position, double size, Qt::AlignmentFlag alignment)
8493{
8494 if (alignment == Qt::AlignLeft)
8495 setRange(position, position+size);
8496 else if (alignment == Qt::AlignRight)
8497 setRange(position-size, position);
8498 else // alignment == Qt::AlignCenter
8499 setRange(position-size/2.0, position+size/2.0);
8500}
8501
8502/*!
8503 Sets the lower bound of the axis range. The upper bound is not changed.
8504 \see setRange
8505*/
8506void QCPAxis::setRangeLower(double lower)
8507{
8508 if (mRange.lower == lower)
8509 return;
8510
8511 QCPRange oldRange = mRange;
8512 mRange.lower = lower;
8513 if (mScaleType == stLogarithmic)
8514 {
8515 mRange = mRange.sanitizedForLogScale();
8516 } else
8517 {
8518 mRange = mRange.sanitizedForLinScale();
8519 }
8520 emit rangeChanged(mRange);
8521 emit rangeChanged(mRange, oldRange);
8522}
8523
8524/*!
8525 Sets the upper bound of the axis range. The lower bound is not changed.
8526 \see setRange
8527*/
8528void QCPAxis::setRangeUpper(double upper)
8529{
8530 if (mRange.upper == upper)
8531 return;
8532
8533 QCPRange oldRange = mRange;
8534 mRange.upper = upper;
8535 if (mScaleType == stLogarithmic)
8536 {
8537 mRange = mRange.sanitizedForLogScale();
8538 } else
8539 {
8540 mRange = mRange.sanitizedForLinScale();
8541 }
8542 emit rangeChanged(mRange);
8543 emit rangeChanged(mRange, oldRange);
8544}
8545
8546/*!
8547 Sets whether the axis range (direction) is displayed reversed. Normally, the values on horizontal
8548 axes increase left to right, on vertical axes bottom to top. When \a reversed is set to true, the
8549 direction of increasing values is inverted.
8550
8551 Note that the range and data interface stays the same for reversed axes, e.g. the \a lower part
8552 of the \ref setRange interface will still reference the mathematically smaller number than the \a
8553 upper part.
8554*/
8555void QCPAxis::setRangeReversed(bool reversed)
8556{
8557 mRangeReversed = reversed;
8558}
8559
8560/*!
8561 The axis ticker is responsible for generating the tick positions and tick labels. See the
8562 documentation of QCPAxisTicker for details on how to work with axis tickers.
8563
8564 You can change the tick positioning/labeling behaviour of this axis by setting a different
8565 QCPAxisTicker subclass using this method. If you only wish to modify the currently installed axis
8566 ticker, access it via \ref ticker.
8567
8568 Since the ticker is stored in the axis as a shared pointer, multiple axes may share the same axis
8569 ticker simply by passing the same shared pointer to multiple axes.
8570
8571 \see ticker
8572*/
8574{
8575 if (ticker)
8576 mTicker = ticker;
8577 else
8578 qDebug() << Q_FUNC_INFO << "can not set nullptr as axis ticker";
8579 // no need to invalidate margin cache here because produced tick labels are checked for changes in setupTickVector
8580}
8581
8582/*!
8583 Sets whether tick marks are displayed.
8584
8585 Note that setting \a show to false does not imply that tick labels are invisible, too. To achieve
8586 that, see \ref setTickLabels.
8587
8588 \see setSubTicks
8589*/
8590void QCPAxis::setTicks(bool show)
8591{
8592 if (mTicks != show)
8593 {
8594 mTicks = show;
8595 mCachedMarginValid = false;
8596 }
8597}
8598
8599/*!
8600 Sets whether tick labels are displayed. Tick labels are the numbers drawn next to tick marks.
8601*/
8603{
8604 if (mTickLabels != show)
8605 {
8606 mTickLabels = show;
8607 mCachedMarginValid = false;
8608 if (!mTickLabels)
8609 mTickVectorLabels.clear();
8610 }
8611}
8612
8613/*!
8614 Sets the distance between the axis base line (including any outward ticks) and the tick labels.
8615 \see setLabelPadding, setPadding
8616*/
8618{
8619 if (mAxisPainter->tickLabelPadding != padding)
8620 {
8621 mAxisPainter->tickLabelPadding = padding;
8622 mCachedMarginValid = false;
8623 }
8624}
8625
8626/*!
8627 Sets the font of the tick labels.
8628
8629 \see setTickLabels, setTickLabelColor
8630*/
8632{
8633 if (font != mTickLabelFont)
8634 {
8635 mTickLabelFont = font;
8636 mCachedMarginValid = false;
8637 }
8638}
8639
8640/*!
8641 Sets the color of the tick labels.
8642
8643 \see setTickLabels, setTickLabelFont
8644*/
8646{
8647 mTickLabelColor = color;
8648}
8649
8650/*!
8651 Sets the rotation of the tick labels. If \a degrees is zero, the labels are drawn normally. Else,
8652 the tick labels are drawn rotated by \a degrees clockwise. The specified angle is bound to values
8653 from -90 to 90 degrees.
8654
8655 If \a degrees is exactly -90, 0 or 90, the tick labels are centered on the tick coordinate. For
8656 other angles, the label is drawn with an offset such that it seems to point toward or away from
8657 the tick mark.
8658*/
8660{
8661 if (!qFuzzyIsNull(degrees-mAxisPainter->tickLabelRotation))
8662 {
8663 mAxisPainter->tickLabelRotation = qBound(-90.0, degrees, 90.0);
8664 mCachedMarginValid = false;
8665 }
8666}
8667
8668/*!
8669 Sets whether the tick labels (numbers) shall appear inside or outside the axis rect.
8670
8671 The usual and default setting is \ref lsOutside. Very compact plots sometimes require tick labels
8672 to be inside the axis rect, to save space. If \a side is set to \ref lsInside, the tick labels
8673 appear on the inside are additionally clipped to the axis rect.
8674*/
8676{
8677 mAxisPainter->tickLabelSide = side;
8678 mCachedMarginValid = false;
8679}
8680
8681/*!
8682 Sets the number format for the numbers in tick labels. This \a formatCode is an extended version
8683 of the format code used e.g. by QString::number() and QLocale::toString(). For reference about
8684 that, see the "Argument Formats" section in the detailed description of the QString class.
8685
8686 \a formatCode is a string of one, two or three characters.
8687
8688 <b>The first character</b> is identical to
8689 the normal format code used by Qt. In short, this means: 'e'/'E' scientific format, 'f' fixed
8690 format, 'g'/'G' scientific or fixed, whichever is shorter. For the 'e', 'E', and 'f' formats,
8691 the precision set by \ref setNumberPrecision represents the number of digits after the decimal
8692 point. For the 'g' and 'G' formats, the precision represents the maximum number of significant
8693 digits, trailing zeroes are omitted.
8694
8695 <b>The second and third characters</b> are optional and specific to QCustomPlot:\n
8696 If the first char was 'e' or 'g', numbers are/might be displayed in the scientific format, e.g.
8697 "5.5e9", which is ugly in a plot. So when the second char of \a formatCode is set to 'b' (for
8698 "beautiful"), those exponential numbers are formatted in a more natural way, i.e. "5.5
8699 [multiplication sign] 10 [superscript] 9". By default, the multiplication sign is a centered dot.
8700 If instead a cross should be shown (as is usual in the USA), the third char of \a formatCode can
8701 be set to 'c'. The inserted multiplication signs are the UTF-8 characters 215 (0xD7) for the
8702 cross and 183 (0xB7) for the dot.
8703
8704 Examples for \a formatCode:
8705 \li \c g normal format code behaviour. If number is small, fixed format is used, if number is large,
8706 normal scientific format is used
8707 \li \c gb If number is small, fixed format is used, if number is large, scientific format is used with
8708 beautifully typeset decimal powers and a dot as multiplication sign
8709 \li \c ebc All numbers are in scientific format with beautifully typeset decimal power and a cross as
8710 multiplication sign
8711 \li \c fb illegal format code, since fixed format doesn't support (or need) beautifully typeset decimal
8712 powers. Format code will be reduced to 'f'.
8713 \li \c hello illegal format code, since first char is not 'e', 'E', 'f', 'g' or 'G'. Current format
8714 code will not be changed.
8715*/
8716void QCPAxis::setNumberFormat(const QString &formatCode)
8717{
8718 if (formatCode.isEmpty())
8719 {
8720 qDebug() << Q_FUNC_INFO << "Passed formatCode is empty";
8721 return;
8722 }
8723 mCachedMarginValid = false;
8724
8725 // interpret first char as number format char:
8726 QString allowedFormatChars(QLatin1String("eEfgG"));
8727 if (allowedFormatChars.contains(formatCode.at(0)))
8728 {
8729 mNumberFormatChar = QLatin1Char(formatCode.at(0).toLatin1());
8730 } else
8731 {
8732 qDebug() << Q_FUNC_INFO << "Invalid number format code (first char not in 'eEfgG'):" << formatCode;
8733 return;
8734 }
8735 if (formatCode.length() < 2)
8736 {
8737 mNumberBeautifulPowers = false;
8738 mAxisPainter->numberMultiplyCross = false;
8739 return;
8740 }
8741
8742 // interpret second char as indicator for beautiful decimal powers:
8743 if (formatCode.at(1) == QLatin1Char('b') && (mNumberFormatChar == QLatin1Char('e') || mNumberFormatChar == QLatin1Char('g')))
8744 {
8745 mNumberBeautifulPowers = true;
8746 } else
8747 {
8748 qDebug() << Q_FUNC_INFO << "Invalid number format code (second char not 'b' or first char neither 'e' nor 'g'):" << formatCode;
8749 return;
8750 }
8751 if (formatCode.length() < 3)
8752 {
8753 mAxisPainter->numberMultiplyCross = false;
8754 return;
8755 }
8756
8757 // interpret third char as indicator for dot or cross multiplication symbol:
8758 if (formatCode.at(2) == QLatin1Char('c'))
8759 {
8760 mAxisPainter->numberMultiplyCross = true;
8761 } else if (formatCode.at(2) == QLatin1Char('d'))
8762 {
8763 mAxisPainter->numberMultiplyCross = false;
8764 } else
8765 {
8766 qDebug() << Q_FUNC_INFO << "Invalid number format code (third char neither 'c' nor 'd'):" << formatCode;
8767 return;
8768 }
8769}
8770
8771/*!
8772 Sets the precision of the tick label numbers. See QLocale::toString(double i, char f, int prec)
8773 for details. The effect of precisions are most notably for number Formats starting with 'e', see
8774 \ref setNumberFormat
8775*/
8777{
8778 if (mNumberPrecision != precision)
8779 {
8780 mNumberPrecision = precision;
8781 mCachedMarginValid = false;
8782 }
8783}
8784
8785/*!
8786 Sets the length of the ticks in pixels. \a inside is the length the ticks will reach inside the
8787 plot and \a outside is the length they will reach outside the plot. If \a outside is greater than
8788 zero, the tick labels and axis label will increase their distance to the axis accordingly, so
8789 they won't collide with the ticks.
8790
8791 \see setSubTickLength, setTickLengthIn, setTickLengthOut
8792*/
8793void QCPAxis::setTickLength(int inside, int outside)
8794{
8795 setTickLengthIn(inside);
8796 setTickLengthOut(outside);
8797}
8798
8799/*!
8800 Sets the length of the inward ticks in pixels. \a inside is the length the ticks will reach
8801 inside the plot.
8802
8803 \see setTickLengthOut, setTickLength, setSubTickLength
8804*/
8806{
8807 if (mAxisPainter->tickLengthIn != inside)
8808 {
8809 mAxisPainter->tickLengthIn = inside;
8810 }
8811}
8812
8813/*!
8814 Sets the length of the outward ticks in pixels. \a outside is the length the ticks will reach
8815 outside the plot. If \a outside is greater than zero, the tick labels and axis label will
8816 increase their distance to the axis accordingly, so they won't collide with the ticks.
8817
8818 \see setTickLengthIn, setTickLength, setSubTickLength
8819*/
8821{
8822 if (mAxisPainter->tickLengthOut != outside)
8823 {
8824 mAxisPainter->tickLengthOut = outside;
8825 mCachedMarginValid = false; // only outside tick length can change margin
8826 }
8827}
8828
8829/*!
8830 Sets whether sub tick marks are displayed.
8831
8832 Sub ticks are only potentially visible if (major) ticks are also visible (see \ref setTicks)
8833
8834 \see setTicks
8835*/
8837{
8838 if (mSubTicks != show)
8839 {
8840 mSubTicks = show;
8841 mCachedMarginValid = false;
8842 }
8843}
8844
8845/*!
8846 Sets the length of the subticks in pixels. \a inside is the length the subticks will reach inside
8847 the plot and \a outside is the length they will reach outside the plot. If \a outside is greater
8848 than zero, the tick labels and axis label will increase their distance to the axis accordingly,
8849 so they won't collide with the ticks.
8850
8851 \see setTickLength, setSubTickLengthIn, setSubTickLengthOut
8852*/
8853void QCPAxis::setSubTickLength(int inside, int outside)
8854{
8855 setSubTickLengthIn(inside);
8856 setSubTickLengthOut(outside);
8857}
8858
8859/*!
8860 Sets the length of the inward subticks in pixels. \a inside is the length the subticks will reach inside
8861 the plot.
8862
8863 \see setSubTickLengthOut, setSubTickLength, setTickLength
8864*/
8866{
8867 if (mAxisPainter->subTickLengthIn != inside)
8868 {
8869 mAxisPainter->subTickLengthIn = inside;
8870 }
8871}
8872
8873/*!
8874 Sets the length of the outward subticks in pixels. \a outside is the length the subticks will reach
8875 outside the plot. If \a outside is greater than zero, the tick labels will increase their
8876 distance to the axis accordingly, so they won't collide with the ticks.
8877
8878 \see setSubTickLengthIn, setSubTickLength, setTickLength
8879*/
8881{
8882 if (mAxisPainter->subTickLengthOut != outside)
8883 {
8884 mAxisPainter->subTickLengthOut = outside;
8885 mCachedMarginValid = false; // only outside tick length can change margin
8886 }
8887}
8888
8889/*!
8890 Sets the pen, the axis base line is drawn with.
8891
8892 \see setTickPen, setSubTickPen
8893*/
8895{
8896 mBasePen = pen;
8897}
8898
8899/*!
8900 Sets the pen, tick marks will be drawn with.
8901
8902 \see setTickLength, setBasePen
8903*/
8905{
8906 mTickPen = pen;
8907}
8908
8909/*!
8910 Sets the pen, subtick marks will be drawn with.
8911
8912 \see setSubTickCount, setSubTickLength, setBasePen
8913*/
8915{
8916 mSubTickPen = pen;
8917}
8918
8919/*!
8920 Sets the font of the axis label.
8921
8922 \see setLabelColor
8923*/
8925{
8926 if (mLabelFont != font)
8927 {
8928 mLabelFont = font;
8929 mCachedMarginValid = false;
8930 }
8931}
8932
8933/*!
8934 Sets the color of the axis label.
8935
8936 \see setLabelFont
8937*/
8939{
8940 mLabelColor = color;
8941}
8942
8943/*!
8944 Sets the text of the axis label that will be shown below/above or next to the axis, depending on
8945 its orientation. To disable axis labels, pass an empty string as \a str.
8946*/
8948{
8949 if (mLabel != str)
8950 {
8951 mLabel = str;
8952 mCachedMarginValid = false;
8953 }
8954}
8955
8956/*!
8957 Sets the distance between the tick labels and the axis label.
8958
8959 \see setTickLabelPadding, setPadding
8960*/
8962{
8963 if (mAxisPainter->labelPadding != padding)
8964 {
8965 mAxisPainter->labelPadding = padding;
8966 mCachedMarginValid = false;
8967 }
8968}
8969
8970/*!
8971 Sets the padding of the axis.
8972
8973 When \ref QCPAxisRect::setAutoMargins is enabled, the padding is the additional outer most space,
8974 that is left blank.
8975
8976 The axis padding has no meaning if \ref QCPAxisRect::setAutoMargins is disabled.
8977
8978 \see setLabelPadding, setTickLabelPadding
8979*/
8980void QCPAxis::setPadding(int padding)
8981{
8982 if (mPadding != padding)
8983 {
8984 mPadding = padding;
8985 mCachedMarginValid = false;
8986 }
8987}
8988
8989/*!
8990 Sets the offset the axis has to its axis rect side.
8991
8992 If an axis rect side has multiple axes and automatic margin calculation is enabled for that side,
8993 only the offset of the inner most axis has meaning (even if it is set to be invisible). The
8994 offset of the other, outer axes is controlled automatically, to place them at appropriate
8995 positions.
8996*/
8997void QCPAxis::setOffset(int offset)
8998{
8999 mAxisPainter->offset = offset;
9000}
9001
9002/*!
9003 Sets the font that is used for tick labels when they are selected.
9004
9005 \see setTickLabelFont, setSelectableParts, setSelectedParts, QCustomPlot::setInteractions
9006*/
9008{
9009 if (font != mSelectedTickLabelFont)
9010 {
9011 mSelectedTickLabelFont = font;
9012 // don't set mCachedMarginValid to false here because margin calculation is always done with non-selected fonts
9013 }
9014}
9015
9016/*!
9017 Sets the font that is used for the axis label when it is selected.
9018
9019 \see setLabelFont, setSelectableParts, setSelectedParts, QCustomPlot::setInteractions
9020*/
9022{
9023 mSelectedLabelFont = font;
9024 // don't set mCachedMarginValid to false here because margin calculation is always done with non-selected fonts
9025}
9026
9027/*!
9028 Sets the color that is used for tick labels when they are selected.
9029
9030 \see setTickLabelColor, setSelectableParts, setSelectedParts, QCustomPlot::setInteractions
9031*/
9033{
9034 if (color != mSelectedTickLabelColor)
9035 {
9036 mSelectedTickLabelColor = color;
9037 }
9038}
9039
9040/*!
9041 Sets the color that is used for the axis label when it is selected.
9042
9043 \see setLabelColor, setSelectableParts, setSelectedParts, QCustomPlot::setInteractions
9044*/
9046{
9047 mSelectedLabelColor = color;
9048}
9049
9050/*!
9051 Sets the pen that is used to draw the axis base line when selected.
9052
9053 \see setBasePen, setSelectableParts, setSelectedParts, QCustomPlot::setInteractions
9054*/
9056{
9057 mSelectedBasePen = pen;
9058}
9059
9060/*!
9061 Sets the pen that is used to draw the (major) ticks when selected.
9062
9063 \see setTickPen, setSelectableParts, setSelectedParts, QCustomPlot::setInteractions
9064*/
9066{
9067 mSelectedTickPen = pen;
9068}
9069
9070/*!
9071 Sets the pen that is used to draw the subticks when selected.
9072
9073 \see setSubTickPen, setSelectableParts, setSelectedParts, QCustomPlot::setInteractions
9074*/
9076{
9077 mSelectedSubTickPen = pen;
9078}
9079
9080/*!
9081 Sets the style for the lower axis ending. See the documentation of QCPLineEnding for available
9082 styles.
9083
9084 For horizontal axes, this method refers to the left ending, for vertical axes the bottom ending.
9085 Note that this meaning does not change when the axis range is reversed with \ref
9086 setRangeReversed.
9087
9088 \see setUpperEnding
9089*/
9091{
9092 mAxisPainter->lowerEnding = ending;
9093}
9094
9095/*!
9096 Sets the style for the upper axis ending. See the documentation of QCPLineEnding for available
9097 styles.
9098
9099 For horizontal axes, this method refers to the right ending, for vertical axes the top ending.
9100 Note that this meaning does not change when the axis range is reversed with \ref
9101 setRangeReversed.
9102
9103 \see setLowerEnding
9104*/
9106{
9107 mAxisPainter->upperEnding = ending;
9108}
9109
9110/*!
9111 If the scale type (\ref setScaleType) is \ref stLinear, \a diff is added to the lower and upper
9112 bounds of the range. The range is simply moved by \a diff.
9113
9114 If the scale type is \ref stLogarithmic, the range bounds are multiplied by \a diff. This
9115 corresponds to an apparent "linear" move in logarithmic scaling by a distance of log(diff).
9116*/
9117void QCPAxis::moveRange(double diff)
9118{
9119 QCPRange oldRange = mRange;
9120 if (mScaleType == stLinear)
9121 {
9122 mRange.lower += diff;
9123 mRange.upper += diff;
9124 } else // mScaleType == stLogarithmic
9125 {
9126 mRange.lower *= diff;
9127 mRange.upper *= diff;
9128 }
9129 emit rangeChanged(mRange);
9130 emit rangeChanged(mRange, oldRange);
9131}
9132
9133/*!
9134 Scales the range of this axis by \a factor around the center of the current axis range. For
9135 example, if \a factor is 2.0, then the axis range will double its size, and the point at the axis
9136 range center won't have changed its position in the QCustomPlot widget (i.e. coordinates around
9137 the center will have moved symmetrically closer).
9138
9139 If you wish to scale around a different coordinate than the current axis range center, use the
9140 overload \ref scaleRange(double factor, double center).
9141*/
9142void QCPAxis::scaleRange(double factor)
9143{
9144 scaleRange(factor, range().center());
9145}
9146
9147/*! \overload
9148
9149 Scales the range of this axis by \a factor around the coordinate \a center. For example, if \a
9150 factor is 2.0, \a center is 1.0, then the axis range will double its size, and the point at
9151 coordinate 1.0 won't have changed its position in the QCustomPlot widget (i.e. coordinates
9152 around 1.0 will have moved symmetrically closer to 1.0).
9153
9154 \see scaleRange(double factor)
9155*/
9156void QCPAxis::scaleRange(double factor, double center)
9157{
9158 QCPRange oldRange = mRange;
9159 if (mScaleType == stLinear)
9160 {
9161 QCPRange newRange;
9162 newRange.lower = (mRange.lower-center)*factor + center;
9163 newRange.upper = (mRange.upper-center)*factor + center;
9164 if (QCPRange::validRange(newRange))
9165 mRange = newRange.sanitizedForLinScale();
9166 } else // mScaleType == stLogarithmic
9167 {
9168 if ((mRange.upper < 0 && center < 0) || (mRange.upper > 0 && center > 0)) // make sure center has same sign as range
9169 {
9170 QCPRange newRange;
9171 newRange.lower = qPow(mRange.lower/center, factor)*center;
9172 newRange.upper = qPow(mRange.upper/center, factor)*center;
9173 if (QCPRange::validRange(newRange))
9174 mRange = newRange.sanitizedForLogScale();
9175 } else
9176 qDebug() << Q_FUNC_INFO << "Center of scaling operation doesn't lie in same logarithmic sign domain as range:" << center;
9177 }
9178 emit rangeChanged(mRange);
9179 emit rangeChanged(mRange, oldRange);
9180}
9181
9182/*!
9183 Scales the range of this axis to have a certain scale \a ratio to \a otherAxis. The scaling will
9184 be done around the center of the current axis range.
9185
9186 For example, if \a ratio is 1, this axis is the \a yAxis and \a otherAxis is \a xAxis, graphs
9187 plotted with those axes will appear in a 1:1 aspect ratio, independent of the aspect ratio the
9188 axis rect has.
9189
9190 This is an operation that changes the range of this axis once, it doesn't fix the scale ratio
9191 indefinitely. Note that calling this function in the constructor of the QCustomPlot's parent
9192 won't have the desired effect, since the widget dimensions aren't defined yet, and a resizeEvent
9193 will follow.
9194*/
9195void QCPAxis::setScaleRatio(const QCPAxis *otherAxis, double ratio)
9196{
9197 int otherPixelSize, ownPixelSize;
9198
9199 if (otherAxis->orientation() == Qt::Horizontal)
9200 otherPixelSize = otherAxis->axisRect()->width();
9201 else
9202 otherPixelSize = otherAxis->axisRect()->height();
9203
9204 if (orientation() == Qt::Horizontal)
9205 ownPixelSize = axisRect()->width();
9206 else
9207 ownPixelSize = axisRect()->height();
9208
9209 double newRangeSize = ratio*otherAxis->range().size()*ownPixelSize/double(otherPixelSize);
9210 setRange(range().center(), newRangeSize, Qt::AlignCenter);
9211}
9212
9213/*!
9214 Changes the axis range such that all plottables associated with this axis are fully visible in
9215 that dimension.
9216
9217 \see QCPAbstractPlottable::rescaleAxes, QCustomPlot::rescaleAxes
9218*/
9219void QCPAxis::rescale(bool onlyVisiblePlottables)
9220{
9221 QCPRange newRange;
9222 bool haveRange = false;
9223 foreach (QCPAbstractPlottable *plottable, plottables())
9224 {
9225 if (!plottable->realVisibility() && onlyVisiblePlottables)
9226 continue;
9227 QCPRange plottableRange;
9228 bool currentFoundRange;
9229 QCP::SignDomain signDomain = QCP::sdBoth;
9230 if (mScaleType == stLogarithmic)
9231 signDomain = (mRange.upper < 0 ? QCP::sdNegative : QCP::sdPositive);
9232 if (plottable->keyAxis() == this)
9233 plottableRange = plottable->getKeyRange(currentFoundRange, signDomain);
9234 else
9235 plottableRange = plottable->getValueRange(currentFoundRange, signDomain);
9236 if (currentFoundRange)
9237 {
9238 if (!haveRange)
9239 newRange = plottableRange;
9240 else
9241 newRange.expand(plottableRange);
9242 haveRange = true;
9243 }
9244 }
9245 if (haveRange)
9246 {
9247 if (!QCPRange::validRange(newRange)) // likely due to range being zero (plottable has only constant data in this axis dimension), shift current range to at least center the plottable
9248 {
9249 double center = (newRange.lower+newRange.upper)*0.5; // upper and lower should be equal anyway, but just to make sure, incase validRange returned false for other reason
9250 if (mScaleType == stLinear)
9251 {
9252 newRange.lower = center-mRange.size()/2.0;
9253 newRange.upper = center+mRange.size()/2.0;
9254 } else // mScaleType == stLogarithmic
9255 {
9256 newRange.lower = center/qSqrt(mRange.upper/mRange.lower);
9257 newRange.upper = center*qSqrt(mRange.upper/mRange.lower);
9258 }
9259 }
9260 setRange(newRange);
9261 }
9262}
9263
9264/*!
9265 Transforms \a value, in pixel coordinates of the QCustomPlot widget, to axis coordinates.
9266*/
9267double QCPAxis::pixelToCoord(double value) const
9268{
9269 if (orientation() == Qt::Horizontal)
9270 {
9271 if (mScaleType == stLinear)
9272 {
9273 if (!mRangeReversed)
9274 return (value-mAxisRect->left())/double(mAxisRect->width())*mRange.size()+mRange.lower;
9275 else
9276 return -(value-mAxisRect->left())/double(mAxisRect->width())*mRange.size()+mRange.upper;
9277 } else // mScaleType == stLogarithmic
9278 {
9279 if (!mRangeReversed)
9280 return qPow(mRange.upper/mRange.lower, (value-mAxisRect->left())/double(mAxisRect->width()))*mRange.lower;
9281 else
9282 return qPow(mRange.upper/mRange.lower, (mAxisRect->left()-value)/double(mAxisRect->width()))*mRange.upper;
9283 }
9284 } else // orientation() == Qt::Vertical
9285 {
9286 if (mScaleType == stLinear)
9287 {
9288 if (!mRangeReversed)
9289 return (mAxisRect->bottom()-value)/double(mAxisRect->height())*mRange.size()+mRange.lower;
9290 else
9291 return -(mAxisRect->bottom()-value)/double(mAxisRect->height())*mRange.size()+mRange.upper;
9292 } else // mScaleType == stLogarithmic
9293 {
9294 if (!mRangeReversed)
9295 return qPow(mRange.upper/mRange.lower, (mAxisRect->bottom()-value)/double(mAxisRect->height()))*mRange.lower;
9296 else
9297 return qPow(mRange.upper/mRange.lower, (value-mAxisRect->bottom())/double(mAxisRect->height()))*mRange.upper;
9298 }
9299 }
9300}
9301
9302/*!
9303 Transforms \a value, in coordinates of the axis, to pixel coordinates of the QCustomPlot widget.
9304*/
9305double QCPAxis::coordToPixel(double value) const
9306{
9307 if (orientation() == Qt::Horizontal)
9308 {
9309 if (mScaleType == stLinear)
9310 {
9311 if (!mRangeReversed)
9312 return (value-mRange.lower)/mRange.size()*mAxisRect->width()+mAxisRect->left();
9313 else
9314 return (mRange.upper-value)/mRange.size()*mAxisRect->width()+mAxisRect->left();
9315 } else // mScaleType == stLogarithmic
9316 {
9317 if (value >= 0.0 && mRange.upper < 0.0) // invalid value for logarithmic scale, just draw it outside visible range
9318 return !mRangeReversed ? mAxisRect->right()+200 : mAxisRect->left()-200;
9319 else if (value <= 0.0 && mRange.upper >= 0.0) // invalid value for logarithmic scale, just draw it outside visible range
9320 return !mRangeReversed ? mAxisRect->left()-200 : mAxisRect->right()+200;
9321 else
9322 {
9323 if (!mRangeReversed)
9324 return qLn(value/mRange.lower)/qLn(mRange.upper/mRange.lower)*mAxisRect->width()+mAxisRect->left();
9325 else
9326 return qLn(mRange.upper/value)/qLn(mRange.upper/mRange.lower)*mAxisRect->width()+mAxisRect->left();
9327 }
9328 }
9329 } else // orientation() == Qt::Vertical
9330 {
9331 if (mScaleType == stLinear)
9332 {
9333 if (!mRangeReversed)
9334 return mAxisRect->bottom()-(value-mRange.lower)/mRange.size()*mAxisRect->height();
9335 else
9336 return mAxisRect->bottom()-(mRange.upper-value)/mRange.size()*mAxisRect->height();
9337 } else // mScaleType == stLogarithmic
9338 {
9339 if (value >= 0.0 && mRange.upper < 0.0) // invalid value for logarithmic scale, just draw it outside visible range
9340 return !mRangeReversed ? mAxisRect->top()-200 : mAxisRect->bottom()+200;
9341 else if (value <= 0.0 && mRange.upper >= 0.0) // invalid value for logarithmic scale, just draw it outside visible range
9342 return !mRangeReversed ? mAxisRect->bottom()+200 : mAxisRect->top()-200;
9343 else
9344 {
9345 if (!mRangeReversed)
9346 return mAxisRect->bottom()-qLn(value/mRange.lower)/qLn(mRange.upper/mRange.lower)*mAxisRect->height();
9347 else
9348 return mAxisRect->bottom()-qLn(mRange.upper/value)/qLn(mRange.upper/mRange.lower)*mAxisRect->height();
9349 }
9350 }
9351 }
9352}
9353
9354/*!
9355 Returns the part of the axis that is hit by \a pos (in pixels). The return value of this function
9356 is independent of the user-selectable parts defined with \ref setSelectableParts. Further, this
9357 function does not change the current selection state of the axis.
9358
9359 If the axis is not visible (\ref setVisible), this function always returns \ref spNone.
9360
9361 \see setSelectedParts, setSelectableParts, QCustomPlot::setInteractions
9362*/
9364{
9365 if (!mVisible)
9366 return spNone;
9367
9368 if (mAxisPainter->axisSelectionBox().contains(pos.toPoint()))
9369 return spAxis;
9370 else if (mAxisPainter->tickLabelsSelectionBox().contains(pos.toPoint()))
9371 return spTickLabels;
9372 else if (mAxisPainter->labelSelectionBox().contains(pos.toPoint()))
9373 return spAxisLabel;
9374 else
9375 return spNone;
9376}
9377
9378/* inherits documentation from base class */
9379double QCPAxis::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
9380{
9381 if (!mParentPlot) return -1;
9382 SelectablePart part = getPartAt(pos);
9383 if ((onlySelectable && !mSelectableParts.testFlag(part)) || part == spNone)
9384 return -1;
9385
9386 if (details)
9387 details->setValue(part);
9388 return mParentPlot->selectionTolerance()*0.99;
9389}
9390
9391/*!
9392 Returns a list of all the plottables that have this axis as key or value axis.
9393
9394 If you are only interested in plottables of type QCPGraph, see \ref graphs.
9395
9396 \see graphs, items
9397*/
9399{
9401 if (!mParentPlot) return result;
9402
9403 foreach (QCPAbstractPlottable *plottable, mParentPlot->mPlottables)
9404 {
9405 if (plottable->keyAxis() == this || plottable->valueAxis() == this)
9406 result.append(plottable);
9407 }
9408 return result;
9409}
9410
9411/*!
9412 Returns a list of all the graphs that have this axis as key or value axis.
9413
9414 \see plottables, items
9415*/
9417{
9418 QList<QCPGraph*> result;
9419 if (!mParentPlot) return result;
9420
9421 foreach (QCPGraph *graph, mParentPlot->mGraphs)
9422 {
9423 if (graph->keyAxis() == this || graph->valueAxis() == this)
9424 result.append(graph);
9425 }
9426 return result;
9427}
9428
9429/*!
9430 Returns a list of all the items that are associated with this axis. An item is considered
9431 associated with an axis if at least one of its positions uses the axis as key or value axis.
9432
9433 \see plottables, graphs
9434*/
9436{
9438 if (!mParentPlot) return result;
9439
9440 foreach (QCPAbstractItem *item, mParentPlot->mItems)
9441 {
9442 foreach (QCPItemPosition *position, item->positions())
9443 {
9444 if (position->keyAxis() == this || position->valueAxis() == this)
9445 {
9446 result.append(item);
9447 break;
9448 }
9449 }
9450 }
9451 return result;
9452}
9453
9454/*!
9455 Transforms a margin side to the logically corresponding axis type. (QCP::msLeft to
9456 QCPAxis::atLeft, QCP::msRight to QCPAxis::atRight, etc.)
9457*/
9459{
9460 switch (side)
9461 {
9462 case QCP::msLeft: return atLeft;
9463 case QCP::msRight: return atRight;
9464 case QCP::msTop: return atTop;
9465 case QCP::msBottom: return atBottom;
9466 default: break;
9467 }
9468 qDebug() << Q_FUNC_INFO << "Invalid margin side passed:" << static_cast<int>(side);
9469 return atLeft;
9470}
9471
9472/*!
9473 Returns the axis type that describes the opposite axis of an axis with the specified \a type.
9474*/
9476{
9477 switch (type)
9478 {
9479 case atLeft: return atRight;
9480 case atRight: return atLeft;
9481 case atBottom: return atTop;
9482 case atTop: return atBottom;
9483 }
9484 qDebug() << Q_FUNC_INFO << "invalid axis type";
9485 return atLeft;
9486}
9487
9488/* inherits documentation from base class */
9489void QCPAxis::selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged)
9490{
9491 Q_UNUSED(event)
9492 SelectablePart part = details.value<SelectablePart>();
9493 if (mSelectableParts.testFlag(part))
9494 {
9495 SelectableParts selBefore = mSelectedParts;
9496 setSelectedParts(additive ? mSelectedParts^part : part);
9497 if (selectionStateChanged)
9498 *selectionStateChanged = mSelectedParts != selBefore;
9499 }
9500}
9501
9502/* inherits documentation from base class */
9503void QCPAxis::deselectEvent(bool *selectionStateChanged)
9504{
9505 SelectableParts selBefore = mSelectedParts;
9506 setSelectedParts(mSelectedParts & ~mSelectableParts);
9507 if (selectionStateChanged)
9508 *selectionStateChanged = mSelectedParts != selBefore;
9509}
9510
9511/*! \internal
9512
9513 This mouse event reimplementation provides the functionality to let the user drag individual axes
9514 exclusively, by startig the drag on top of the axis.
9515
9516 For the axis to accept this event and perform the single axis drag, the parent \ref QCPAxisRect
9517 must be configured accordingly, i.e. it must allow range dragging in the orientation of this axis
9518 (\ref QCPAxisRect::setRangeDrag) and this axis must be a draggable axis (\ref
9519 QCPAxisRect::setRangeDragAxes)
9520
9521 \seebaseclassmethod
9522
9523 \note The dragging of possibly multiple axes at once by starting the drag anywhere in the axis
9524 rect is handled by the axis rect's mouse event, e.g. \ref QCPAxisRect::mousePressEvent.
9525*/
9527{
9528 Q_UNUSED(details)
9529 if (!mParentPlot->interactions().testFlag(QCP::iRangeDrag) ||
9530 !mAxisRect->rangeDrag().testFlag(orientation()) ||
9531 !mAxisRect->rangeDragAxes(orientation()).contains(this))
9532 {
9533 event->ignore();
9534 return;
9535 }
9536
9537 if (event->buttons() & Qt::LeftButton)
9538 {
9539 mDragging = true;
9540 // initialize antialiasing backup in case we start dragging:
9541 if (mParentPlot->noAntialiasingOnDrag())
9542 {
9543 mAADragBackup = mParentPlot->antialiasedElements();
9544 mNotAADragBackup = mParentPlot->notAntialiasedElements();
9545 }
9546 // Mouse range dragging interaction:
9547 if (mParentPlot->interactions().testFlag(QCP::iRangeDrag))
9548 mDragStartRange = mRange;
9549 }
9550}
9551
9552/*! \internal
9553
9554 This mouse event reimplementation provides the functionality to let the user drag individual axes
9555 exclusively, by startig the drag on top of the axis.
9556
9557 \seebaseclassmethod
9558
9559 \note The dragging of possibly multiple axes at once by starting the drag anywhere in the axis
9560 rect is handled by the axis rect's mouse event, e.g. \ref QCPAxisRect::mousePressEvent.
9561
9562 \see QCPAxis::mousePressEvent
9563*/
9564void QCPAxis::mouseMoveEvent(QMouseEvent *event, const QPointF &startPos)
9565{
9566 if (mDragging)
9567 {
9568 const double startPixel = orientation() == Qt::Horizontal ? startPos.x() : startPos.y();
9569 const double currentPixel = orientation() == Qt::Horizontal ? event->pos().x() : event->pos().y();
9570 if (mScaleType == QCPAxis::stLinear)
9571 {
9572 const double diff = pixelToCoord(startPixel) - pixelToCoord(currentPixel);
9573 setRange(mDragStartRange.lower+diff, mDragStartRange.upper+diff);
9574 } else if (mScaleType == QCPAxis::stLogarithmic)
9575 {
9576 const double diff = pixelToCoord(startPixel) / pixelToCoord(currentPixel);
9577 setRange(mDragStartRange.lower*diff, mDragStartRange.upper*diff);
9578 }
9579
9580 if (mParentPlot->noAntialiasingOnDrag())
9582 mParentPlot->replot(QCustomPlot::rpQueuedReplot);
9583 }
9584}
9585
9586/*! \internal
9587
9588 This mouse event reimplementation provides the functionality to let the user drag individual axes
9589 exclusively, by startig the drag on top of the axis.
9590
9591 \seebaseclassmethod
9592
9593 \note The dragging of possibly multiple axes at once by starting the drag anywhere in the axis
9594 rect is handled by the axis rect's mouse event, e.g. \ref QCPAxisRect::mousePressEvent.
9595
9596 \see QCPAxis::mousePressEvent
9597*/
9599{
9600 Q_UNUSED(event)
9601 Q_UNUSED(startPos)
9602 mDragging = false;
9603 if (mParentPlot->noAntialiasingOnDrag())
9604 {
9605 mParentPlot->setAntialiasedElements(mAADragBackup);
9606 mParentPlot->setNotAntialiasedElements(mNotAADragBackup);
9607 }
9608}
9609
9610/*! \internal
9611
9612 This mouse event reimplementation provides the functionality to let the user zoom individual axes
9613 exclusively, by performing the wheel event on top of the axis.
9614
9615 For the axis to accept this event and perform the single axis zoom, the parent \ref QCPAxisRect
9616 must be configured accordingly, i.e. it must allow range zooming in the orientation of this axis
9617 (\ref QCPAxisRect::setRangeZoom) and this axis must be a zoomable axis (\ref
9618 QCPAxisRect::setRangeZoomAxes)
9619
9620 \seebaseclassmethod
9621
9622 \note The zooming of possibly multiple axes at once by performing the wheel event anywhere in the
9623 axis rect is handled by the axis rect's mouse event, e.g. \ref QCPAxisRect::wheelEvent.
9624*/
9626{
9627 // Mouse range zooming interaction:
9628 if (!mParentPlot->interactions().testFlag(QCP::iRangeZoom) ||
9629 !mAxisRect->rangeZoom().testFlag(orientation()) ||
9630 !mAxisRect->rangeZoomAxes(orientation()).contains(this))
9631 {
9632 event->ignore();
9633 return;
9634 }
9635
9636#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
9637 const double delta = event->delta();
9638#else
9639 const double delta = event->angleDelta().y();
9640#endif
9641
9642#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
9643 const QPointF pos = event->pos();
9644#else
9645 const QPointF pos = event->position();
9646#endif
9647
9648 const double wheelSteps = delta/120.0; // a single step delta is +/-120 usually
9649 const double factor = qPow(mAxisRect->rangeZoomFactor(orientation()), wheelSteps);
9650 scaleRange(factor, pixelToCoord(orientation() == Qt::Horizontal ? pos.x() : pos.y()));
9651 mParentPlot->replot();
9652}
9653
9654/*! \internal
9655
9656 A convenience function to easily set the QPainter::Antialiased hint on the provided \a painter
9657 before drawing axis lines.
9658
9659 This is the antialiasing state the painter passed to the \ref draw method is in by default.
9660
9661 This function takes into account the local setting of the antialiasing flag as well as the
9662 overrides set with \ref QCustomPlot::setAntialiasedElements and \ref
9663 QCustomPlot::setNotAntialiasedElements.
9664
9665 \seebaseclassmethod
9666
9667 \see setAntialiased
9668*/
9670{
9671 applyAntialiasingHint(painter, mAntialiased, QCP::aeAxes);
9672}
9673
9674/*! \internal
9675
9676 Draws the axis with the specified \a painter, using the internal QCPAxisPainterPrivate instance.
9677
9678 \seebaseclassmethod
9679*/
9681{
9682 QVector<double> subTickPositions; // the final coordToPixel transformed vector passed to QCPAxisPainter
9683 QVector<double> tickPositions; // the final coordToPixel transformed vector passed to QCPAxisPainter
9684 QVector<QString> tickLabels; // the final vector passed to QCPAxisPainter
9685 tickPositions.reserve(mTickVector.size());
9686 tickLabels.reserve(mTickVector.size());
9687 subTickPositions.reserve(mSubTickVector.size());
9688
9689 if (mTicks)
9690 {
9691 for (int i=0; i<mTickVector.size(); ++i)
9692 {
9693 tickPositions.append(coordToPixel(mTickVector.at(i)));
9694 if (mTickLabels)
9695 tickLabels.append(mTickVectorLabels.at(i));
9696 }
9697
9698 if (mSubTicks)
9699 {
9700 const int subTickCount = mSubTickVector.size();
9701 for (int i=0; i<subTickCount; ++i)
9702 subTickPositions.append(coordToPixel(mSubTickVector.at(i)));
9703 }
9704 }
9705
9706 // transfer all properties of this axis to QCPAxisPainterPrivate which it needs to draw the axis.
9707 // Note that some axis painter properties are already set by direct feed-through with QCPAxis setters
9708 mAxisPainter->type = mAxisType;
9709 mAxisPainter->basePen = getBasePen();
9710 mAxisPainter->labelFont = getLabelFont();
9711 mAxisPainter->labelColor = getLabelColor();
9712 mAxisPainter->label = mLabel;
9713 mAxisPainter->substituteExponent = mNumberBeautifulPowers;
9714 mAxisPainter->tickPen = getTickPen();
9715 mAxisPainter->subTickPen = getSubTickPen();
9716 mAxisPainter->tickLabelFont = getTickLabelFont();
9717 mAxisPainter->tickLabelColor = getTickLabelColor();
9718 mAxisPainter->axisRect = mAxisRect->rect();
9719 mAxisPainter->viewportRect = mParentPlot->viewport();
9720 mAxisPainter->abbreviateDecimalPowers = mScaleType == stLogarithmic;
9721 mAxisPainter->reversedEndings = mRangeReversed;
9722 mAxisPainter->tickPositions = tickPositions;
9723 mAxisPainter->tickLabels = tickLabels;
9724 mAxisPainter->subTickPositions = subTickPositions;
9725 mAxisPainter->draw(painter);
9726}
9727
9728/*! \internal
9729
9730 Prepares the internal tick vector, sub tick vector and tick label vector. This is done by calling
9731 QCPAxisTicker::generate on the currently installed ticker.
9732
9733 If a change in the label text/count is detected, the cached axis margin is invalidated to make
9734 sure the next margin calculation recalculates the label sizes and returns an up-to-date value.
9735*/
9737{
9738 if (!mParentPlot) return;
9739 if ((!mTicks && !mTickLabels && !mGrid->visible()) || mRange.size() <= 0) return;
9740
9741 QVector<QString> oldLabels = mTickVectorLabels;
9742 mTicker->generate(mRange, mParentPlot->locale(), mNumberFormatChar, mNumberPrecision, mTickVector, mSubTicks ? &mSubTickVector : nullptr, mTickLabels ? &mTickVectorLabels : nullptr);
9743 mCachedMarginValid &= mTickVectorLabels == oldLabels; // if labels have changed, margin might have changed, too
9744}
9745
9746/*! \internal
9747
9748 Returns the pen that is used to draw the axis base line. Depending on the selection state, this
9749 is either mSelectedBasePen or mBasePen.
9750*/
9752{
9753 return mSelectedParts.testFlag(spAxis) ? mSelectedBasePen : mBasePen;
9754}
9755
9756/*! \internal
9757
9758 Returns the pen that is used to draw the (major) ticks. Depending on the selection state, this
9759 is either mSelectedTickPen or mTickPen.
9760*/
9762{
9763 return mSelectedParts.testFlag(spAxis) ? mSelectedTickPen : mTickPen;
9764}
9765
9766/*! \internal
9767
9768 Returns the pen that is used to draw the subticks. Depending on the selection state, this
9769 is either mSelectedSubTickPen or mSubTickPen.
9770*/
9772{
9773 return mSelectedParts.testFlag(spAxis) ? mSelectedSubTickPen : mSubTickPen;
9774}
9775
9776/*! \internal
9777
9778 Returns the font that is used to draw the tick labels. Depending on the selection state, this
9779 is either mSelectedTickLabelFont or mTickLabelFont.
9780*/
9782{
9783 return mSelectedParts.testFlag(spTickLabels) ? mSelectedTickLabelFont : mTickLabelFont;
9784}
9785
9786/*! \internal
9787
9788 Returns the font that is used to draw the axis label. Depending on the selection state, this
9789 is either mSelectedLabelFont or mLabelFont.
9790*/
9792{
9793 return mSelectedParts.testFlag(spAxisLabel) ? mSelectedLabelFont : mLabelFont;
9794}
9795
9796/*! \internal
9797
9798 Returns the color that is used to draw the tick labels. Depending on the selection state, this
9799 is either mSelectedTickLabelColor or mTickLabelColor.
9800*/
9802{
9803 return mSelectedParts.testFlag(spTickLabels) ? mSelectedTickLabelColor : mTickLabelColor;
9804}
9805
9806/*! \internal
9807
9808 Returns the color that is used to draw the axis label. Depending on the selection state, this
9809 is either mSelectedLabelColor or mLabelColor.
9810*/
9812{
9813 return mSelectedParts.testFlag(spAxisLabel) ? mSelectedLabelColor : mLabelColor;
9814}
9815
9816/*! \internal
9817
9818 Returns the appropriate outward margin for this axis. It is needed if \ref
9819 QCPAxisRect::setAutoMargins is set to true on the parent axis rect. An axis with axis type \ref
9820 atLeft will return an appropriate left margin, \ref atBottom will return an appropriate bottom
9821 margin and so forth. For the calculation, this function goes through similar steps as \ref draw,
9822 so changing one function likely requires the modification of the other one as well.
9823
9824 The margin consists of the outward tick length, tick label padding, tick label size, label
9825 padding, label size, and padding.
9826
9827 The margin is cached internally, so repeated calls while leaving the axis range, fonts, etc.
9828 unchanged are very fast.
9829*/
9831{
9832 if (!mVisible) // if not visible, directly return 0, don't cache 0 because we can't react to setVisible in QCPAxis
9833 return 0;
9834
9835 if (mCachedMarginValid)
9836 return mCachedMargin;
9837
9838 // run through similar steps as QCPAxis::draw, and calculate margin needed to fit axis and its labels
9839 int margin = 0;
9840
9841 QVector<double> tickPositions; // the final coordToPixel transformed vector passed to QCPAxisPainter
9842 QVector<QString> tickLabels; // the final vector passed to QCPAxisPainter
9843 tickPositions.reserve(mTickVector.size());
9844 tickLabels.reserve(mTickVector.size());
9845
9846 if (mTicks)
9847 {
9848 for (int i=0; i<mTickVector.size(); ++i)
9849 {
9850 tickPositions.append(coordToPixel(mTickVector.at(i)));
9851 if (mTickLabels)
9852 tickLabels.append(mTickVectorLabels.at(i));
9853 }
9854 }
9855 // transfer all properties of this axis to QCPAxisPainterPrivate which it needs to calculate the size.
9856 // Note that some axis painter properties are already set by direct feed-through with QCPAxis setters
9857 mAxisPainter->type = mAxisType;
9858 mAxisPainter->labelFont = getLabelFont();
9859 mAxisPainter->label = mLabel;
9860 mAxisPainter->tickLabelFont = mTickLabelFont;
9861 mAxisPainter->axisRect = mAxisRect->rect();
9862 mAxisPainter->viewportRect = mParentPlot->viewport();
9863 mAxisPainter->tickPositions = tickPositions;
9864 mAxisPainter->tickLabels = tickLabels;
9865 margin += mAxisPainter->size();
9866 margin += mPadding;
9867
9868 mCachedMargin = margin;
9869 mCachedMarginValid = true;
9870 return margin;
9871}
9872
9873/* inherits documentation from base class */
9878
9879
9880////////////////////////////////////////////////////////////////////////////////////////////////////
9881//////////////////// QCPAxisPainterPrivate
9882////////////////////////////////////////////////////////////////////////////////////////////////////
9883
9884/*! \class QCPAxisPainterPrivate
9885
9886 \internal
9887 \brief (Private)
9888
9889 This is a private class and not part of the public QCustomPlot interface.
9890
9891 It is used by QCPAxis to do the low-level drawing of axis backbone, tick marks, tick labels and
9892 axis label. It also buffers the labels to reduce replot times. The parameters are configured by
9893 directly accessing the public member variables.
9894*/
9895
9896/*!
9897 Constructs a QCPAxisPainterPrivate instance. Make sure to not create a new instance on every
9898 redraw, to utilize the caching mechanisms.
9899*/
9901 type(QCPAxis::atLeft),
9902 basePen(QPen(Qt::black, 0, Qt::SolidLine, Qt::SquareCap)),
9903 lowerEnding(QCPLineEnding::esNone),
9904 upperEnding(QCPLineEnding::esNone),
9905 labelPadding(0),
9906 tickLabelPadding(0),
9907 tickLabelRotation(0),
9908 tickLabelSide(QCPAxis::lsOutside),
9909 substituteExponent(true),
9910 numberMultiplyCross(false),
9911 tickLengthIn(5),
9912 tickLengthOut(0),
9913 subTickLengthIn(2),
9914 subTickLengthOut(0),
9915 tickPen(QPen(Qt::black, 0, Qt::SolidLine, Qt::SquareCap)),
9916 subTickPen(QPen(Qt::black, 0, Qt::SolidLine, Qt::SquareCap)),
9917 offset(0),
9918 abbreviateDecimalPowers(false),
9919 reversedEndings(false),
9920 mParentPlot(parentPlot),
9921 mLabelCache(16) // cache at most 16 (tick) labels
9922{
9923}
9924
9925QCPAxisPainterPrivate::~QCPAxisPainterPrivate()
9926{
9927}
9928
9929/*! \internal
9930
9931 Draws the axis with the specified \a painter.
9932
9933 The selection boxes (mAxisSelectionBox, mTickLabelsSelectionBox, mLabelSelectionBox) are set
9934 here, too.
9935*/
9937{
9939 if (newHash != mLabelParameterHash)
9940 {
9941 mLabelCache.clear();
9942 mLabelParameterHash = newHash;
9943 }
9944
9945 QPoint origin;
9946 switch (type)
9947 {
9948 case QCPAxis::atLeft: origin = axisRect.bottomLeft() +QPoint(-offset, 0); break;
9949 case QCPAxis::atRight: origin = axisRect.bottomRight()+QPoint(+offset, 0); break;
9950 case QCPAxis::atTop: origin = axisRect.topLeft() +QPoint(0, -offset); break;
9951 case QCPAxis::atBottom: origin = axisRect.bottomLeft() +QPoint(0, +offset); break;
9952 }
9953
9954 double xCor = 0, yCor = 0; // paint system correction, for pixel exact matches (affects baselines and ticks of top/right axes)
9955 switch (type)
9956 {
9957 case QCPAxis::atTop: yCor = -1; break;
9958 case QCPAxis::atRight: xCor = 1; break;
9959 default: break;
9960 }
9961 int margin = 0;
9962 // draw baseline:
9963 QLineF baseLine;
9964 painter->setPen(basePen);
9966 baseLine.setPoints(origin+QPointF(xCor, yCor), origin+QPointF(axisRect.width()+xCor, yCor));
9967 else
9968 baseLine.setPoints(origin+QPointF(xCor, yCor), origin+QPointF(xCor, -axisRect.height()+yCor));
9969 if (reversedEndings)
9970 baseLine = QLineF(baseLine.p2(), baseLine.p1()); // won't make a difference for line itself, but for line endings later
9971 painter->drawLine(baseLine);
9972
9973 // draw ticks:
9974 if (!tickPositions.isEmpty())
9975 {
9976 painter->setPen(tickPen);
9977 int tickDir = (type == QCPAxis::atBottom || type == QCPAxis::atRight) ? -1 : 1; // direction of ticks ("inward" is right for left axis and left for right axis)
9979 {
9980 foreach (double tickPos, tickPositions)
9981 painter->drawLine(QLineF(tickPos+xCor, origin.y()-tickLengthOut*tickDir+yCor, tickPos+xCor, origin.y()+tickLengthIn*tickDir+yCor));
9982 } else
9983 {
9984 foreach (double tickPos, tickPositions)
9985 painter->drawLine(QLineF(origin.x()-tickLengthOut*tickDir+xCor, tickPos+yCor, origin.x()+tickLengthIn*tickDir+xCor, tickPos+yCor));
9986 }
9987 }
9988
9989 // draw subticks:
9990 if (!subTickPositions.isEmpty())
9991 {
9992 painter->setPen(subTickPen);
9993 // direction of ticks ("inward" is right for left axis and left for right axis)
9994 int tickDir = (type == QCPAxis::atBottom || type == QCPAxis::atRight) ? -1 : 1;
9996 {
9997 foreach (double subTickPos, subTickPositions)
9998 painter->drawLine(QLineF(subTickPos+xCor, origin.y()-subTickLengthOut*tickDir+yCor, subTickPos+xCor, origin.y()+subTickLengthIn*tickDir+yCor));
9999 } else
10000 {
10001 foreach (double subTickPos, subTickPositions)
10002 painter->drawLine(QLineF(origin.x()-subTickLengthOut*tickDir+xCor, subTickPos+yCor, origin.x()+subTickLengthIn*tickDir+xCor, subTickPos+yCor));
10003 }
10004 }
10005 margin += qMax(0, qMax(tickLengthOut, subTickLengthOut));
10006
10007 // draw axis base endings:
10008 bool antialiasingBackup = painter->antialiasing();
10009 painter->setAntialiasing(true); // always want endings to be antialiased, even if base and ticks themselves aren't
10010 painter->setBrush(QBrush(basePen.color()));
10011 QCPVector2D baseLineVector(baseLine.dx(), baseLine.dy());
10012 if (lowerEnding.style() != QCPLineEnding::esNone)
10013 lowerEnding.draw(painter, QCPVector2D(baseLine.p1())-baseLineVector.normalized()*lowerEnding.realLength()*(lowerEnding.inverted()?-1:1), -baseLineVector);
10014 if (upperEnding.style() != QCPLineEnding::esNone)
10015 upperEnding.draw(painter, QCPVector2D(baseLine.p2())+baseLineVector.normalized()*upperEnding.realLength()*(upperEnding.inverted()?-1:1), baseLineVector);
10016 painter->setAntialiasing(antialiasingBackup);
10017
10018 // tick labels:
10019 QRect oldClipRect;
10020 if (tickLabelSide == QCPAxis::lsInside) // if using inside labels, clip them to the axis rect
10021 {
10022 oldClipRect = painter->clipRegion().boundingRect();
10023 painter->setClipRect(axisRect);
10024 }
10025 QSize tickLabelsSize(0, 0); // size of largest tick label, for offset calculation of axis label
10026 if (!tickLabels.isEmpty())
10027 {
10028 if (tickLabelSide == QCPAxis::lsOutside)
10029 margin += tickLabelPadding;
10030 painter->setFont(tickLabelFont);
10031 painter->setPen(QPen(tickLabelColor));
10032 const int maxLabelIndex = qMin(tickPositions.size(), tickLabels.size());
10033 int distanceToAxis = margin;
10034 if (tickLabelSide == QCPAxis::lsInside)
10035 distanceToAxis = -(qMax(tickLengthIn, subTickLengthIn)+tickLabelPadding);
10036 for (int i=0; i<maxLabelIndex; ++i)
10037 placeTickLabel(painter, tickPositions.at(i), distanceToAxis, tickLabels.at(i), &tickLabelsSize);
10038 if (tickLabelSide == QCPAxis::lsOutside)
10039 margin += (QCPAxis::orientation(type) == Qt::Horizontal) ? tickLabelsSize.height() : tickLabelsSize.width();
10040 }
10041 if (tickLabelSide == QCPAxis::lsInside)
10042 painter->setClipRect(oldClipRect);
10043
10044 // axis label:
10045 QRect labelBounds;
10046 if (!label.isEmpty())
10047 {
10048 margin += labelPadding;
10049 painter->setFont(labelFont);
10050 painter->setPen(QPen(labelColor));
10051 labelBounds = painter->fontMetrics().boundingRect(0, 0, 0, 0, Qt::TextDontClip, label);
10052 if (type == QCPAxis::atLeft)
10053 {
10054 QTransform oldTransform = painter->transform();
10055 painter->translate((origin.x()-margin-labelBounds.height()), origin.y());
10056 painter->rotate(-90);
10057 painter->drawText(0, 0, axisRect.height(), labelBounds.height(), Qt::TextDontClip | Qt::AlignCenter, label);
10058 painter->setTransform(oldTransform);
10059 }
10060 else if (type == QCPAxis::atRight)
10061 {
10062 QTransform oldTransform = painter->transform();
10063 painter->translate((origin.x()+margin+labelBounds.height()), origin.y()-axisRect.height());
10064 painter->rotate(90);
10065 painter->drawText(0, 0, axisRect.height(), labelBounds.height(), Qt::TextDontClip | Qt::AlignCenter, label);
10066 painter->setTransform(oldTransform);
10067 }
10068 else if (type == QCPAxis::atTop)
10069 painter->drawText(origin.x(), origin.y()-margin-labelBounds.height(), axisRect.width(), labelBounds.height(), Qt::TextDontClip | Qt::AlignCenter, label);
10070 else if (type == QCPAxis::atBottom)
10071 painter->drawText(origin.x(), origin.y()+margin, axisRect.width(), labelBounds.height(), Qt::TextDontClip | Qt::AlignCenter, label);
10072 }
10073
10074 // set selection boxes:
10075 int selectionTolerance = 0;
10076 if (mParentPlot)
10077 selectionTolerance = mParentPlot->selectionTolerance();
10078 else
10079 qDebug() << Q_FUNC_INFO << "mParentPlot is null";
10080 int selAxisOutSize = qMax(qMax(tickLengthOut, subTickLengthOut), selectionTolerance);
10081 int selAxisInSize = selectionTolerance;
10082 int selTickLabelSize;
10083 int selTickLabelOffset;
10084 if (tickLabelSide == QCPAxis::lsOutside)
10085 {
10086 selTickLabelSize = (QCPAxis::orientation(type) == Qt::Horizontal ? tickLabelsSize.height() : tickLabelsSize.width());
10087 selTickLabelOffset = qMax(tickLengthOut, subTickLengthOut)+tickLabelPadding;
10088 } else
10089 {
10090 selTickLabelSize = -(QCPAxis::orientation(type) == Qt::Horizontal ? tickLabelsSize.height() : tickLabelsSize.width());
10091 selTickLabelOffset = -(qMax(tickLengthIn, subTickLengthIn)+tickLabelPadding);
10092 }
10093 int selLabelSize = labelBounds.height();
10094 int selLabelOffset = qMax(tickLengthOut, subTickLengthOut)+(!tickLabels.isEmpty() && tickLabelSide == QCPAxis::lsOutside ? tickLabelPadding+selTickLabelSize : 0)+labelPadding;
10095 if (type == QCPAxis::atLeft)
10096 {
10097 mAxisSelectionBox.setCoords(origin.x()-selAxisOutSize, axisRect.top(), origin.x()+selAxisInSize, axisRect.bottom());
10098 mTickLabelsSelectionBox.setCoords(origin.x()-selTickLabelOffset-selTickLabelSize, axisRect.top(), origin.x()-selTickLabelOffset, axisRect.bottom());
10099 mLabelSelectionBox.setCoords(origin.x()-selLabelOffset-selLabelSize, axisRect.top(), origin.x()-selLabelOffset, axisRect.bottom());
10100 } else if (type == QCPAxis::atRight)
10101 {
10102 mAxisSelectionBox.setCoords(origin.x()-selAxisInSize, axisRect.top(), origin.x()+selAxisOutSize, axisRect.bottom());
10103 mTickLabelsSelectionBox.setCoords(origin.x()+selTickLabelOffset+selTickLabelSize, axisRect.top(), origin.x()+selTickLabelOffset, axisRect.bottom());
10104 mLabelSelectionBox.setCoords(origin.x()+selLabelOffset+selLabelSize, axisRect.top(), origin.x()+selLabelOffset, axisRect.bottom());
10105 } else if (type == QCPAxis::atTop)
10106 {
10107 mAxisSelectionBox.setCoords(axisRect.left(), origin.y()-selAxisOutSize, axisRect.right(), origin.y()+selAxisInSize);
10108 mTickLabelsSelectionBox.setCoords(axisRect.left(), origin.y()-selTickLabelOffset-selTickLabelSize, axisRect.right(), origin.y()-selTickLabelOffset);
10109 mLabelSelectionBox.setCoords(axisRect.left(), origin.y()-selLabelOffset-selLabelSize, axisRect.right(), origin.y()-selLabelOffset);
10110 } else if (type == QCPAxis::atBottom)
10111 {
10112 mAxisSelectionBox.setCoords(axisRect.left(), origin.y()-selAxisInSize, axisRect.right(), origin.y()+selAxisOutSize);
10113 mTickLabelsSelectionBox.setCoords(axisRect.left(), origin.y()+selTickLabelOffset+selTickLabelSize, axisRect.right(), origin.y()+selTickLabelOffset);
10114 mLabelSelectionBox.setCoords(axisRect.left(), origin.y()+selLabelOffset+selLabelSize, axisRect.right(), origin.y()+selLabelOffset);
10115 }
10116 mAxisSelectionBox = mAxisSelectionBox.normalized();
10117 mTickLabelsSelectionBox = mTickLabelsSelectionBox.normalized();
10118 mLabelSelectionBox = mLabelSelectionBox.normalized();
10119 // draw hitboxes for debug purposes:
10120 //painter->setBrush(Qt::NoBrush);
10121 //painter->drawRects(QVector<QRect>() << mAxisSelectionBox << mTickLabelsSelectionBox << mLabelSelectionBox);
10122}
10123
10124/*! \internal
10125
10126 Returns the size ("margin" in QCPAxisRect context, so measured perpendicular to the axis backbone
10127 direction) needed to fit the axis.
10128*/
10130{
10131 int result = 0;
10132
10134 if (newHash != mLabelParameterHash)
10135 {
10136 mLabelCache.clear();
10137 mLabelParameterHash = newHash;
10138 }
10139
10140 // get length of tick marks pointing outwards:
10141 if (!tickPositions.isEmpty())
10142 result += qMax(0, qMax(tickLengthOut, subTickLengthOut));
10143
10144 // calculate size of tick labels:
10145 if (tickLabelSide == QCPAxis::lsOutside)
10146 {
10147 QSize tickLabelsSize(0, 0);
10148 if (!tickLabels.isEmpty())
10149 {
10150 foreach (const QString &tickLabel, tickLabels)
10151 getMaxTickLabelSize(tickLabelFont, tickLabel, &tickLabelsSize);
10152 result += QCPAxis::orientation(type) == Qt::Horizontal ? tickLabelsSize.height() : tickLabelsSize.width();
10153 result += tickLabelPadding;
10154 }
10155 }
10156
10157 // calculate size of axis label (only height needed, because left/right labels are rotated by 90 degrees):
10158 if (!label.isEmpty())
10159 {
10160 QFontMetrics fontMetrics(labelFont);
10161 QRect bounds;
10162 bounds = fontMetrics.boundingRect(0, 0, 0, 0, Qt::TextDontClip | Qt::AlignHCenter | Qt::AlignVCenter, label);
10163 result += bounds.height() + labelPadding;
10164 }
10165
10166 return result;
10167}
10168
10169/*! \internal
10170
10171 Clears the internal label cache. Upon the next \ref draw, all labels will be created new. This
10172 method is called automatically in \ref draw, if any parameters have changed that invalidate the
10173 cached labels, such as font, color, etc.
10174*/
10176{
10177 mLabelCache.clear();
10178}
10179
10180/*! \internal
10181
10182 Returns a hash that allows uniquely identifying whether the label parameters have changed such
10183 that the cached labels must be refreshed (\ref clearCache). It is used in \ref draw. If the
10184 return value of this method hasn't changed since the last redraw, the respective label parameters
10185 haven't changed and cached labels may be used.
10186*/
10188{
10189 QByteArray result;
10190 result.append(QByteArray::number(mParentPlot->bufferDevicePixelRatio()));
10191 result.append(QByteArray::number(tickLabelRotation));
10192 result.append(QByteArray::number(int(tickLabelSide)));
10193 result.append(QByteArray::number(int(substituteExponent)));
10194 result.append(QByteArray::number(int(numberMultiplyCross)));
10195 result.append(tickLabelColor.name().toLatin1()+QByteArray::number(tickLabelColor.alpha(), 16));
10196 result.append(tickLabelFont.toString().toLatin1());
10197 return result;
10198}
10199
10200/*! \internal
10201
10202 Draws a single tick label with the provided \a painter, utilizing the internal label cache to
10203 significantly speed up drawing of labels that were drawn in previous calls. The tick label is
10204 always bound to an axis, the distance to the axis is controllable via \a distanceToAxis in
10205 pixels. The pixel position in the axis direction is passed in the \a position parameter. Hence
10206 for the bottom axis, \a position would indicate the horizontal pixel position (not coordinate),
10207 at which the label should be drawn.
10208
10209 In order to later draw the axis label in a place that doesn't overlap with the tick labels, the
10210 largest tick label size is needed. This is acquired by passing a \a tickLabelsSize to the \ref
10211 drawTickLabel calls during the process of drawing all tick labels of one axis. In every call, \a
10212 tickLabelsSize is expanded, if the drawn label exceeds the value \a tickLabelsSize currently
10213 holds.
10214
10215 The label is drawn with the font and pen that are currently set on the \a painter. To draw
10216 superscripted powers, the font is temporarily made smaller by a fixed factor (see \ref
10217 getTickLabelData).
10218*/
10219void QCPAxisPainterPrivate::placeTickLabel(QCPPainter *painter, double position, int distanceToAxis, const QString &text, QSize *tickLabelsSize)
10220{
10221 // warning: if you change anything here, also adapt getMaxTickLabelSize() accordingly!
10222 if (text.isEmpty()) return;
10223 QSize finalSize;
10224 QPointF labelAnchor;
10225 switch (type)
10226 {
10227 case QCPAxis::atLeft: labelAnchor = QPointF(axisRect.left()-distanceToAxis-offset, position); break;
10228 case QCPAxis::atRight: labelAnchor = QPointF(axisRect.right()+distanceToAxis+offset, position); break;
10229 case QCPAxis::atTop: labelAnchor = QPointF(position, axisRect.top()-distanceToAxis-offset); break;
10230 case QCPAxis::atBottom: labelAnchor = QPointF(position, axisRect.bottom()+distanceToAxis+offset); break;
10231 }
10232 if (mParentPlot->plottingHints().testFlag(QCP::phCacheLabels) && !painter->modes().testFlag(QCPPainter::pmNoCaching)) // label caching enabled
10233 {
10234 CachedLabel *cachedLabel = mLabelCache.take(text); // attempt to get label from cache
10235 if (!cachedLabel) // no cached label existed, create it
10236 {
10237 cachedLabel = new CachedLabel;
10238 TickLabelData labelData = getTickLabelData(painter->font(), text);
10239 cachedLabel->offset = getTickLabelDrawOffset(labelData)+labelData.rotatedTotalBounds.topLeft();
10240 if (!qFuzzyCompare(1.0, mParentPlot->bufferDevicePixelRatio()))
10241 {
10242 cachedLabel->pixmap = QPixmap(labelData.rotatedTotalBounds.size()*mParentPlot->bufferDevicePixelRatio());
10243#ifdef QCP_DEVICEPIXELRATIO_SUPPORTED
10244# ifdef QCP_DEVICEPIXELRATIO_FLOAT
10245 cachedLabel->pixmap.setDevicePixelRatio(mParentPlot->devicePixelRatioF());
10246# else
10247 cachedLabel->pixmap.setDevicePixelRatio(mParentPlot->devicePixelRatio());
10248# endif
10249#endif
10250 } else
10251 cachedLabel->pixmap = QPixmap(labelData.rotatedTotalBounds.size());
10252 cachedLabel->pixmap.fill(Qt::transparent);
10253 QCPPainter cachePainter(&cachedLabel->pixmap);
10254 cachePainter.setPen(painter->pen());
10255 drawTickLabel(&cachePainter, -labelData.rotatedTotalBounds.topLeft().x(), -labelData.rotatedTotalBounds.topLeft().y(), labelData);
10256 }
10257 // if label would be partly clipped by widget border on sides, don't draw it (only for outside tick labels):
10258 bool labelClippedByBorder = false;
10259 if (tickLabelSide == QCPAxis::lsOutside)
10260 {
10262 labelClippedByBorder = labelAnchor.x()+cachedLabel->offset.x()+cachedLabel->pixmap.width()/mParentPlot->bufferDevicePixelRatio() > viewportRect.right() || labelAnchor.x()+cachedLabel->offset.x() < viewportRect.left();
10263 else
10264 labelClippedByBorder = labelAnchor.y()+cachedLabel->offset.y()+cachedLabel->pixmap.height()/mParentPlot->bufferDevicePixelRatio() > viewportRect.bottom() || labelAnchor.y()+cachedLabel->offset.y() < viewportRect.top();
10265 }
10266 if (!labelClippedByBorder)
10267 {
10268 painter->drawPixmap(labelAnchor+cachedLabel->offset, cachedLabel->pixmap);
10269 finalSize = cachedLabel->pixmap.size()/mParentPlot->bufferDevicePixelRatio();
10270 }
10271 mLabelCache.insert(text, cachedLabel); // return label to cache or insert for the first time if newly created
10272 } else // label caching disabled, draw text directly on surface:
10273 {
10274 TickLabelData labelData = getTickLabelData(painter->font(), text);
10275 QPointF finalPosition = labelAnchor + getTickLabelDrawOffset(labelData);
10276 // if label would be partly clipped by widget border on sides, don't draw it (only for outside tick labels):
10277 bool labelClippedByBorder = false;
10278 if (tickLabelSide == QCPAxis::lsOutside)
10279 {
10281 labelClippedByBorder = finalPosition.x()+(labelData.rotatedTotalBounds.width()+labelData.rotatedTotalBounds.left()) > viewportRect.right() || finalPosition.x()+labelData.rotatedTotalBounds.left() < viewportRect.left();
10282 else
10283 labelClippedByBorder = finalPosition.y()+(labelData.rotatedTotalBounds.height()+labelData.rotatedTotalBounds.top()) > viewportRect.bottom() || finalPosition.y()+labelData.rotatedTotalBounds.top() < viewportRect.top();
10284 }
10285 if (!labelClippedByBorder)
10286 {
10287 drawTickLabel(painter, finalPosition.x(), finalPosition.y(), labelData);
10288 finalSize = labelData.rotatedTotalBounds.size();
10289 }
10290 }
10291
10292 // expand passed tickLabelsSize if current tick label is larger:
10293 if (finalSize.width() > tickLabelsSize->width())
10294 tickLabelsSize->setWidth(finalSize.width());
10295 if (finalSize.height() > tickLabelsSize->height())
10296 tickLabelsSize->setHeight(finalSize.height());
10297}
10298
10299/*! \internal
10300
10301 This is a \ref placeTickLabel helper function.
10302
10303 Draws the tick label specified in \a labelData with \a painter at the pixel positions \a x and \a
10304 y. This function is used by \ref placeTickLabel to create new tick labels for the cache, or to
10305 directly draw the labels on the QCustomPlot surface when label caching is disabled, i.e. when
10306 QCP::phCacheLabels plotting hint is not set.
10307*/
10308void QCPAxisPainterPrivate::drawTickLabel(QCPPainter *painter, double x, double y, const TickLabelData &labelData) const
10309{
10310 // backup painter settings that we're about to change:
10311 QTransform oldTransform = painter->transform();
10312 QFont oldFont = painter->font();
10313
10314 // transform painter to position/rotation:
10315 painter->translate(x, y);
10316 if (!qFuzzyIsNull(tickLabelRotation))
10317 painter->rotate(tickLabelRotation);
10318
10319 // draw text:
10320 if (!labelData.expPart.isEmpty()) // indicator that beautiful powers must be used
10321 {
10322 painter->setFont(labelData.baseFont);
10323 painter->drawText(0, 0, 0, 0, Qt::TextDontClip, labelData.basePart);
10324 if (!labelData.suffixPart.isEmpty())
10325 painter->drawText(labelData.baseBounds.width()+1+labelData.expBounds.width(), 0, 0, 0, Qt::TextDontClip, labelData.suffixPart);
10326 painter->setFont(labelData.expFont);
10327 painter->drawText(labelData.baseBounds.width()+1, 0, labelData.expBounds.width(), labelData.expBounds.height(), Qt::TextDontClip, labelData.expPart);
10328 } else
10329 {
10330 painter->setFont(labelData.baseFont);
10331 painter->drawText(0, 0, labelData.totalBounds.width(), labelData.totalBounds.height(), Qt::TextDontClip | Qt::AlignHCenter, labelData.basePart);
10332 }
10333
10334 // reset painter settings to what it was before:
10335 painter->setTransform(oldTransform);
10336 painter->setFont(oldFont);
10337}
10338
10339/*! \internal
10340
10341 This is a \ref placeTickLabel helper function.
10342
10343 Transforms the passed \a text and \a font to a tickLabelData structure that can then be further
10344 processed by \ref getTickLabelDrawOffset and \ref drawTickLabel. It splits the text into base and
10345 exponent if necessary (member substituteExponent) and calculates appropriate bounding boxes.
10346*/
10347QCPAxisPainterPrivate::TickLabelData QCPAxisPainterPrivate::getTickLabelData(const QFont &font, const QString &text) const
10348{
10349 TickLabelData result;
10350
10351 // determine whether beautiful decimal powers should be used
10352 bool useBeautifulPowers = false;
10353 int ePos = -1; // first index of exponent part, text before that will be basePart, text until eLast will be expPart
10354 int eLast = -1; // last index of exponent part, rest of text after this will be suffixPart
10355 if (substituteExponent)
10356 {
10357 ePos = text.indexOf(QString(mParentPlot->locale().exponential()));
10358 if (ePos > 0 && text.at(ePos-1).isDigit())
10359 {
10360 eLast = ePos;
10361 while (eLast+1 < text.size() && (text.at(eLast+1) == QLatin1Char('+') || text.at(eLast+1) == QLatin1Char('-') || text.at(eLast+1).isDigit()))
10362 ++eLast;
10363 if (eLast > ePos) // only if also to right of 'e' is a digit/+/- interpret it as beautifiable power
10364 useBeautifulPowers = true;
10365 }
10366 }
10367
10368 // calculate text bounding rects and do string preparation for beautiful decimal powers:
10369 result.baseFont = font;
10370 if (result.baseFont.pointSizeF() > 0) // might return -1 if specified with setPixelSize, in that case we can't do correction in next line
10371 result.baseFont.setPointSizeF(result.baseFont.pointSizeF()+0.05); // QFontMetrics.boundingRect has a bug for exact point sizes that make the results oscillate due to internal rounding
10372 if (useBeautifulPowers)
10373 {
10374 // split text into parts of number/symbol that will be drawn normally and part that will be drawn as exponent:
10375 result.basePart = text.left(ePos);
10376 result.suffixPart = text.mid(eLast+1); // also drawn normally but after exponent
10377 // in log scaling, we want to turn "1*10^n" into "10^n", else add multiplication sign and decimal base:
10378 if (abbreviateDecimalPowers && result.basePart == QLatin1String("1"))
10379 result.basePart = QLatin1String("10");
10380 else
10381 result.basePart += (numberMultiplyCross ? QString(QChar(215)) : QString(QChar(183))) + QLatin1String("10");
10382 result.expPart = text.mid(ePos+1, eLast-ePos);
10383 // clip "+" and leading zeros off expPart:
10384 while (result.expPart.length() > 2 && result.expPart.at(1) == QLatin1Char('0')) // length > 2 so we leave one zero when numberFormatChar is 'e'
10385 result.expPart.remove(1, 1);
10386 if (!result.expPart.isEmpty() && result.expPart.at(0) == QLatin1Char('+'))
10387 result.expPart.remove(0, 1);
10388 // prepare smaller font for exponent:
10389 result.expFont = font;
10390 if (result.expFont.pointSize() > 0)
10391 result.expFont.setPointSize(int(result.expFont.pointSize()*0.75));
10392 else
10393 result.expFont.setPixelSize(int(result.expFont.pixelSize()*0.75));
10394 // calculate bounding rects of base part(s), exponent part and total one:
10395 result.baseBounds = QFontMetrics(result.baseFont).boundingRect(0, 0, 0, 0, Qt::TextDontClip, result.basePart);
10396 result.expBounds = QFontMetrics(result.expFont).boundingRect(0, 0, 0, 0, Qt::TextDontClip, result.expPart);
10397 if (!result.suffixPart.isEmpty())
10398 result.suffixBounds = QFontMetrics(result.baseFont).boundingRect(0, 0, 0, 0, Qt::TextDontClip, result.suffixPart);
10399 result.totalBounds = result.baseBounds.adjusted(0, 0, result.expBounds.width()+result.suffixBounds.width()+2, 0); // +2 consists of the 1 pixel spacing between base and exponent (see drawTickLabel) and an extra pixel to include AA
10400 } else // useBeautifulPowers == false
10401 {
10402 result.basePart = text;
10403 result.totalBounds = QFontMetrics(result.baseFont).boundingRect(0, 0, 0, 0, Qt::TextDontClip | Qt::AlignHCenter, result.basePart);
10404 }
10405 result.totalBounds.moveTopLeft(QPoint(0, 0)); // want bounding box aligned top left at origin, independent of how it was created, to make further processing simpler
10406
10407 // calculate possibly different bounding rect after rotation:
10408 result.rotatedTotalBounds = result.totalBounds;
10409 if (!qFuzzyIsNull(tickLabelRotation))
10410 {
10411 QTransform transform;
10412 transform.rotate(tickLabelRotation);
10413 result.rotatedTotalBounds = transform.mapRect(result.rotatedTotalBounds);
10414 }
10415
10416 return result;
10417}
10418
10419/*! \internal
10420
10421 This is a \ref placeTickLabel helper function.
10422
10423 Calculates the offset at which the top left corner of the specified tick label shall be drawn.
10424 The offset is relative to a point right next to the tick the label belongs to.
10425
10426 This function is thus responsible for e.g. centering tick labels under ticks and positioning them
10427 appropriately when they are rotated.
10428*/
10429QPointF QCPAxisPainterPrivate::getTickLabelDrawOffset(const TickLabelData &labelData) const
10430{
10431 /*
10432 calculate label offset from base point at tick (non-trivial, for best visual appearance): short
10433 explanation for bottom axis: The anchor, i.e. the point in the label that is placed
10434 horizontally under the corresponding tick is always on the label side that is closer to the
10435 axis (e.g. the left side of the text when we're rotating clockwise). On that side, the height
10436 is halved and the resulting point is defined the anchor. This way, a 90 degree rotated text
10437 will be centered under the tick (i.e. displaced horizontally by half its height). At the same
10438 time, a 45 degree rotated text will "point toward" its tick, as is typical for rotated tick
10439 labels.
10440 */
10441 bool doRotation = !qFuzzyIsNull(tickLabelRotation);
10442 bool flip = qFuzzyCompare(qAbs(tickLabelRotation), 90.0); // perfect +/-90 degree flip. Indicates vertical label centering on vertical axes.
10443 double radians = tickLabelRotation/180.0*M_PI;
10444 double x = 0;
10445 double y = 0;
10446 if ((type == QCPAxis::atLeft && tickLabelSide == QCPAxis::lsOutside) || (type == QCPAxis::atRight && tickLabelSide == QCPAxis::lsInside)) // Anchor at right side of tick label
10447 {
10448 if (doRotation)
10449 {
10450 if (tickLabelRotation > 0)
10451 {
10452 x = -qCos(radians)*labelData.totalBounds.width();
10453 y = flip ? -labelData.totalBounds.width()/2.0 : -qSin(radians)*labelData.totalBounds.width()-qCos(radians)*labelData.totalBounds.height()/2.0;
10454 } else
10455 {
10456 x = -qCos(-radians)*labelData.totalBounds.width()-qSin(-radians)*labelData.totalBounds.height();
10457 y = flip ? +labelData.totalBounds.width()/2.0 : +qSin(-radians)*labelData.totalBounds.width()-qCos(-radians)*labelData.totalBounds.height()/2.0;
10458 }
10459 } else
10460 {
10461 x = -labelData.totalBounds.width();
10462 y = -labelData.totalBounds.height()/2.0;
10463 }
10464 } else if ((type == QCPAxis::atRight && tickLabelSide == QCPAxis::lsOutside) || (type == QCPAxis::atLeft && tickLabelSide == QCPAxis::lsInside)) // Anchor at left side of tick label
10465 {
10466 if (doRotation)
10467 {
10468 if (tickLabelRotation > 0)
10469 {
10470 x = +qSin(radians)*labelData.totalBounds.height();
10471 y = flip ? -labelData.totalBounds.width()/2.0 : -qCos(radians)*labelData.totalBounds.height()/2.0;
10472 } else
10473 {
10474 x = 0;
10475 y = flip ? +labelData.totalBounds.width()/2.0 : -qCos(-radians)*labelData.totalBounds.height()/2.0;
10476 }
10477 } else
10478 {
10479 x = 0;
10480 y = -labelData.totalBounds.height()/2.0;
10481 }
10482 } else if ((type == QCPAxis::atTop && tickLabelSide == QCPAxis::lsOutside) || (type == QCPAxis::atBottom && tickLabelSide == QCPAxis::lsInside)) // Anchor at bottom side of tick label
10483 {
10484 if (doRotation)
10485 {
10486 if (tickLabelRotation > 0)
10487 {
10488 x = -qCos(radians)*labelData.totalBounds.width()+qSin(radians)*labelData.totalBounds.height()/2.0;
10489 y = -qSin(radians)*labelData.totalBounds.width()-qCos(radians)*labelData.totalBounds.height();
10490 } else
10491 {
10492 x = -qSin(-radians)*labelData.totalBounds.height()/2.0;
10493 y = -qCos(-radians)*labelData.totalBounds.height();
10494 }
10495 } else
10496 {
10497 x = -labelData.totalBounds.width()/2.0;
10498 y = -labelData.totalBounds.height();
10499 }
10500 } else if ((type == QCPAxis::atBottom && tickLabelSide == QCPAxis::lsOutside) || (type == QCPAxis::atTop && tickLabelSide == QCPAxis::lsInside)) // Anchor at top side of tick label
10501 {
10502 if (doRotation)
10503 {
10504 if (tickLabelRotation > 0)
10505 {
10506 x = +qSin(radians)*labelData.totalBounds.height()/2.0;
10507 y = 0;
10508 } else
10509 {
10510 x = -qCos(-radians)*labelData.totalBounds.width()-qSin(-radians)*labelData.totalBounds.height()/2.0;
10511 y = +qSin(-radians)*labelData.totalBounds.width();
10512 }
10513 } else
10514 {
10515 x = -labelData.totalBounds.width()/2.0;
10516 y = 0;
10517 }
10518 }
10519
10520 return {x, y};
10521}
10522
10523/*! \internal
10524
10525 Simulates the steps done by \ref placeTickLabel by calculating bounding boxes of the text label
10526 to be drawn, depending on number format etc. Since only the largest tick label is wanted for the
10527 margin calculation, the passed \a tickLabelsSize is only expanded, if it's currently set to a
10528 smaller width/height.
10529*/
10530void QCPAxisPainterPrivate::getMaxTickLabelSize(const QFont &font, const QString &text, QSize *tickLabelsSize) const
10531{
10532 // note: this function must return the same tick label sizes as the placeTickLabel function.
10533 QSize finalSize;
10534 if (mParentPlot->plottingHints().testFlag(QCP::phCacheLabels) && mLabelCache.contains(text)) // label caching enabled and have cached label
10535 {
10536 const CachedLabel *cachedLabel = mLabelCache.object(text);
10537 finalSize = cachedLabel->pixmap.size()/mParentPlot->bufferDevicePixelRatio();
10538 } else // label caching disabled or no label with this text cached:
10539 {
10540 TickLabelData labelData = getTickLabelData(font, text);
10541 finalSize = labelData.rotatedTotalBounds.size();
10542 }
10543
10544 // expand passed tickLabelsSize if current tick label is larger:
10545 if (finalSize.width() > tickLabelsSize->width())
10546 tickLabelsSize->setWidth(finalSize.width());
10547 if (finalSize.height() > tickLabelsSize->height())
10548 tickLabelsSize->setHeight(finalSize.height());
10549}
10550/* end of 'src/axis/axis.cpp' */
10551
10552
10553/* including file 'src/scatterstyle.cpp' */
10554/* modified 2022-11-06T12:45:56, size 17466 */
10555
10556////////////////////////////////////////////////////////////////////////////////////////////////////
10557//////////////////// QCPScatterStyle
10558////////////////////////////////////////////////////////////////////////////////////////////////////
10559
10560/*! \class QCPScatterStyle
10561 \brief Represents the visual appearance of scatter points
10562
10563 This class holds information about shape, color and size of scatter points. In plottables like
10564 QCPGraph it is used to store how scatter points shall be drawn. For example, \ref
10565 QCPGraph::setScatterStyle takes a QCPScatterStyle instance.
10566
10567 A scatter style consists of a shape (\ref setShape), a line color (\ref setPen) and possibly a
10568 fill (\ref setBrush), if the shape provides a fillable area. Further, the size of the shape can
10569 be controlled with \ref setSize.
10570
10571 \section QCPScatterStyle-defining Specifying a scatter style
10572
10573 You can set all these configurations either by calling the respective functions on an instance:
10574 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpscatterstyle-creation-1
10575
10576 Or you can use one of the various constructors that take different parameter combinations, making
10577 it easy to specify a scatter style in a single call, like so:
10578 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpscatterstyle-creation-2
10579
10580 \section QCPScatterStyle-undefinedpen Leaving the color/pen up to the plottable
10581
10582 There are two constructors which leave the pen undefined: \ref QCPScatterStyle() and \ref
10583 QCPScatterStyle(ScatterShape shape, double size). If those constructors are used, a call to \ref
10584 isPenDefined will return false. It leads to scatter points that inherit the pen from the
10585 plottable that uses the scatter style. Thus, if such a scatter style is passed to QCPGraph, the line
10586 color of the graph (\ref QCPGraph::setPen) will be used by the scatter points. This makes
10587 it very convenient to set up typical scatter settings:
10588
10589 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpscatterstyle-shortcreation
10590
10591 Notice that it wasn't even necessary to explicitly call a QCPScatterStyle constructor. This works
10592 because QCPScatterStyle provides a constructor that can transform a \ref ScatterShape directly
10593 into a QCPScatterStyle instance (that's the \ref QCPScatterStyle(ScatterShape shape, double size)
10594 constructor with a default for \a size). In those cases, C++ allows directly supplying a \ref
10595 ScatterShape, where actually a QCPScatterStyle is expected.
10596
10597 \section QCPScatterStyle-custompath-and-pixmap Custom shapes and pixmaps
10598
10599 QCPScatterStyle supports drawing custom shapes and arbitrary pixmaps as scatter points.
10600
10601 For custom shapes, you can provide a QPainterPath with the desired shape to the \ref
10602 setCustomPath function or call the constructor that takes a painter path. The scatter shape will
10603 automatically be set to \ref ssCustom.
10604
10605 For pixmaps, you call \ref setPixmap with the desired QPixmap. Alternatively you can use the
10606 constructor that takes a QPixmap. The scatter shape will automatically be set to \ref ssPixmap.
10607 Note that \ref setSize does not influence the appearance of the pixmap.
10608*/
10609
10610/* start documentation of inline functions */
10611
10612/*! \fn bool QCPScatterStyle::isNone() const
10613
10614 Returns whether the scatter shape is \ref ssNone.
10615
10616 \see setShape
10617*/
10618
10619/*! \fn bool QCPScatterStyle::isPenDefined() const
10620
10621 Returns whether a pen has been defined for this scatter style.
10622
10623 The pen is undefined if a constructor is called that does not carry \a pen as parameter. Those
10624 are \ref QCPScatterStyle() and \ref QCPScatterStyle(ScatterShape shape, double size). If the pen
10625 is undefined, the pen of the respective plottable will be used for drawing scatters.
10626
10627 If a pen was defined for this scatter style instance, and you now wish to undefine the pen, call
10628 \ref undefinePen.
10629
10630 \see setPen
10631*/
10632
10633/* end documentation of inline functions */
10634
10635/*!
10636 Creates a new QCPScatterStyle instance with size set to 6. No shape, pen or brush is defined.
10637
10638 Since the pen is undefined (\ref isPenDefined returns false), the scatter color will be inherited
10639 from the plottable that uses this scatter style.
10640*/
10642 mSize(6),
10643 mShape(ssNone),
10644 mPen(Qt::NoPen),
10645 mBrush(Qt::NoBrush),
10646 mPenDefined(false)
10647{
10648}
10649
10650/*!
10651 Creates a new QCPScatterStyle instance with shape set to \a shape and size to \a size. No pen or
10652 brush is defined.
10653
10654 Since the pen is undefined (\ref isPenDefined returns false), the scatter color will be inherited
10655 from the plottable that uses this scatter style.
10656*/
10658 mSize(size),
10659 mShape(shape),
10660 mPen(Qt::NoPen),
10661 mBrush(Qt::NoBrush),
10662 mPenDefined(false)
10663{
10664}
10665
10666/*!
10667 Creates a new QCPScatterStyle instance with shape set to \a shape, the pen color set to \a color,
10668 and size to \a size. No brush is defined, i.e. the scatter point will not be filled.
10669*/
10670QCPScatterStyle::QCPScatterStyle(ScatterShape shape, const QColor &color, double size) :
10671 mSize(size),
10672 mShape(shape),
10673 mPen(QPen(color)),
10674 mBrush(Qt::NoBrush),
10675 mPenDefined(true)
10676{
10677}
10678
10679/*!
10680 Creates a new QCPScatterStyle instance with shape set to \a shape, the pen color set to \a color,
10681 the brush color to \a fill (with a solid pattern), and size to \a size.
10682*/
10683QCPScatterStyle::QCPScatterStyle(ScatterShape shape, const QColor &color, const QColor &fill, double size) :
10684 mSize(size),
10685 mShape(shape),
10686 mPen(QPen(color)),
10687 mBrush(QBrush(fill)),
10688 mPenDefined(true)
10689{
10690}
10691
10692/*!
10693 Creates a new QCPScatterStyle instance with shape set to \a shape, the pen set to \a pen, the
10694 brush to \a brush, and size to \a size.
10695
10696 \warning In some cases it might be tempting to directly use a pen style like <tt>Qt::NoPen</tt> as \a pen
10697 and a color like <tt>Qt::blue</tt> as \a brush. Notice however, that the corresponding call\n
10698 <tt>QCPScatterStyle(QCPScatterShape::ssCircle, Qt::NoPen, Qt::blue, 5)</tt>\n
10699 doesn't necessarily lead C++ to use this constructor in some cases, but might mistake
10700 <tt>Qt::NoPen</tt> for a QColor and use the
10701 \ref QCPScatterStyle(ScatterShape shape, const QColor &color, const QColor &fill, double size)
10702 constructor instead (which will lead to an unexpected look of the scatter points). To prevent
10703 this, be more explicit with the parameter types. For example, use <tt>QBrush(Qt::blue)</tt>
10704 instead of just <tt>Qt::blue</tt>, to clearly point out to the compiler that this constructor is
10705 wanted.
10706*/
10707QCPScatterStyle::QCPScatterStyle(ScatterShape shape, const QPen &pen, const QBrush &brush, double size) :
10708 mSize(size),
10709 mShape(shape),
10710 mPen(pen),
10711 mBrush(brush),
10712 mPenDefined(pen.style() != Qt::NoPen)
10713{
10714}
10715
10716/*!
10717 Creates a new QCPScatterStyle instance which will show the specified \a pixmap. The scatter shape
10718 is set to \ref ssPixmap.
10719*/
10721 mSize(5),
10722 mShape(ssPixmap),
10723 mPen(Qt::NoPen),
10724 mBrush(Qt::NoBrush),
10725 mPixmap(pixmap),
10726 mPenDefined(false)
10727{
10728}
10729
10730/*!
10731 Creates a new QCPScatterStyle instance with a custom shape that is defined via \a customPath. The
10732 scatter shape is set to \ref ssCustom.
10733
10734 The custom shape line will be drawn with \a pen and filled with \a brush. The size has a slightly
10735 different meaning than for built-in scatter points: The custom path will be drawn scaled by a
10736 factor of \a size/6.0. Since the default \a size is 6, the custom path will appear in its
10737 original size by default. To for example double the size of the path, set \a size to 12.
10738*/
10739QCPScatterStyle::QCPScatterStyle(const QPainterPath &customPath, const QPen &pen, const QBrush &brush, double size) :
10740 mSize(size),
10741 mShape(ssCustom),
10742 mPen(pen),
10743 mBrush(brush),
10744 mCustomPath(customPath),
10745 mPenDefined(pen.style() != Qt::NoPen)
10746{
10747}
10748
10749/*!
10750 Copies the specified \a properties from the \a other scatter style to this scatter style.
10751*/
10753{
10754 if (properties.testFlag(spPen))
10755 {
10756 setPen(other.pen());
10757 if (!other.isPenDefined())
10758 undefinePen();
10759 }
10760 if (properties.testFlag(spBrush))
10761 setBrush(other.brush());
10762 if (properties.testFlag(spSize))
10763 setSize(other.size());
10764 if (properties.testFlag(spShape))
10765 {
10766 setShape(other.shape());
10767 if (other.shape() == ssPixmap)
10768 setPixmap(other.pixmap());
10769 else if (other.shape() == ssCustom)
10770 setCustomPath(other.customPath());
10771 }
10772}
10773
10774/*!
10775 Sets the size (pixel diameter) of the drawn scatter points to \a size.
10776
10777 \see setShape
10778*/
10780{
10781 mSize = size;
10782}
10783
10784/*!
10785 Sets the shape to \a shape.
10786
10787 Note that the calls \ref setPixmap and \ref setCustomPath automatically set the shape to \ref
10788 ssPixmap and \ref ssCustom, respectively.
10789
10790 \see setSize
10791*/
10793{
10794 mShape = shape;
10795}
10796
10797/*!
10798 Sets the pen that will be used to draw scatter points to \a pen.
10799
10800 If the pen was previously undefined (see \ref isPenDefined), the pen is considered defined after
10801 a call to this function, even if \a pen is <tt>Qt::NoPen</tt>. If you have defined a pen
10802 previously by calling this function and now wish to undefine the pen, call \ref undefinePen.
10803
10804 \see setBrush
10805*/
10807{
10808 mPenDefined = true;
10809 mPen = pen;
10810}
10811
10812/*!
10813 Sets the brush that will be used to fill scatter points to \a brush. Note that not all scatter
10814 shapes have fillable areas. For example, \ref ssPlus does not while \ref ssCircle does.
10815
10816 \see setPen
10817*/
10819{
10820 mBrush = brush;
10821}
10822
10823/*!
10824 Sets the pixmap that will be drawn as scatter point to \a pixmap.
10825
10826 Note that \ref setSize does not influence the appearance of the pixmap.
10827
10828 The scatter shape is automatically set to \ref ssPixmap.
10829*/
10831{
10833 mPixmap = pixmap;
10834}
10835
10836/*!
10837 Sets the custom shape that will be drawn as scatter point to \a customPath.
10838
10839 The scatter shape is automatically set to \ref ssCustom.
10840*/
10842{
10844 mCustomPath = customPath;
10845}
10846
10847/*!
10848 Sets this scatter style to have an undefined pen (see \ref isPenDefined for what an undefined pen
10849 implies).
10850
10851 A call to \ref setPen will define a pen.
10852*/
10854{
10855 mPenDefined = false;
10856}
10857
10858/*!
10859 Applies the pen and the brush of this scatter style to \a painter. If this scatter style has an
10860 undefined pen (\ref isPenDefined), sets the pen of \a painter to \a defaultPen instead.
10861
10862 This function is used by plottables (or any class that wants to draw scatters) just before a
10863 number of scatters with this style shall be drawn with the \a painter.
10864
10865 \see drawShape
10866*/
10867void QCPScatterStyle::applyTo(QCPPainter *painter, const QPen &defaultPen) const
10868{
10869 painter->setPen(mPenDefined ? mPen : defaultPen);
10870 painter->setBrush(mBrush);
10871}
10872
10873/*!
10874 Draws the scatter shape with \a painter at position \a pos.
10875
10876 This function does not modify the pen or the brush on the painter, as \ref applyTo is meant to be
10877 called before scatter points are drawn with \ref drawShape.
10878
10879 \see applyTo
10880*/
10881void QCPScatterStyle::drawShape(QCPPainter *painter, const QPointF &pos) const
10882{
10883 drawShape(painter, pos.x(), pos.y());
10884}
10885
10886/*! \overload
10887 Draws the scatter shape with \a painter at position \a x and \a y.
10888*/
10889void QCPScatterStyle::drawShape(QCPPainter *painter, double x, double y) const
10890{
10891 double w = mSize/2.0;
10892 switch (mShape)
10893 {
10894 case ssNone: break;
10895 case ssDot:
10896 {
10897 painter->drawLine(QPointF(x, y), QPointF(x+0.0001, y));
10898 break;
10899 }
10900 case ssCross:
10901 {
10902 painter->drawLine(QLineF(x-w, y-w, x+w, y+w));
10903 painter->drawLine(QLineF(x-w, y+w, x+w, y-w));
10904 break;
10905 }
10906 case ssPlus:
10907 {
10908 painter->drawLine(QLineF(x-w, y, x+w, y));
10909 painter->drawLine(QLineF( x, y+w, x, y-w));
10910 break;
10911 }
10912 case ssCircle:
10913 {
10914 painter->drawEllipse(QPointF(x , y), w, w);
10915 break;
10916 }
10917 case ssDisc:
10918 {
10919 QBrush b = painter->brush();
10920 painter->setBrush(painter->pen().color());
10921 painter->drawEllipse(QPointF(x , y), w, w);
10922 painter->setBrush(b);
10923 break;
10924 }
10925 case ssSquare:
10926 {
10927 painter->drawRect(QRectF(x-w, y-w, mSize, mSize));
10928 break;
10929 }
10930 case ssDiamond:
10931 {
10932 QPointF lineArray[4] = {QPointF(x-w, y),
10933 QPointF( x, y-w),
10934 QPointF(x+w, y),
10935 QPointF( x, y+w)};
10936 painter->drawPolygon(lineArray, 4);
10937 break;
10938 }
10939 case ssStar:
10940 {
10941 painter->drawLine(QLineF(x-w, y, x+w, y));
10942 painter->drawLine(QLineF( x, y+w, x, y-w));
10943 painter->drawLine(QLineF(x-w*0.707, y-w*0.707, x+w*0.707, y+w*0.707));
10944 painter->drawLine(QLineF(x-w*0.707, y+w*0.707, x+w*0.707, y-w*0.707));
10945 break;
10946 }
10947 case ssTriangle:
10948 {
10949 QPointF lineArray[3] = {QPointF(x-w, y+0.755*w),
10950 QPointF(x+w, y+0.755*w),
10951 QPointF( x, y-0.977*w)};
10952 painter->drawPolygon(lineArray, 3);
10953 break;
10954 }
10955 case ssTriangleInverted:
10956 {
10957 QPointF lineArray[3] = {QPointF(x-w, y-0.755*w),
10958 QPointF(x+w, y-0.755*w),
10959 QPointF( x, y+0.977*w)};
10960 painter->drawPolygon(lineArray, 3);
10961 break;
10962 }
10963 case ssCrossSquare:
10964 {
10965 painter->drawRect(QRectF(x-w, y-w, mSize, mSize));
10966 painter->drawLine(QLineF(x-w, y-w, x+w*0.95, y+w*0.95));
10967 painter->drawLine(QLineF(x-w, y+w*0.95, x+w*0.95, y-w));
10968 break;
10969 }
10970 case ssPlusSquare:
10971 {
10972 painter->drawRect(QRectF(x-w, y-w, mSize, mSize));
10973 painter->drawLine(QLineF(x-w, y, x+w*0.95, y));
10974 painter->drawLine(QLineF( x, y+w, x, y-w));
10975 break;
10976 }
10977 case ssCrossCircle:
10978 {
10979 painter->drawEllipse(QPointF(x, y), w, w);
10980 painter->drawLine(QLineF(x-w*0.707, y-w*0.707, x+w*0.670, y+w*0.670));
10981 painter->drawLine(QLineF(x-w*0.707, y+w*0.670, x+w*0.670, y-w*0.707));
10982 break;
10983 }
10984 case ssPlusCircle:
10985 {
10986 painter->drawEllipse(QPointF(x, y), w, w);
10987 painter->drawLine(QLineF(x-w, y, x+w, y));
10988 painter->drawLine(QLineF( x, y+w, x, y-w));
10989 break;
10990 }
10991 case ssPeace:
10992 {
10993 painter->drawEllipse(QPointF(x, y), w, w);
10994 painter->drawLine(QLineF(x, y-w, x, y+w));
10995 painter->drawLine(QLineF(x, y, x-w*0.707, y+w*0.707));
10996 painter->drawLine(QLineF(x, y, x+w*0.707, y+w*0.707));
10997 break;
10998 }
10999 case ssPixmap:
11000 {
11001 const double widthHalf = mPixmap.width()*0.5;
11002 const double heightHalf = mPixmap.height()*0.5;
11003#if QT_VERSION < QT_VERSION_CHECK(4, 8, 0)
11004 const QRectF clipRect = painter->clipRegion().boundingRect().adjusted(-widthHalf, -heightHalf, widthHalf, heightHalf);
11005#else
11006 const QRectF clipRect = painter->clipBoundingRect().adjusted(-widthHalf, -heightHalf, widthHalf, heightHalf);
11007#endif
11008 if (clipRect.contains(x, y))
11009 painter->drawPixmap(qRound(x-widthHalf), qRound(y-heightHalf), mPixmap);
11010 break;
11011 }
11012 case ssCustom:
11013 {
11014 QTransform oldTransform = painter->transform();
11015 painter->translate(x, y);
11016 painter->scale(mSize/6.0, mSize/6.0);
11017 painter->drawPath(mCustomPath);
11018 painter->setTransform(oldTransform);
11019 break;
11020 }
11021 }
11022}
11023/* end of 'src/scatterstyle.cpp' */
11024
11025
11026/* including file 'src/plottable.cpp' */
11027/* modified 2022-11-06T12:45:56, size 38818 */
11028
11029////////////////////////////////////////////////////////////////////////////////////////////////////
11030//////////////////// QCPSelectionDecorator
11031////////////////////////////////////////////////////////////////////////////////////////////////////
11032
11033/*! \class QCPSelectionDecorator
11034 \brief Controls how a plottable's data selection is drawn
11035
11036 Each \ref QCPAbstractPlottable instance has one \ref QCPSelectionDecorator (accessible via \ref
11037 QCPAbstractPlottable::selectionDecorator) and uses it when drawing selected segments of its data.
11038
11039 The selection decorator controls both pen (\ref setPen) and brush (\ref setBrush), as well as the
11040 scatter style (\ref setScatterStyle) if the plottable draws scatters. Since a \ref
11041 QCPScatterStyle is itself composed of different properties such as color shape and size, the
11042 decorator allows specifying exactly which of those properties shall be used for the selected data
11043 point, via \ref setUsedScatterProperties.
11044
11045 A \ref QCPSelectionDecorator subclass instance can be passed to a plottable via \ref
11046 QCPAbstractPlottable::setSelectionDecorator, allowing greater customizability of the appearance
11047 of selected segments.
11048
11049 Use \ref copyFrom to easily transfer the settings of one decorator to another one. This is
11050 especially useful since plottables take ownership of the passed selection decorator, and thus the
11051 same decorator instance can not be passed to multiple plottables.
11052
11053 Selection decorators can also themselves perform drawing operations by reimplementing \ref
11054 drawDecoration, which is called by the plottable's draw method. The base class \ref
11055 QCPSelectionDecorator does not make use of this however. For example, \ref
11056 QCPSelectionDecoratorBracket draws brackets around selected data segments.
11057*/
11058
11059/*!
11060 Creates a new QCPSelectionDecorator instance with default values
11061*/
11063 mPen(QColor(80, 80, 255), 2.5),
11064 mBrush(Qt::NoBrush),
11065 mUsedScatterProperties(QCPScatterStyle::spNone),
11066 mPlottable(nullptr)
11067{
11068}
11069
11070QCPSelectionDecorator::~QCPSelectionDecorator()
11071{
11072}
11073
11074/*!
11075 Sets the pen that will be used by the parent plottable to draw selected data segments.
11076*/
11078{
11079 mPen = pen;
11080}
11081
11082/*!
11083 Sets the brush that will be used by the parent plottable to draw selected data segments.
11084*/
11086{
11087 mBrush = brush;
11088}
11089
11090/*!
11091 Sets the scatter style that will be used by the parent plottable to draw scatters in selected
11092 data segments.
11093
11094 \a usedProperties specifies which parts of the passed \a scatterStyle will be used by the
11095 plottable. The used properties can also be changed via \ref setUsedScatterProperties.
11096*/
11098{
11099 mScatterStyle = scatterStyle;
11100 setUsedScatterProperties(usedProperties);
11101}
11102
11103/*!
11104 Use this method to define which properties of the scatter style (set via \ref setScatterStyle)
11105 will be used for selected data segments. All properties of the scatter style that are not
11106 specified in \a properties will remain as specified in the plottable's original scatter style.
11107
11108 \see QCPScatterStyle::ScatterProperty
11109*/
11111{
11112 mUsedScatterProperties = properties;
11113}
11114
11115/*!
11116 Sets the pen of \a painter to the pen of this selection decorator.
11117
11118 \see applyBrush, getFinalScatterStyle
11119*/
11121{
11122 painter->setPen(mPen);
11123}
11124
11125/*!
11126 Sets the brush of \a painter to the brush of this selection decorator.
11127
11128 \see applyPen, getFinalScatterStyle
11129*/
11131{
11132 painter->setBrush(mBrush);
11133}
11134
11135/*!
11136 Returns the scatter style that the parent plottable shall use for selected scatter points. The
11137 plottable's original (unselected) scatter style must be passed as \a unselectedStyle. Depending
11138 on the setting of \ref setUsedScatterProperties, the returned scatter style is a mixture of this
11139 selecion decorator's scatter style (\ref setScatterStyle), and \a unselectedStyle.
11140
11141 \see applyPen, applyBrush, setScatterStyle
11142*/
11144{
11145 QCPScatterStyle result(unselectedStyle);
11146 result.setFromOther(mScatterStyle, mUsedScatterProperties);
11147
11148 // if style shall inherit pen from plottable (has no own pen defined), give it the selected
11149 // plottable pen explicitly, so it doesn't use the unselected plottable pen when used in the
11150 // plottable:
11151 if (!result.isPenDefined())
11152 result.setPen(mPen);
11153
11154 return result;
11155}
11156
11157/*!
11158 Copies all properties (e.g. color, fill, scatter style) of the \a other selection decorator to
11159 this selection decorator.
11160*/
11162{
11163 setPen(other->pen());
11164 setBrush(other->brush());
11165 setScatterStyle(other->scatterStyle(), other->usedScatterProperties());
11166}
11167
11168/*!
11169 This method is called by all plottables' draw methods to allow custom selection decorations to be
11170 drawn. Use the passed \a painter to perform the drawing operations. \a selection carries the data
11171 selection for which the decoration shall be drawn.
11172
11173 The default base class implementation of \ref QCPSelectionDecorator has no special decoration, so
11174 this method does nothing.
11175*/
11177{
11178 Q_UNUSED(painter)
11179 Q_UNUSED(selection)
11180}
11181
11182/*! \internal
11183
11184 This method is called as soon as a selection decorator is associated with a plottable, by a call
11185 to \ref QCPAbstractPlottable::setSelectionDecorator. This way the selection decorator can obtain a pointer to the plottable that uses it (e.g. to access
11186 data points via the \ref QCPAbstractPlottable::interface1D interface).
11187
11188 If the selection decorator was already added to a different plottable before, this method aborts
11189 the registration and returns false.
11190*/
11192{
11193 if (!mPlottable)
11194 {
11195 mPlottable = plottable;
11196 return true;
11197 } else
11198 {
11199 qDebug() << Q_FUNC_INFO << "This selection decorator is already registered with plottable:" << reinterpret_cast<quintptr>(mPlottable);
11200 return false;
11201 }
11202}
11203
11204
11205////////////////////////////////////////////////////////////////////////////////////////////////////
11206//////////////////// QCPAbstractPlottable
11207////////////////////////////////////////////////////////////////////////////////////////////////////
11208
11209/*! \class QCPAbstractPlottable
11210 \brief The abstract base class for all data representing objects in a plot.
11211
11212 It defines a very basic interface like name, pen, brush, visibility etc. Since this class is
11213 abstract, it can't be instantiated. Use one of the subclasses or create a subclass yourself to
11214 create new ways of displaying data (see "Creating own plottables" below). Plottables that display
11215 one-dimensional data (i.e. data points have a single key dimension and one or multiple values at
11216 each key) are based off of the template subclass \ref QCPAbstractPlottable1D, see details
11217 there.
11218
11219 All further specifics are in the subclasses, for example:
11220 \li A normal graph with possibly a line and/or scatter points \ref QCPGraph
11221 (typically created with \ref QCustomPlot::addGraph)
11222 \li A parametric curve: \ref QCPCurve
11223 \li A bar chart: \ref QCPBars
11224 \li A statistical box plot: \ref QCPStatisticalBox
11225 \li A color encoded two-dimensional map: \ref QCPColorMap
11226 \li An OHLC/Candlestick chart: \ref QCPFinancial
11227
11228 \section plottables-subclassing Creating own plottables
11229
11230 Subclassing directly from QCPAbstractPlottable is only recommended if you wish to display
11231 two-dimensional data like \ref QCPColorMap, i.e. two logical key dimensions and one (or more)
11232 data dimensions. If you want to display data with only one logical key dimension, you should
11233 rather derive from \ref QCPAbstractPlottable1D.
11234
11235 If subclassing QCPAbstractPlottable directly, these are the pure virtual functions you must
11236 implement:
11237 \li \ref selectTest
11238 \li \ref draw
11239 \li \ref drawLegendIcon
11240 \li \ref getKeyRange
11241 \li \ref getValueRange
11242
11243 See the documentation of those functions for what they need to do.
11244
11245 For drawing your plot, you can use the \ref coordsToPixels functions to translate a point in plot
11246 coordinates to pixel coordinates. This function is quite convenient, because it takes the
11247 orientation of the key and value axes into account for you (x and y are swapped when the key axis
11248 is vertical and the value axis horizontal). If you are worried about performance (i.e. you need
11249 to translate many points in a loop like QCPGraph), you can directly use \ref
11250 QCPAxis::coordToPixel. However, you must then take care about the orientation of the axis
11251 yourself.
11252
11253 Here are some important members you inherit from QCPAbstractPlottable:
11254 <table>
11255 <tr>
11256 <td>QCustomPlot *\b mParentPlot</td>
11257 <td>A pointer to the parent QCustomPlot instance. The parent plot is inferred from the axes that are passed in the constructor.</td>
11258 </tr><tr>
11259 <td>QString \b mName</td>
11260 <td>The name of the plottable.</td>
11261 </tr><tr>
11262 <td>QPen \b mPen</td>
11263 <td>The generic pen of the plottable. You should use this pen for the most prominent data representing lines in the plottable
11264 (e.g QCPGraph uses this pen for its graph lines and scatters)</td>
11265 </tr><tr>
11266 <td>QBrush \b mBrush</td>
11267 <td>The generic brush of the plottable. You should use this brush for the most prominent fillable structures in the plottable
11268 (e.g. QCPGraph uses this brush to control filling under the graph)</td>
11269 </tr><tr>
11270 <td>QPointer<\ref QCPAxis> \b mKeyAxis, \b mValueAxis</td>
11271 <td>The key and value axes this plottable is attached to. Call their QCPAxis::coordToPixel functions to translate coordinates
11272 to pixels in either the key or value dimension. Make sure to check whether the pointer is \c nullptr before using it. If one of
11273 the axes is null, don't draw the plottable.</td>
11274 </tr><tr>
11275 <td>\ref QCPSelectionDecorator \b mSelectionDecorator</td>
11276 <td>The currently set selection decorator which specifies how selected data of the plottable shall be drawn and decorated.
11277 When drawing your data, you must consult this decorator for the appropriate pen/brush before drawing unselected/selected data segments.
11278 Finally, you should call its \ref QCPSelectionDecorator::drawDecoration method at the end of your \ref draw implementation.</td>
11279 </tr><tr>
11280 <td>\ref QCP::SelectionType \b mSelectable</td>
11281 <td>In which composition, if at all, this plottable's data may be selected. Enforcing this setting on the data selection is done
11282 by QCPAbstractPlottable automatically.</td>
11283 </tr><tr>
11284 <td>\ref QCPDataSelection \b mSelection</td>
11285 <td>Holds the current selection state of the plottable's data, i.e. the selected data ranges (\ref QCPDataRange).</td>
11286 </tr>
11287 </table>
11288*/
11289
11290/* start of documentation of inline functions */
11291
11292/*! \fn QCPSelectionDecorator *QCPAbstractPlottable::selectionDecorator() const
11293
11294 Provides access to the selection decorator of this plottable. The selection decorator controls
11295 how selected data ranges are drawn (e.g. their pen color and fill), see \ref
11296 QCPSelectionDecorator for details.
11297
11298 If you wish to use an own \ref QCPSelectionDecorator subclass, pass an instance of it to \ref
11299 setSelectionDecorator.
11300*/
11301
11302/*! \fn bool QCPAbstractPlottable::selected() const
11303
11304 Returns true if there are any data points of the plottable currently selected. Use \ref selection
11305 to retrieve the current \ref QCPDataSelection.
11306*/
11307
11308/*! \fn QCPDataSelection QCPAbstractPlottable::selection() const
11309
11310 Returns a \ref QCPDataSelection encompassing all the data points that are currently selected on
11311 this plottable.
11312
11313 \see selected, setSelection, setSelectable
11314*/
11315
11316/*! \fn virtual QCPPlottableInterface1D *QCPAbstractPlottable::interface1D()
11317
11318 If this plottable is a one-dimensional plottable, i.e. it implements the \ref
11319 QCPPlottableInterface1D, returns the \a this pointer with that type. Otherwise (e.g. in the case
11320 of a \ref QCPColorMap) returns zero.
11321
11322 You can use this method to gain read access to data coordinates while holding a pointer to the
11323 abstract base class only.
11324*/
11325
11326/* end of documentation of inline functions */
11327/* start of documentation of pure virtual functions */
11328
11329/*! \fn void QCPAbstractPlottable::drawLegendIcon(QCPPainter *painter, const QRect &rect) const = 0
11330 \internal
11331
11332 called by QCPLegend::draw (via QCPPlottableLegendItem::draw) to create a graphical representation
11333 of this plottable inside \a rect, next to the plottable name.
11334
11335 The passed \a painter has its cliprect set to \a rect, so painting outside of \a rect won't
11336 appear outside the legend icon border.
11337*/
11338
11339/*! \fn QCPRange QCPAbstractPlottable::getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain) const = 0
11340
11341 Returns the coordinate range that all data in this plottable span in the key axis dimension. For
11342 logarithmic plots, one can set \a inSignDomain to either \ref QCP::sdNegative or \ref
11343 QCP::sdPositive in order to restrict the returned range to that sign domain. E.g. when only
11344 negative range is wanted, set \a inSignDomain to \ref QCP::sdNegative and all positive points
11345 will be ignored for range calculation. For no restriction, just set \a inSignDomain to \ref
11346 QCP::sdBoth (default). \a foundRange is an output parameter that indicates whether a range could
11347 be found or not. If this is false, you shouldn't use the returned range (e.g. no points in data).
11348
11349 Note that \a foundRange is not the same as \ref QCPRange::validRange, since the range returned by
11350 this function may have size zero (e.g. when there is only one data point). In this case \a
11351 foundRange would return true, but the returned range is not a valid range in terms of \ref
11352 QCPRange::validRange.
11353
11354 \see rescaleAxes, getValueRange
11355*/
11356
11357/*! \fn QCPRange QCPAbstractPlottable::getValueRange(bool &foundRange, QCP::SignDomain inSignDomain, const QCPRange &inKeyRange) const = 0
11358
11359 Returns the coordinate range that the data points in the specified key range (\a inKeyRange) span
11360 in the value axis dimension. For logarithmic plots, one can set \a inSignDomain to either \ref
11361 QCP::sdNegative or \ref QCP::sdPositive in order to restrict the returned range to that sign
11362 domain. E.g. when only negative range is wanted, set \a inSignDomain to \ref QCP::sdNegative and
11363 all positive points will be ignored for range calculation. For no restriction, just set \a
11364 inSignDomain to \ref QCP::sdBoth (default). \a foundRange is an output parameter that indicates
11365 whether a range could be found or not. If this is false, you shouldn't use the returned range
11366 (e.g. no points in data).
11367
11368 If \a inKeyRange has both lower and upper bound set to zero (is equal to <tt>QCPRange()</tt>),
11369 all data points are considered, without any restriction on the keys.
11370
11371 Note that \a foundRange is not the same as \ref QCPRange::validRange, since the range returned by
11372 this function may have size zero (e.g. when there is only one data point). In this case \a
11373 foundRange would return true, but the returned range is not a valid range in terms of \ref
11374 QCPRange::validRange.
11375
11376 \see rescaleAxes, getKeyRange
11377*/
11378
11379/* end of documentation of pure virtual functions */
11380/* start of documentation of signals */
11381
11382/*! \fn void QCPAbstractPlottable::selectionChanged(bool selected)
11383
11384 This signal is emitted when the selection state of this plottable has changed, either by user
11385 interaction or by a direct call to \ref setSelection. The parameter \a selected indicates whether
11386 there are any points selected or not.
11387
11388 \see selectionChanged(const QCPDataSelection &selection)
11389*/
11390
11391/*! \fn void QCPAbstractPlottable::selectionChanged(const QCPDataSelection &selection)
11392
11393 This signal is emitted when the selection state of this plottable has changed, either by user
11394 interaction or by a direct call to \ref setSelection. The parameter \a selection holds the
11395 currently selected data ranges.
11396
11397 \see selectionChanged(bool selected)
11398*/
11399
11400/*! \fn void QCPAbstractPlottable::selectableChanged(QCP::SelectionType selectable);
11401
11402 This signal is emitted when the selectability of this plottable has changed.
11403
11404 \see setSelectable
11405*/
11406
11407/* end of documentation of signals */
11408
11409/*!
11410 Constructs an abstract plottable which uses \a keyAxis as its key axis ("x") and \a valueAxis as
11411 its value axis ("y"). \a keyAxis and \a valueAxis must reside in the same QCustomPlot instance
11412 and have perpendicular orientations. If either of these restrictions is violated, a corresponding
11413 message is printed to the debug output (qDebug), the construction is not aborted, though.
11414
11415 Since QCPAbstractPlottable is an abstract class that defines the basic interface to plottables,
11416 it can't be directly instantiated.
11417
11418 You probably want one of the subclasses like \ref QCPGraph or \ref QCPCurve instead.
11419*/
11421 QCPLayerable(keyAxis->parentPlot(), QString(), keyAxis->axisRect()),
11422 mName(),
11423 mAntialiasedFill(true),
11424 mAntialiasedScatters(true),
11425 mPen(Qt::black),
11426 mBrush(Qt::NoBrush),
11427 mKeyAxis(keyAxis),
11428 mValueAxis(valueAxis),
11429 mSelectable(QCP::stWhole),
11430 mSelectionDecorator(nullptr)
11431{
11432 if (keyAxis->parentPlot() != valueAxis->parentPlot())
11433 qDebug() << Q_FUNC_INFO << "Parent plot of keyAxis is not the same as that of valueAxis.";
11434 if (keyAxis->orientation() == valueAxis->orientation())
11435 qDebug() << Q_FUNC_INFO << "keyAxis and valueAxis must be orthogonal to each other.";
11436
11437 mParentPlot->registerPlottable(this);
11439}
11440
11441QCPAbstractPlottable::~QCPAbstractPlottable()
11442{
11443 if (mSelectionDecorator)
11444 {
11445 delete mSelectionDecorator;
11446 mSelectionDecorator = nullptr;
11447 }
11448}
11449
11450/*!
11451 The name is the textual representation of this plottable as it is displayed in the legend
11452 (\ref QCPLegend). It may contain any UTF-8 characters, including newlines.
11453*/
11455{
11456 mName = name;
11457}
11458
11459/*!
11460 Sets whether fills of this plottable are drawn antialiased or not.
11461
11462 Note that this setting may be overridden by \ref QCustomPlot::setAntialiasedElements and \ref
11463 QCustomPlot::setNotAntialiasedElements.
11464*/
11466{
11467 mAntialiasedFill = enabled;
11468}
11469
11470/*!
11471 Sets whether the scatter symbols of this plottable are drawn antialiased or not.
11472
11473 Note that this setting may be overridden by \ref QCustomPlot::setAntialiasedElements and \ref
11474 QCustomPlot::setNotAntialiasedElements.
11475*/
11477{
11478 mAntialiasedScatters = enabled;
11479}
11480
11481/*!
11482 The pen is used to draw basic lines that make up the plottable representation in the
11483 plot.
11484
11485 For example, the \ref QCPGraph subclass draws its graph lines with this pen.
11486
11487 \see setBrush
11488*/
11490{
11491 mPen = pen;
11492}
11493
11494/*!
11495 The brush is used to draw basic fills of the plottable representation in the
11496 plot. The Fill can be a color, gradient or texture, see the usage of QBrush.
11497
11498 For example, the \ref QCPGraph subclass draws the fill under the graph with this brush, when
11499 it's not set to Qt::NoBrush.
11500
11501 \see setPen
11502*/
11504{
11505 mBrush = brush;
11506}
11507
11508/*!
11509 The key axis of a plottable can be set to any axis of a QCustomPlot, as long as it is orthogonal
11510 to the plottable's value axis. This function performs no checks to make sure this is the case.
11511 The typical mathematical choice is to use the x-axis (QCustomPlot::xAxis) as key axis and the
11512 y-axis (QCustomPlot::yAxis) as value axis.
11513
11514 Normally, the key and value axes are set in the constructor of the plottable (or \ref
11515 QCustomPlot::addGraph when working with QCPGraphs through the dedicated graph interface).
11516
11517 \see setValueAxis
11518*/
11520{
11521 mKeyAxis = axis;
11522}
11523
11524/*!
11525 The value axis of a plottable can be set to any axis of a QCustomPlot, as long as it is
11526 orthogonal to the plottable's key axis. This function performs no checks to make sure this is the
11527 case. The typical mathematical choice is to use the x-axis (QCustomPlot::xAxis) as key axis and
11528 the y-axis (QCustomPlot::yAxis) as value axis.
11529
11530 Normally, the key and value axes are set in the constructor of the plottable (or \ref
11531 QCustomPlot::addGraph when working with QCPGraphs through the dedicated graph interface).
11532
11533 \see setKeyAxis
11534*/
11536{
11537 mValueAxis = axis;
11538}
11539
11540
11541/*!
11542 Sets which data ranges of this plottable are selected. Selected data ranges are drawn differently
11543 (e.g. color) in the plot. This can be controlled via the selection decorator (see \ref
11544 selectionDecorator).
11545
11546 The entire selection mechanism for plottables is handled automatically when \ref
11547 QCustomPlot::setInteractions contains iSelectPlottables. You only need to call this function when
11548 you wish to change the selection state programmatically.
11549
11550 Using \ref setSelectable you can further specify for each plottable whether and to which
11551 granularity it is selectable. If \a selection is not compatible with the current \ref
11552 QCP::SelectionType set via \ref setSelectable, the resulting selection will be adjusted
11553 accordingly (see \ref QCPDataSelection::enforceType).
11554
11555 emits the \ref selectionChanged signal when \a selected is different from the previous selection state.
11556
11557 \see setSelectable, selectTest
11558*/
11560{
11561 selection.enforceType(mSelectable);
11562 if (mSelection != selection)
11563 {
11564 mSelection = selection;
11565 emit selectionChanged(selected());
11566 emit selectionChanged(mSelection);
11567 }
11568}
11569
11570/*!
11571 Use this method to set an own QCPSelectionDecorator (subclass) instance. This allows you to
11572 customize the visual representation of selected data ranges further than by using the default
11573 QCPSelectionDecorator.
11574
11575 The plottable takes ownership of the \a decorator.
11576
11577 The currently set decorator can be accessed via \ref selectionDecorator.
11578*/
11580{
11581 if (decorator)
11582 {
11583 if (decorator->registerWithPlottable(this))
11584 {
11585 delete mSelectionDecorator; // delete old decorator if necessary
11586 mSelectionDecorator = decorator;
11587 }
11588 } else if (mSelectionDecorator) // just clear decorator
11589 {
11590 delete mSelectionDecorator;
11591 mSelectionDecorator = nullptr;
11592 }
11593}
11594
11595/*!
11596 Sets whether and to which granularity this plottable can be selected.
11597
11598 A selection can happen by clicking on the QCustomPlot surface (When \ref
11599 QCustomPlot::setInteractions contains \ref QCP::iSelectPlottables), by dragging a selection rect
11600 (When \ref QCustomPlot::setSelectionRectMode is \ref QCP::srmSelect), or programmatically by
11601 calling \ref setSelection.
11602
11603 \see setSelection, QCP::SelectionType
11604*/
11606{
11607 if (mSelectable != selectable)
11608 {
11609 mSelectable = selectable;
11610 QCPDataSelection oldSelection = mSelection;
11611 mSelection.enforceType(mSelectable);
11612 emit selectableChanged(mSelectable);
11613 if (mSelection != oldSelection)
11614 {
11615 emit selectionChanged(selected());
11616 emit selectionChanged(mSelection);
11617 }
11618 }
11619}
11620
11621
11622/*!
11623 Convenience function for transforming a key/value pair to pixels on the QCustomPlot surface,
11624 taking the orientations of the axes associated with this plottable into account (e.g. whether key
11625 represents x or y).
11626
11627 \a key and \a value are transformed to the coodinates in pixels and are written to \a x and \a y.
11628
11629 \see pixelsToCoords, QCPAxis::coordToPixel
11630*/
11631void QCPAbstractPlottable::coordsToPixels(double key, double value, double &x, double &y) const
11632{
11633 QCPAxis *keyAxis = mKeyAxis.data();
11634 QCPAxis *valueAxis = mValueAxis.data();
11635 if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
11636
11637 if (keyAxis->orientation() == Qt::Horizontal)
11638 {
11639 x = keyAxis->coordToPixel(key);
11640 y = valueAxis->coordToPixel(value);
11641 } else
11642 {
11643 y = keyAxis->coordToPixel(key);
11644 x = valueAxis->coordToPixel(value);
11645 }
11646}
11647
11648/*! \overload
11649
11650 Transforms the given \a key and \a value to pixel coordinates and returns them in a QPointF.
11651*/
11652const QPointF QCPAbstractPlottable::coordsToPixels(double key, double value) const
11653{
11654 QCPAxis *keyAxis = mKeyAxis.data();
11655 QCPAxis *valueAxis = mValueAxis.data();
11656 if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return QPointF(); }
11657
11658 if (keyAxis->orientation() == Qt::Horizontal)
11659 return QPointF(keyAxis->coordToPixel(key), valueAxis->coordToPixel(value));
11660 else
11661 return QPointF(valueAxis->coordToPixel(value), keyAxis->coordToPixel(key));
11662}
11663
11664/*!
11665 Convenience function for transforming a x/y pixel pair on the QCustomPlot surface to plot coordinates,
11666 taking the orientations of the axes associated with this plottable into account (e.g. whether key
11667 represents x or y).
11668
11669 \a x and \a y are transformed to the plot coodinates and are written to \a key and \a value.
11670
11671 \see coordsToPixels, QCPAxis::coordToPixel
11672*/
11673void QCPAbstractPlottable::pixelsToCoords(double x, double y, double &key, double &value) const
11674{
11675 QCPAxis *keyAxis = mKeyAxis.data();
11676 QCPAxis *valueAxis = mValueAxis.data();
11677 if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
11678
11679 if (keyAxis->orientation() == Qt::Horizontal)
11680 {
11681 key = keyAxis->pixelToCoord(x);
11682 value = valueAxis->pixelToCoord(y);
11683 } else
11684 {
11685 key = keyAxis->pixelToCoord(y);
11686 value = valueAxis->pixelToCoord(x);
11687 }
11688}
11689
11690/*! \overload
11691
11692 Returns the pixel input \a pixelPos as plot coordinates \a key and \a value.
11693*/
11694void QCPAbstractPlottable::pixelsToCoords(const QPointF &pixelPos, double &key, double &value) const
11695{
11696 pixelsToCoords(pixelPos.x(), pixelPos.y(), key, value);
11697}
11698
11699/*!
11700 Rescales the key and value axes associated with this plottable to contain all displayed data, so
11701 the whole plottable is visible. If the scaling of an axis is logarithmic, rescaleAxes will make
11702 sure not to rescale to an illegal range i.e. a range containing different signs and/or zero.
11703 Instead it will stay in the current sign domain and ignore all parts of the plottable that lie
11704 outside of that domain.
11705
11706 \a onlyEnlarge makes sure the ranges are only expanded, never reduced. So it's possible to show
11707 multiple plottables in their entirety by multiple calls to rescaleAxes where the first call has
11708 \a onlyEnlarge set to false (the default), and all subsequent set to true.
11709
11710 \see rescaleKeyAxis, rescaleValueAxis, QCustomPlot::rescaleAxes, QCPAxis::rescale
11711*/
11712void QCPAbstractPlottable::rescaleAxes(bool onlyEnlarge) const
11713{
11714 rescaleKeyAxis(onlyEnlarge);
11715 rescaleValueAxis(onlyEnlarge);
11716}
11717
11718/*!
11719 Rescales the key axis of the plottable so the whole plottable is visible.
11720
11721 See \ref rescaleAxes for detailed behaviour.
11722*/
11723void QCPAbstractPlottable::rescaleKeyAxis(bool onlyEnlarge) const
11724{
11725 QCPAxis *keyAxis = mKeyAxis.data();
11726 if (!keyAxis) { qDebug() << Q_FUNC_INFO << "invalid key axis"; return; }
11727
11728 QCP::SignDomain signDomain = QCP::sdBoth;
11729 if (keyAxis->scaleType() == QCPAxis::stLogarithmic)
11730 signDomain = (keyAxis->range().upper < 0 ? QCP::sdNegative : QCP::sdPositive);
11731
11732 bool foundRange;
11733 QCPRange newRange = getKeyRange(foundRange, signDomain);
11734 if (foundRange)
11735 {
11736 if (onlyEnlarge)
11737 newRange.expand(keyAxis->range());
11738 if (!QCPRange::validRange(newRange)) // likely due to range being zero (plottable has only constant data in this axis dimension), shift current range to at least center the plottable
11739 {
11740 double center = (newRange.lower+newRange.upper)*0.5; // upper and lower should be equal anyway, but just to make sure, incase validRange returned false for other reason
11741 if (keyAxis->scaleType() == QCPAxis::stLinear)
11742 {
11743 newRange.lower = center-keyAxis->range().size()/2.0;
11744 newRange.upper = center+keyAxis->range().size()/2.0;
11745 } else // scaleType() == stLogarithmic
11746 {
11747 newRange.lower = center/qSqrt(keyAxis->range().upper/keyAxis->range().lower);
11748 newRange.upper = center*qSqrt(keyAxis->range().upper/keyAxis->range().lower);
11749 }
11750 }
11751 keyAxis->setRange(newRange);
11752 }
11753}
11754
11755/*!
11756 Rescales the value axis of the plottable so the whole plottable is visible. If \a inKeyRange is
11757 set to true, only the data points which are in the currently visible key axis range are
11758 considered.
11759
11760 Returns true if the axis was actually scaled. This might not be the case if this plottable has an
11761 invalid range, e.g. because it has no data points.
11762
11763 See \ref rescaleAxes for detailed behaviour.
11764*/
11765void QCPAbstractPlottable::rescaleValueAxis(bool onlyEnlarge, bool inKeyRange) const
11766{
11767 QCPAxis *keyAxis = mKeyAxis.data();
11768 QCPAxis *valueAxis = mValueAxis.data();
11769 if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
11770
11771 QCP::SignDomain signDomain = QCP::sdBoth;
11772 if (valueAxis->scaleType() == QCPAxis::stLogarithmic)
11773 signDomain = (valueAxis->range().upper < 0 ? QCP::sdNegative : QCP::sdPositive);
11774
11775 bool foundRange;
11776 QCPRange newRange = getValueRange(foundRange, signDomain, inKeyRange ? keyAxis->range() : QCPRange());
11777 if (foundRange)
11778 {
11779 if (onlyEnlarge)
11780 newRange.expand(valueAxis->range());
11781 if (!QCPRange::validRange(newRange)) // likely due to range being zero (plottable has only constant data in this axis dimension), shift current range to at least center the plottable
11782 {
11783 double center = (newRange.lower+newRange.upper)*0.5; // upper and lower should be equal anyway, but just to make sure, incase validRange returned false for other reason
11784 if (valueAxis->scaleType() == QCPAxis::stLinear)
11785 {
11786 newRange.lower = center-valueAxis->range().size()/2.0;
11787 newRange.upper = center+valueAxis->range().size()/2.0;
11788 } else // scaleType() == stLogarithmic
11789 {
11790 newRange.lower = center/qSqrt(valueAxis->range().upper/valueAxis->range().lower);
11791 newRange.upper = center*qSqrt(valueAxis->range().upper/valueAxis->range().lower);
11792 }
11793 }
11794 valueAxis->setRange(newRange);
11795 }
11796}
11797
11798/*! \overload
11799
11800 Adds this plottable to the specified \a legend.
11801
11802 Creates a QCPPlottableLegendItem which is inserted into the legend. Returns true on success, i.e.
11803 when the legend exists and a legend item associated with this plottable isn't already in the
11804 legend.
11805
11806 If the plottable needs a more specialized representation in the legend, you can create a
11807 corresponding subclass of \ref QCPPlottableLegendItem and add it to the legend manually instead
11808 of calling this method.
11809
11810 \see removeFromLegend, QCPLegend::addItem
11811*/
11813{
11814 if (!legend)
11815 {
11816 qDebug() << Q_FUNC_INFO << "passed legend is null";
11817 return false;
11818 }
11819 if (legend->parentPlot() != mParentPlot)
11820 {
11821 qDebug() << Q_FUNC_INFO << "passed legend isn't in the same QCustomPlot as this plottable";
11822 return false;
11823 }
11824
11825 if (!legend->hasItemWithPlottable(this))
11826 {
11827 legend->addItem(new QCPPlottableLegendItem(legend, this));
11828 return true;
11829 } else
11830 return false;
11831}
11832
11833/*! \overload
11834
11835 Adds this plottable to the legend of the parent QCustomPlot (\ref QCustomPlot::legend).
11836
11837 \see removeFromLegend
11838*/
11840{
11841 if (!mParentPlot || !mParentPlot->legend)
11842 return false;
11843 else
11844 return addToLegend(mParentPlot->legend);
11845}
11846
11847/*! \overload
11848
11849 Removes the plottable from the specifed \a legend. This means the \ref QCPPlottableLegendItem
11850 that is associated with this plottable is removed.
11851
11852 Returns true on success, i.e. if the legend exists and a legend item associated with this
11853 plottable was found and removed.
11854
11855 \see addToLegend, QCPLegend::removeItem
11856*/
11858{
11859 if (!legend)
11860 {
11861 qDebug() << Q_FUNC_INFO << "passed legend is null";
11862 return false;
11863 }
11864
11865 if (QCPPlottableLegendItem *lip = legend->itemWithPlottable(this))
11866 return legend->removeItem(lip);
11867 else
11868 return false;
11869}
11870
11871/*! \overload
11872
11873 Removes the plottable from the legend of the parent QCustomPlot.
11874
11875 \see addToLegend
11876*/
11878{
11879 if (!mParentPlot || !mParentPlot->legend)
11880 return false;
11881 else
11882 return removeFromLegend(mParentPlot->legend);
11883}
11884
11885/* inherits documentation from base class */
11887{
11888 if (mKeyAxis && mValueAxis)
11889 return mKeyAxis.data()->axisRect()->rect() & mValueAxis.data()->axisRect()->rect();
11890 else
11891 return {};
11892}
11893
11894/* inherits documentation from base class */
11899
11900/*! \internal
11901
11902 A convenience function to easily set the QPainter::Antialiased hint on the provided \a painter
11903 before drawing plottable lines.
11904
11905 This is the antialiasing state the painter passed to the \ref draw method is in by default.
11906
11907 This function takes into account the local setting of the antialiasing flag as well as the
11908 overrides set with \ref QCustomPlot::setAntialiasedElements and \ref
11909 QCustomPlot::setNotAntialiasedElements.
11910
11911 \seebaseclassmethod
11912
11913 \see setAntialiased, applyFillAntialiasingHint, applyScattersAntialiasingHint
11914*/
11916{
11917 applyAntialiasingHint(painter, mAntialiased, QCP::aePlottables);
11918}
11919
11920/*! \internal
11921
11922 A convenience function to easily set the QPainter::Antialiased hint on the provided \a painter
11923 before drawing plottable fills.
11924
11925 This function takes into account the local setting of the antialiasing flag as well as the
11926 overrides set with \ref QCustomPlot::setAntialiasedElements and \ref
11927 QCustomPlot::setNotAntialiasedElements.
11928
11929 \see setAntialiased, applyDefaultAntialiasingHint, applyScattersAntialiasingHint
11930*/
11932{
11933 applyAntialiasingHint(painter, mAntialiasedFill, QCP::aeFills);
11934}
11935
11936/*! \internal
11937
11938 A convenience function to easily set the QPainter::Antialiased hint on the provided \a painter
11939 before drawing plottable scatter points.
11940
11941 This function takes into account the local setting of the antialiasing flag as well as the
11942 overrides set with \ref QCustomPlot::setAntialiasedElements and \ref
11943 QCustomPlot::setNotAntialiasedElements.
11944
11945 \see setAntialiased, applyFillAntialiasingHint, applyDefaultAntialiasingHint
11946*/
11948{
11949 applyAntialiasingHint(painter, mAntialiasedScatters, QCP::aeScatters);
11950}
11951
11952/* inherits documentation from base class */
11953void QCPAbstractPlottable::selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged)
11954{
11955 Q_UNUSED(event)
11956
11957 if (mSelectable != QCP::stNone)
11958 {
11959 QCPDataSelection newSelection = details.value<QCPDataSelection>();
11960 QCPDataSelection selectionBefore = mSelection;
11961 if (additive)
11962 {
11963 if (mSelectable == QCP::stWhole) // in whole selection mode, we toggle to no selection even if currently unselected point was hit
11964 {
11965 if (selected())
11967 else
11968 setSelection(newSelection);
11969 } else // in all other selection modes we toggle selections of homogeneously selected/unselected segments
11970 {
11971 if (mSelection.contains(newSelection)) // if entire newSelection is already selected, toggle selection
11972 setSelection(mSelection-newSelection);
11973 else
11974 setSelection(mSelection+newSelection);
11975 }
11976 } else
11977 setSelection(newSelection);
11978 if (selectionStateChanged)
11979 *selectionStateChanged = mSelection != selectionBefore;
11980 }
11981}
11982
11983/* inherits documentation from base class */
11984void QCPAbstractPlottable::deselectEvent(bool *selectionStateChanged)
11985{
11986 if (mSelectable != QCP::stNone)
11987 {
11988 QCPDataSelection selectionBefore = mSelection;
11990 if (selectionStateChanged)
11991 *selectionStateChanged = mSelection != selectionBefore;
11992 }
11993}
11994/* end of 'src/plottable.cpp' */
11995
11996
11997/* including file 'src/item.cpp' */
11998/* modified 2022-11-06T12:45:56, size 49486 */
11999
12000////////////////////////////////////////////////////////////////////////////////////////////////////
12001//////////////////// QCPItemAnchor
12002////////////////////////////////////////////////////////////////////////////////////////////////////
12003
12004/*! \class QCPItemAnchor
12005 \brief An anchor of an item to which positions can be attached to.
12006
12007 An item (QCPAbstractItem) may have one or more anchors. Unlike QCPItemPosition, an anchor doesn't
12008 control anything on its item, but provides a way to tie other items via their positions to the
12009 anchor.
12010
12011 For example, a QCPItemRect is defined by its positions \a topLeft and \a bottomRight.
12012 Additionally it has various anchors like \a top, \a topRight or \a bottomLeft etc. So you can
12013 attach the \a start (which is a QCPItemPosition) of a QCPItemLine to one of the anchors by
12014 calling QCPItemPosition::setParentAnchor on \a start, passing the wanted anchor of the
12015 QCPItemRect. This way the start of the line will now always follow the respective anchor location
12016 on the rect item.
12017
12018 Note that QCPItemPosition derives from QCPItemAnchor, so every position can also serve as an
12019 anchor to other positions.
12020
12021 To learn how to provide anchors in your own item subclasses, see the subclassing section of the
12022 QCPAbstractItem documentation.
12023*/
12024
12025/* start documentation of inline functions */
12026
12027/*! \fn virtual QCPItemPosition *QCPItemAnchor::toQCPItemPosition()
12028
12029 Returns \c nullptr if this instance is merely a QCPItemAnchor, and a valid pointer of type
12030 QCPItemPosition* if it actually is a QCPItemPosition (which is a subclass of QCPItemAnchor).
12031
12032 This safe downcast functionality could also be achieved with a dynamic_cast. However, QCustomPlot avoids
12033 dynamic_cast to work with projects that don't have RTTI support enabled (e.g. -fno-rtti flag with
12034 gcc compiler).
12035*/
12036
12037/* end documentation of inline functions */
12038
12039/*!
12040 Creates a new QCPItemAnchor. You shouldn't create QCPItemAnchor instances directly, even if
12041 you want to make a new item subclass. Use \ref QCPAbstractItem::createAnchor instead, as
12042 explained in the subclassing section of the QCPAbstractItem documentation.
12043*/
12044QCPItemAnchor::QCPItemAnchor(QCustomPlot *parentPlot, QCPAbstractItem *parentItem, const QString &name, int anchorId) :
12045 mName(name),
12046 mParentPlot(parentPlot),
12047 mParentItem(parentItem),
12048 mAnchorId(anchorId)
12049{
12050}
12051
12052QCPItemAnchor::~QCPItemAnchor()
12053{
12054 // unregister as parent at children:
12055 foreach (QCPItemPosition *child, mChildrenX.values())
12056 {
12057 if (child->parentAnchorX() == this)
12058 child->setParentAnchorX(nullptr); // this acts back on this anchor and child removes itself from mChildrenX
12059 }
12060 foreach (QCPItemPosition *child, mChildrenY.values())
12061 {
12062 if (child->parentAnchorY() == this)
12063 child->setParentAnchorY(nullptr); // this acts back on this anchor and child removes itself from mChildrenY
12064 }
12065}
12066
12067/*!
12068 Returns the final absolute pixel position of the QCPItemAnchor on the QCustomPlot surface.
12069
12070 The pixel information is internally retrieved via QCPAbstractItem::anchorPixelPosition of the
12071 parent item, QCPItemAnchor is just an intermediary.
12072*/
12074{
12075 if (mParentItem)
12076 {
12077 if (mAnchorId > -1)
12078 {
12079 return mParentItem->anchorPixelPosition(mAnchorId);
12080 } else
12081 {
12082 qDebug() << Q_FUNC_INFO << "no valid anchor id set:" << mAnchorId;
12083 return {};
12084 }
12085 } else
12086 {
12087 qDebug() << Q_FUNC_INFO << "no parent item set";
12088 return {};
12089 }
12090}
12091
12092/*! \internal
12093
12094 Adds \a pos to the childX list of this anchor, which keeps track of which children use this
12095 anchor as parent anchor for the respective coordinate. This is necessary to notify the children
12096 prior to destruction of the anchor.
12097
12098 Note that this function does not change the parent setting in \a pos.
12099*/
12101{
12102 if (!mChildrenX.contains(pos))
12103 mChildrenX.insert(pos);
12104 else
12105 qDebug() << Q_FUNC_INFO << "provided pos is child already" << reinterpret_cast<quintptr>(pos);
12106}
12107
12108/*! \internal
12109
12110 Removes \a pos from the childX list of this anchor.
12111
12112 Note that this function does not change the parent setting in \a pos.
12113*/
12115{
12116 if (!mChildrenX.remove(pos))
12117 qDebug() << Q_FUNC_INFO << "provided pos isn't child" << reinterpret_cast<quintptr>(pos);
12118}
12119
12120/*! \internal
12121
12122 Adds \a pos to the childY list of this anchor, which keeps track of which children use this
12123 anchor as parent anchor for the respective coordinate. This is necessary to notify the children
12124 prior to destruction of the anchor.
12125
12126 Note that this function does not change the parent setting in \a pos.
12127*/
12129{
12130 if (!mChildrenY.contains(pos))
12131 mChildrenY.insert(pos);
12132 else
12133 qDebug() << Q_FUNC_INFO << "provided pos is child already" << reinterpret_cast<quintptr>(pos);
12134}
12135
12136/*! \internal
12137
12138 Removes \a pos from the childY list of this anchor.
12139
12140 Note that this function does not change the parent setting in \a pos.
12141*/
12143{
12144 if (!mChildrenY.remove(pos))
12145 qDebug() << Q_FUNC_INFO << "provided pos isn't child" << reinterpret_cast<quintptr>(pos);
12146}
12147
12148
12149////////////////////////////////////////////////////////////////////////////////////////////////////
12150//////////////////// QCPItemPosition
12151////////////////////////////////////////////////////////////////////////////////////////////////////
12152
12153/*! \class QCPItemPosition
12154 \brief Manages the position of an item.
12155
12156 Every item has at least one public QCPItemPosition member pointer which provides ways to position the
12157 item on the QCustomPlot surface. Some items have multiple positions, for example QCPItemRect has two:
12158 \a topLeft and \a bottomRight.
12159
12160 QCPItemPosition has a type (\ref PositionType) that can be set with \ref setType. This type
12161 defines how coordinates passed to \ref setCoords are to be interpreted, e.g. as absolute pixel
12162 coordinates, as plot coordinates of certain axes (\ref QCPItemPosition::setAxes), as fractions of
12163 the axis rect (\ref QCPItemPosition::setAxisRect), etc. For more advanced plots it is also
12164 possible to assign different types per X/Y coordinate of the position (see \ref setTypeX, \ref
12165 setTypeY). This way an item could be positioned for example at a fixed pixel distance from the
12166 top in the Y direction, while following a plot coordinate in the X direction.
12167
12168 A QCPItemPosition may have a parent QCPItemAnchor, see \ref setParentAnchor. This way you can tie
12169 multiple items together. If the QCPItemPosition has a parent, its coordinates (\ref setCoords)
12170 are considered to be absolute pixels in the reference frame of the parent anchor, where (0, 0)
12171 means directly ontop of the parent anchor. For example, You could attach the \a start position of
12172 a QCPItemLine to the \a bottom anchor of a QCPItemText to make the starting point of the line
12173 always be centered under the text label, no matter where the text is moved to. For more advanced
12174 plots, it is possible to assign different parent anchors per X/Y coordinate of the position, see
12175 \ref setParentAnchorX, \ref setParentAnchorY. This way an item could follow another item in the X
12176 direction but stay at a fixed position in the Y direction. Or even follow item A in X, and item B
12177 in Y.
12178
12179 Note that every QCPItemPosition inherits from QCPItemAnchor and thus can itself be used as parent
12180 anchor for other positions.
12181
12182 To set the apparent pixel position on the QCustomPlot surface directly, use \ref setPixelPosition. This
12183 works no matter what type this QCPItemPosition is or what parent-child situation it is in, as \ref
12184 setPixelPosition transforms the coordinates appropriately, to make the position appear at the specified
12185 pixel values.
12186*/
12187
12188/* start documentation of inline functions */
12189
12190/*! \fn QCPItemPosition::PositionType *QCPItemPosition::type() const
12191
12192 Returns the current position type.
12193
12194 If different types were set for X and Y (\ref setTypeX, \ref setTypeY), this method returns the
12195 type of the X coordinate. In that case rather use \a typeX() and \a typeY().
12196
12197 \see setType
12198*/
12199
12200/*! \fn QCPItemAnchor *QCPItemPosition::parentAnchor() const
12201
12202 Returns the current parent anchor.
12203
12204 If different parent anchors were set for X and Y (\ref setParentAnchorX, \ref setParentAnchorY),
12205 this method returns the parent anchor of the Y coordinate. In that case rather use \a
12206 parentAnchorX() and \a parentAnchorY().
12207
12208 \see setParentAnchor
12209*/
12210
12211/* end documentation of inline functions */
12212
12213/*!
12214 Creates a new QCPItemPosition. You shouldn't create QCPItemPosition instances directly, even if
12215 you want to make a new item subclass. Use \ref QCPAbstractItem::createPosition instead, as
12216 explained in the subclassing section of the QCPAbstractItem documentation.
12217*/
12219 QCPItemAnchor(parentPlot, parentItem, name),
12220 mPositionTypeX(ptAbsolute),
12221 mPositionTypeY(ptAbsolute),
12222 mKey(0),
12223 mValue(0),
12224 mParentAnchorX(nullptr),
12225 mParentAnchorY(nullptr)
12226{
12227}
12228
12229QCPItemPosition::~QCPItemPosition()
12230{
12231 // unregister as parent at children:
12232 // Note: this is done in ~QCPItemAnchor again, but it's important QCPItemPosition does it itself, because only then
12233 // the setParentAnchor(0) call the correct QCPItemPosition::pixelPosition function instead of QCPItemAnchor::pixelPosition
12234 foreach (QCPItemPosition *child, mChildrenX.values())
12235 {
12236 if (child->parentAnchorX() == this)
12237 child->setParentAnchorX(nullptr); // this acts back on this anchor and child removes itself from mChildrenX
12238 }
12239 foreach (QCPItemPosition *child, mChildrenY.values())
12240 {
12241 if (child->parentAnchorY() == this)
12242 child->setParentAnchorY(nullptr); // this acts back on this anchor and child removes itself from mChildrenY
12243 }
12244 // unregister as child in parent:
12245 if (mParentAnchorX)
12246 mParentAnchorX->removeChildX(this);
12247 if (mParentAnchorY)
12248 mParentAnchorY->removeChildY(this);
12249}
12250
12251/* can't make this a header inline function, because QPointer breaks with forward declared types, see QTBUG-29588 */
12252QCPAxisRect *QCPItemPosition::axisRect() const
12253{
12254 return mAxisRect.data();
12255}
12256
12257/*!
12258 Sets the type of the position. The type defines how the coordinates passed to \ref setCoords
12259 should be handled and how the QCPItemPosition should behave in the plot.
12260
12261 The possible values for \a type can be separated in two main categories:
12262
12263 \li The position is regarded as a point in plot coordinates. This corresponds to \ref ptPlotCoords
12264 and requires two axes that define the plot coordinate system. They can be specified with \ref setAxes.
12265 By default, the QCustomPlot's x- and yAxis are used.
12266
12267 \li The position is fixed on the QCustomPlot surface, i.e. independent of axis ranges. This
12268 corresponds to all other types, i.e. \ref ptAbsolute, \ref ptViewportRatio and \ref
12269 ptAxisRectRatio. They differ only in the way the absolute position is described, see the
12270 documentation of \ref PositionType for details. For \ref ptAxisRectRatio, note that you can specify
12271 the axis rect with \ref setAxisRect. By default this is set to the main axis rect.
12272
12273 Note that the position type \ref ptPlotCoords is only available (and sensible) when the position
12274 has no parent anchor (\ref setParentAnchor).
12275
12276 If the type is changed, the apparent pixel position on the plot is preserved. This means
12277 the coordinates as retrieved with coords() and set with \ref setCoords may change in the process.
12278
12279 This method sets the type for both X and Y directions. It is also possible to set different types
12280 for X and Y, see \ref setTypeX, \ref setTypeY.
12281*/
12287
12288/*!
12289 This method sets the position type of the X coordinate to \a type.
12290
12291 For a detailed description of what a position type is, see the documentation of \ref setType.
12292
12293 \see setType, setTypeY
12294*/
12296{
12297 if (mPositionTypeX != type)
12298 {
12299 // if switching from or to coordinate type that isn't valid (e.g. because axes or axis rect
12300 // were deleted), don't try to recover the pixelPosition() because it would output a qDebug warning.
12301 bool retainPixelPosition = true;
12302 if ((mPositionTypeX == ptPlotCoords || type == ptPlotCoords) && (!mKeyAxis || !mValueAxis))
12303 retainPixelPosition = false;
12304 if ((mPositionTypeX == ptAxisRectRatio || type == ptAxisRectRatio) && (!mAxisRect))
12305 retainPixelPosition = false;
12306
12307 QPointF pixel;
12308 if (retainPixelPosition)
12309 pixel = pixelPosition();
12310
12311 mPositionTypeX = type;
12312
12313 if (retainPixelPosition)
12314 setPixelPosition(pixel);
12315 }
12316}
12317
12318/*!
12319 This method sets the position type of the Y coordinate to \a type.
12320
12321 For a detailed description of what a position type is, see the documentation of \ref setType.
12322
12323 \see setType, setTypeX
12324*/
12326{
12327 if (mPositionTypeY != type)
12328 {
12329 // if switching from or to coordinate type that isn't valid (e.g. because axes or axis rect
12330 // were deleted), don't try to recover the pixelPosition() because it would output a qDebug warning.
12331 bool retainPixelPosition = true;
12332 if ((mPositionTypeY == ptPlotCoords || type == ptPlotCoords) && (!mKeyAxis || !mValueAxis))
12333 retainPixelPosition = false;
12334 if ((mPositionTypeY == ptAxisRectRatio || type == ptAxisRectRatio) && (!mAxisRect))
12335 retainPixelPosition = false;
12336
12337 QPointF pixel;
12338 if (retainPixelPosition)
12339 pixel = pixelPosition();
12340
12341 mPositionTypeY = type;
12342
12343 if (retainPixelPosition)
12344 setPixelPosition(pixel);
12345 }
12346}
12347
12348/*!
12349 Sets the parent of this QCPItemPosition to \a parentAnchor. This means the position will now
12350 follow any position changes of the anchor. The local coordinate system of positions with a parent
12351 anchor always is absolute pixels, with (0, 0) being exactly on top of the parent anchor. (Hence
12352 the type shouldn't be set to \ref ptPlotCoords for positions with parent anchors.)
12353
12354 if \a keepPixelPosition is true, the current pixel position of the QCPItemPosition is preserved
12355 during reparenting. If it's set to false, the coordinates are set to (0, 0), i.e. the position
12356 will be exactly on top of the parent anchor.
12357
12358 To remove this QCPItemPosition from any parent anchor, set \a parentAnchor to \c nullptr.
12359
12360 If the QCPItemPosition previously had no parent and the type is \ref ptPlotCoords, the type is
12361 set to \ref ptAbsolute, to keep the position in a valid state.
12362
12363 This method sets the parent anchor for both X and Y directions. It is also possible to set
12364 different parents for X and Y, see \ref setParentAnchorX, \ref setParentAnchorY.
12365*/
12366bool QCPItemPosition::setParentAnchor(QCPItemAnchor *parentAnchor, bool keepPixelPosition)
12367{
12368 bool successX = setParentAnchorX(parentAnchor, keepPixelPosition);
12369 bool successY = setParentAnchorY(parentAnchor, keepPixelPosition);
12370 return successX && successY;
12371}
12372
12373/*!
12374 This method sets the parent anchor of the X coordinate to \a parentAnchor.
12375
12376 For a detailed description of what a parent anchor is, see the documentation of \ref setParentAnchor.
12377
12378 \see setParentAnchor, setParentAnchorY
12379*/
12380bool QCPItemPosition::setParentAnchorX(QCPItemAnchor *parentAnchor, bool keepPixelPosition)
12381{
12382 // make sure self is not assigned as parent:
12383 if (parentAnchor == this)
12384 {
12385 qDebug() << Q_FUNC_INFO << "can't set self as parent anchor" << reinterpret_cast<quintptr>(parentAnchor);
12386 return false;
12387 }
12388 // make sure no recursive parent-child-relationships are created:
12389 QCPItemAnchor *currentParent = parentAnchor;
12390 while (currentParent)
12391 {
12392 if (QCPItemPosition *currentParentPos = currentParent->toQCPItemPosition())
12393 {
12394 // is a QCPItemPosition, might have further parent, so keep iterating
12395 if (currentParentPos == this)
12396 {
12397 qDebug() << Q_FUNC_INFO << "can't create recursive parent-child-relationship" << reinterpret_cast<quintptr>(parentAnchor);
12398 return false;
12399 }
12400 currentParent = currentParentPos->parentAnchorX();
12401 } else
12402 {
12403 // is a QCPItemAnchor, can't have further parent. Now make sure the parent items aren't the
12404 // same, to prevent a position being child of an anchor which itself depends on the position,
12405 // because they're both on the same item:
12406 if (currentParent->mParentItem == mParentItem)
12407 {
12408 qDebug() << Q_FUNC_INFO << "can't set parent to be an anchor which itself depends on this position" << reinterpret_cast<quintptr>(parentAnchor);
12409 return false;
12410 }
12411 break;
12412 }
12413 }
12414
12415 // if previously no parent set and PosType is still ptPlotCoords, set to ptAbsolute:
12416 if (!mParentAnchorX && mPositionTypeX == ptPlotCoords)
12418
12419 // save pixel position:
12420 QPointF pixelP;
12421 if (keepPixelPosition)
12422 pixelP = pixelPosition();
12423 // unregister at current parent anchor:
12424 if (mParentAnchorX)
12425 mParentAnchorX->removeChildX(this);
12426 // register at new parent anchor:
12427 if (parentAnchor)
12428 parentAnchor->addChildX(this);
12429 mParentAnchorX = parentAnchor;
12430 // restore pixel position under new parent:
12431 if (keepPixelPosition)
12432 setPixelPosition(pixelP);
12433 else
12434 setCoords(0, coords().y());
12435 return true;
12436}
12437
12438/*!
12439 This method sets the parent anchor of the Y coordinate to \a parentAnchor.
12440
12441 For a detailed description of what a parent anchor is, see the documentation of \ref setParentAnchor.
12442
12443 \see setParentAnchor, setParentAnchorX
12444*/
12445bool QCPItemPosition::setParentAnchorY(QCPItemAnchor *parentAnchor, bool keepPixelPosition)
12446{
12447 // make sure self is not assigned as parent:
12448 if (parentAnchor == this)
12449 {
12450 qDebug() << Q_FUNC_INFO << "can't set self as parent anchor" << reinterpret_cast<quintptr>(parentAnchor);
12451 return false;
12452 }
12453 // make sure no recursive parent-child-relationships are created:
12454 QCPItemAnchor *currentParent = parentAnchor;
12455 while (currentParent)
12456 {
12457 if (QCPItemPosition *currentParentPos = currentParent->toQCPItemPosition())
12458 {
12459 // is a QCPItemPosition, might have further parent, so keep iterating
12460 if (currentParentPos == this)
12461 {
12462 qDebug() << Q_FUNC_INFO << "can't create recursive parent-child-relationship" << reinterpret_cast<quintptr>(parentAnchor);
12463 return false;
12464 }
12465 currentParent = currentParentPos->parentAnchorY();
12466 } else
12467 {
12468 // is a QCPItemAnchor, can't have further parent. Now make sure the parent items aren't the
12469 // same, to prevent a position being child of an anchor which itself depends on the position,
12470 // because they're both on the same item:
12471 if (currentParent->mParentItem == mParentItem)
12472 {
12473 qDebug() << Q_FUNC_INFO << "can't set parent to be an anchor which itself depends on this position" << reinterpret_cast<quintptr>(parentAnchor);
12474 return false;
12475 }
12476 break;
12477 }
12478 }
12479
12480 // if previously no parent set and PosType is still ptPlotCoords, set to ptAbsolute:
12481 if (!mParentAnchorY && mPositionTypeY == ptPlotCoords)
12483
12484 // save pixel position:
12485 QPointF pixelP;
12486 if (keepPixelPosition)
12487 pixelP = pixelPosition();
12488 // unregister at current parent anchor:
12489 if (mParentAnchorY)
12490 mParentAnchorY->removeChildY(this);
12491 // register at new parent anchor:
12492 if (parentAnchor)
12493 parentAnchor->addChildY(this);
12494 mParentAnchorY = parentAnchor;
12495 // restore pixel position under new parent:
12496 if (keepPixelPosition)
12497 setPixelPosition(pixelP);
12498 else
12499 setCoords(coords().x(), 0);
12500 return true;
12501}
12502
12503/*!
12504 Sets the coordinates of this QCPItemPosition. What the coordinates mean, is defined by the type
12505 (\ref setType, \ref setTypeX, \ref setTypeY).
12506
12507 For example, if the type is \ref ptAbsolute, \a key and \a value mean the x and y pixel position
12508 on the QCustomPlot surface. In that case the origin (0, 0) is in the top left corner of the
12509 QCustomPlot viewport. If the type is \ref ptPlotCoords, \a key and \a value mean a point in the
12510 plot coordinate system defined by the axes set by \ref setAxes. By default those are the
12511 QCustomPlot's xAxis and yAxis. See the documentation of \ref setType for other available
12512 coordinate types and their meaning.
12513
12514 If different types were configured for X and Y (\ref setTypeX, \ref setTypeY), \a key and \a
12515 value must also be provided in the different coordinate systems. Here, the X type refers to \a
12516 key, and the Y type refers to \a value.
12517
12518 \see setPixelPosition
12519*/
12520void QCPItemPosition::setCoords(double key, double value)
12521{
12522 mKey = key;
12523 mValue = value;
12524}
12525
12526/*! \overload
12527
12528 Sets the coordinates as a QPointF \a pos where pos.x has the meaning of \a key and pos.y the
12529 meaning of \a value of the \ref setCoords(double key, double value) method.
12530*/
12532{
12533 setCoords(pos.x(), pos.y());
12534}
12535
12536/*!
12537 Returns the final absolute pixel position of the QCPItemPosition on the QCustomPlot surface. It
12538 includes all effects of type (\ref setType) and possible parent anchors (\ref setParentAnchor).
12539
12540 \see setPixelPosition
12541*/
12543{
12544 QPointF result;
12545
12546 // determine X:
12547 switch (mPositionTypeX)
12548 {
12549 case ptAbsolute:
12550 {
12551 result.rx() = mKey;
12552 if (mParentAnchorX)
12553 result.rx() += mParentAnchorX->pixelPosition().x();
12554 break;
12555 }
12556 case ptViewportRatio:
12557 {
12558 result.rx() = mKey*mParentPlot->viewport().width();
12559 if (mParentAnchorX)
12560 result.rx() += mParentAnchorX->pixelPosition().x();
12561 else
12562 result.rx() += mParentPlot->viewport().left();
12563 break;
12564 }
12565 case ptAxisRectRatio:
12566 {
12567 if (mAxisRect)
12568 {
12569 result.rx() = mKey*mAxisRect.data()->width();
12570 if (mParentAnchorX)
12571 result.rx() += mParentAnchorX->pixelPosition().x();
12572 else
12573 result.rx() += mAxisRect.data()->left();
12574 } else
12575 qDebug() << Q_FUNC_INFO << "Item position type x is ptAxisRectRatio, but no axis rect was defined";
12576 break;
12577 }
12578 case ptPlotCoords:
12579 {
12580 if (mKeyAxis && mKeyAxis.data()->orientation() == Qt::Horizontal)
12581 result.rx() = mKeyAxis.data()->coordToPixel(mKey);
12582 else if (mValueAxis && mValueAxis.data()->orientation() == Qt::Horizontal)
12583 result.rx() = mValueAxis.data()->coordToPixel(mValue);
12584 else
12585 qDebug() << Q_FUNC_INFO << "Item position type x is ptPlotCoords, but no axes were defined";
12586 break;
12587 }
12588 }
12589
12590 // determine Y:
12591 switch (mPositionTypeY)
12592 {
12593 case ptAbsolute:
12594 {
12595 result.ry() = mValue;
12596 if (mParentAnchorY)
12597 result.ry() += mParentAnchorY->pixelPosition().y();
12598 break;
12599 }
12600 case ptViewportRatio:
12601 {
12602 result.ry() = mValue*mParentPlot->viewport().height();
12603 if (mParentAnchorY)
12604 result.ry() += mParentAnchorY->pixelPosition().y();
12605 else
12606 result.ry() += mParentPlot->viewport().top();
12607 break;
12608 }
12609 case ptAxisRectRatio:
12610 {
12611 if (mAxisRect)
12612 {
12613 result.ry() = mValue*mAxisRect.data()->height();
12614 if (mParentAnchorY)
12615 result.ry() += mParentAnchorY->pixelPosition().y();
12616 else
12617 result.ry() += mAxisRect.data()->top();
12618 } else
12619 qDebug() << Q_FUNC_INFO << "Item position type y is ptAxisRectRatio, but no axis rect was defined";
12620 break;
12621 }
12622 case ptPlotCoords:
12623 {
12624 if (mKeyAxis && mKeyAxis.data()->orientation() == Qt::Vertical)
12625 result.ry() = mKeyAxis.data()->coordToPixel(mKey);
12626 else if (mValueAxis && mValueAxis.data()->orientation() == Qt::Vertical)
12627 result.ry() = mValueAxis.data()->coordToPixel(mValue);
12628 else
12629 qDebug() << Q_FUNC_INFO << "Item position type y is ptPlotCoords, but no axes were defined";
12630 break;
12631 }
12632 }
12633
12634 return result;
12635}
12636
12637/*!
12638 When \ref setType is \ref ptPlotCoords, this function may be used to specify the axes the
12639 coordinates set with \ref setCoords relate to. By default they are set to the initial xAxis and
12640 yAxis of the QCustomPlot.
12641*/
12642void QCPItemPosition::setAxes(QCPAxis *keyAxis, QCPAxis *valueAxis)
12643{
12644 mKeyAxis = keyAxis;
12645 mValueAxis = valueAxis;
12646}
12647
12648/*!
12649 When \ref setType is \ref ptAxisRectRatio, this function may be used to specify the axis rect the
12650 coordinates set with \ref setCoords relate to. By default this is set to the main axis rect of
12651 the QCustomPlot.
12652*/
12654{
12655 mAxisRect = axisRect;
12656}
12657
12658/*!
12659 Sets the apparent pixel position. This works no matter what type (\ref setType) this
12660 QCPItemPosition is or what parent-child situation it is in, as coordinates are transformed
12661 appropriately, to make the position finally appear at the specified pixel values.
12662
12663 Only if the type is \ref ptAbsolute and no parent anchor is set, this function's effect is
12664 identical to that of \ref setCoords.
12665
12666 \see pixelPosition, setCoords
12667*/
12669{
12670 double x = pixelPosition.x();
12671 double y = pixelPosition.y();
12672
12673 switch (mPositionTypeX)
12674 {
12675 case ptAbsolute:
12676 {
12677 if (mParentAnchorX)
12678 x -= mParentAnchorX->pixelPosition().x();
12679 break;
12680 }
12681 case ptViewportRatio:
12682 {
12683 if (mParentAnchorX)
12684 x -= mParentAnchorX->pixelPosition().x();
12685 else
12686 x -= mParentPlot->viewport().left();
12687 x /= double(mParentPlot->viewport().width());
12688 break;
12689 }
12690 case ptAxisRectRatio:
12691 {
12692 if (mAxisRect)
12693 {
12694 if (mParentAnchorX)
12695 x -= mParentAnchorX->pixelPosition().x();
12696 else
12697 x -= mAxisRect.data()->left();
12698 x /= double(mAxisRect.data()->width());
12699 } else
12700 qDebug() << Q_FUNC_INFO << "Item position type x is ptAxisRectRatio, but no axis rect was defined";
12701 break;
12702 }
12703 case ptPlotCoords:
12704 {
12705 if (mKeyAxis && mKeyAxis.data()->orientation() == Qt::Horizontal)
12706 x = mKeyAxis.data()->pixelToCoord(x);
12707 else if (mValueAxis && mValueAxis.data()->orientation() == Qt::Horizontal)
12708 y = mValueAxis.data()->pixelToCoord(x);
12709 else
12710 qDebug() << Q_FUNC_INFO << "Item position type x is ptPlotCoords, but no axes were defined";
12711 break;
12712 }
12713 }
12714
12715 switch (mPositionTypeY)
12716 {
12717 case ptAbsolute:
12718 {
12719 if (mParentAnchorY)
12720 y -= mParentAnchorY->pixelPosition().y();
12721 break;
12722 }
12723 case ptViewportRatio:
12724 {
12725 if (mParentAnchorY)
12726 y -= mParentAnchorY->pixelPosition().y();
12727 else
12728 y -= mParentPlot->viewport().top();
12729 y /= double(mParentPlot->viewport().height());
12730 break;
12731 }
12732 case ptAxisRectRatio:
12733 {
12734 if (mAxisRect)
12735 {
12736 if (mParentAnchorY)
12737 y -= mParentAnchorY->pixelPosition().y();
12738 else
12739 y -= mAxisRect.data()->top();
12740 y /= double(mAxisRect.data()->height());
12741 } else
12742 qDebug() << Q_FUNC_INFO << "Item position type y is ptAxisRectRatio, but no axis rect was defined";
12743 break;
12744 }
12745 case ptPlotCoords:
12746 {
12747 if (mKeyAxis && mKeyAxis.data()->orientation() == Qt::Vertical)
12748 x = mKeyAxis.data()->pixelToCoord(y);
12749 else if (mValueAxis && mValueAxis.data()->orientation() == Qt::Vertical)
12750 y = mValueAxis.data()->pixelToCoord(y);
12751 else
12752 qDebug() << Q_FUNC_INFO << "Item position type y is ptPlotCoords, but no axes were defined";
12753 break;
12754 }
12755 }
12756
12757 setCoords(x, y);
12758}
12759
12760
12761////////////////////////////////////////////////////////////////////////////////////////////////////
12762//////////////////// QCPAbstractItem
12763////////////////////////////////////////////////////////////////////////////////////////////////////
12764
12765/*! \class QCPAbstractItem
12766 \brief The abstract base class for all items in a plot.
12767
12768 In QCustomPlot, items are supplemental graphical elements that are neither plottables
12769 (QCPAbstractPlottable) nor axes (QCPAxis). While plottables are always tied to two axes and thus
12770 plot coordinates, items can also be placed in absolute coordinates independent of any axes. Each
12771 specific item has at least one QCPItemPosition member which controls the positioning. Some items
12772 are defined by more than one coordinate and thus have two or more QCPItemPosition members (For
12773 example, QCPItemRect has \a topLeft and \a bottomRight).
12774
12775 This abstract base class defines a very basic interface like visibility and clipping. Since this
12776 class is abstract, it can't be instantiated. Use one of the subclasses or create a subclass
12777 yourself to create new items.
12778
12779 The built-in items are:
12780 <table>
12781 <tr><td>QCPItemLine</td><td>A line defined by a start and an end point. May have different ending styles on each side (e.g. arrows).</td></tr>
12782 <tr><td>QCPItemStraightLine</td><td>A straight line defined by a start and a direction point. Unlike QCPItemLine, the straight line is infinitely long and has no endings.</td></tr>
12783 <tr><td>QCPItemCurve</td><td>A curve defined by start, end and two intermediate control points. May have different ending styles on each side (e.g. arrows).</td></tr>
12784 <tr><td>QCPItemRect</td><td>A rectangle</td></tr>
12785 <tr><td>QCPItemEllipse</td><td>An ellipse</td></tr>
12786 <tr><td>QCPItemPixmap</td><td>An arbitrary pixmap</td></tr>
12787 <tr><td>QCPItemText</td><td>A text label</td></tr>
12788 <tr><td>QCPItemBracket</td><td>A bracket which may be used to reference/highlight certain parts in the plot.</td></tr>
12789 <tr><td>QCPItemTracer</td><td>An item that can be attached to a QCPGraph and sticks to its data points, given a key coordinate.</td></tr>
12790 </table>
12791
12792 \section items-clipping Clipping
12793
12794 Items are by default clipped to the main axis rect (they are only visible inside the axis rect).
12795 To make an item visible outside that axis rect, disable clipping via \ref setClipToAxisRect
12796 "setClipToAxisRect(false)".
12797
12798 On the other hand if you want the item to be clipped to a different axis rect, specify it via
12799 \ref setClipAxisRect. This clipAxisRect property of an item is only used for clipping behaviour, and
12800 in principle is independent of the coordinate axes the item might be tied to via its position
12801 members (\ref QCPItemPosition::setAxes). However, it is common that the axis rect for clipping
12802 also contains the axes used for the item positions.
12803
12804 \section items-using Using items
12805
12806 First you instantiate the item you want to use and add it to the plot:
12807 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpitemline-creation-1
12808 by default, the positions of the item are bound to the x- and y-Axis of the plot. So we can just
12809 set the plot coordinates where the line should start/end:
12810 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpitemline-creation-2
12811 If we don't want the line to be positioned in plot coordinates but a different coordinate system,
12812 e.g. absolute pixel positions on the QCustomPlot surface, we need to change the position type like this:
12813 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpitemline-creation-3
12814 Then we can set the coordinates, this time in pixels:
12815 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpitemline-creation-4
12816 and make the line visible on the entire QCustomPlot, by disabling clipping to the axis rect:
12817 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpitemline-creation-5
12818
12819 For more advanced plots, it is even possible to set different types and parent anchors per X/Y
12820 coordinate of an item position, using for example \ref QCPItemPosition::setTypeX or \ref
12821 QCPItemPosition::setParentAnchorX. For details, see the documentation of \ref QCPItemPosition.
12822
12823 \section items-subclassing Creating own items
12824
12825 To create an own item, you implement a subclass of QCPAbstractItem. These are the pure
12826 virtual functions, you must implement:
12827 \li \ref selectTest
12828 \li \ref draw
12829
12830 See the documentation of those functions for what they need to do.
12831
12832 \subsection items-positioning Allowing the item to be positioned
12833
12834 As mentioned, item positions are represented by QCPItemPosition members. Let's assume the new item shall
12835 have only one point as its position (as opposed to two like a rect or multiple like a polygon). You then add
12836 a public member of type QCPItemPosition like so:
12837
12838 \code QCPItemPosition * const myPosition;\endcode
12839
12840 the const makes sure the pointer itself can't be modified from the user of your new item (the QCPItemPosition
12841 instance it points to, can be modified, of course).
12842 The initialization of this pointer is made easy with the \ref createPosition function. Just assign
12843 the return value of this function to each QCPItemPosition in the constructor of your item. \ref createPosition
12844 takes a string which is the name of the position, typically this is identical to the variable name.
12845 For example, the constructor of QCPItemExample could look like this:
12846
12847 \code
12848 QCPItemExample::QCPItemExample(QCustomPlot *parentPlot) :
12849 QCPAbstractItem(parentPlot),
12850 myPosition(createPosition("myPosition"))
12851 {
12852 // other constructor code
12853 }
12854 \endcode
12855
12856 \subsection items-drawing The draw function
12857
12858 To give your item a visual representation, reimplement the \ref draw function and use the passed
12859 QCPPainter to draw the item. You can retrieve the item position in pixel coordinates from the
12860 position member(s) via \ref QCPItemPosition::pixelPosition.
12861
12862 To optimize performance you should calculate a bounding rect first (don't forget to take the pen
12863 width into account), check whether it intersects the \ref clipRect, and only draw the item at all
12864 if this is the case.
12865
12866 \subsection items-selection The selectTest function
12867
12868 Your implementation of the \ref selectTest function may use the helpers \ref
12869 QCPVector2D::distanceSquaredToLine and \ref rectDistance. With these, the implementation of the
12870 selection test becomes significantly simpler for most items. See the documentation of \ref
12871 selectTest for what the function parameters mean and what the function should return.
12872
12873 \subsection anchors Providing anchors
12874
12875 Providing anchors (QCPItemAnchor) starts off like adding a position. First you create a public
12876 member, e.g.
12877
12878 \code QCPItemAnchor * const bottom;\endcode
12879
12880 and create it in the constructor with the \ref createAnchor function, assigning it a name and an
12881 anchor id (an integer enumerating all anchors on the item, you may create an own enum for this).
12882 Since anchors can be placed anywhere, relative to the item's position(s), your item needs to
12883 provide the position of every anchor with the reimplementation of the \ref anchorPixelPosition(int
12884 anchorId) function.
12885
12886 In essence the QCPItemAnchor is merely an intermediary that itself asks your item for the pixel
12887 position when anything attached to the anchor needs to know the coordinates.
12888*/
12889
12890/* start of documentation of inline functions */
12891
12892/*! \fn QList<QCPItemPosition*> QCPAbstractItem::positions() const
12893
12894 Returns all positions of the item in a list.
12895
12896 \see anchors, position
12897*/
12898
12899/*! \fn QList<QCPItemAnchor*> QCPAbstractItem::anchors() const
12900
12901 Returns all anchors of the item in a list. Note that since a position (QCPItemPosition) is always
12902 also an anchor, the list will also contain the positions of this item.
12903
12904 \see positions, anchor
12905*/
12906
12907/* end of documentation of inline functions */
12908/* start documentation of pure virtual functions */
12909
12910/*! \fn void QCPAbstractItem::draw(QCPPainter *painter) = 0
12911 \internal
12912
12913 Draws this item with the provided \a painter.
12914
12915 The cliprect of the provided painter is set to the rect returned by \ref clipRect before this
12916 function is called. The clipRect depends on the clipping settings defined by \ref
12917 setClipToAxisRect and \ref setClipAxisRect.
12918*/
12919
12920/* end documentation of pure virtual functions */
12921/* start documentation of signals */
12922
12923/*! \fn void QCPAbstractItem::selectionChanged(bool selected)
12924 This signal is emitted when the selection state of this item has changed, either by user interaction
12925 or by a direct call to \ref setSelected.
12926*/
12927
12928/* end documentation of signals */
12929
12930/*!
12931 Base class constructor which initializes base class members.
12932*/
12934 QCPLayerable(parentPlot),
12935 mClipToAxisRect(false),
12936 mSelectable(true),
12937 mSelected(false)
12938{
12939 parentPlot->registerItem(this);
12940
12941 QList<QCPAxisRect*> rects = parentPlot->axisRects();
12942 if (!rects.isEmpty())
12943 {
12944 setClipToAxisRect(true);
12945 setClipAxisRect(rects.first());
12946 }
12947}
12948
12949QCPAbstractItem::~QCPAbstractItem()
12950{
12951 // don't delete mPositions because every position is also an anchor and thus in mAnchors
12952 qDeleteAll(mAnchors);
12953}
12954
12955/* can't make this a header inline function, because QPointer breaks with forward declared types, see QTBUG-29588 */
12956QCPAxisRect *QCPAbstractItem::clipAxisRect() const
12957{
12958 return mClipAxisRect.data();
12959}
12960
12961/*!
12962 Sets whether the item shall be clipped to an axis rect or whether it shall be visible on the
12963 entire QCustomPlot. The axis rect can be set with \ref setClipAxisRect.
12964
12965 \see setClipAxisRect
12966*/
12968{
12969 mClipToAxisRect = clip;
12970 if (mClipToAxisRect)
12971 setParentLayerable(mClipAxisRect.data());
12972}
12973
12974/*!
12975 Sets the clip axis rect. It defines the rect that will be used to clip the item when \ref
12976 setClipToAxisRect is set to true.
12977
12978 \see setClipToAxisRect
12979*/
12981{
12982 mClipAxisRect = rect;
12983 if (mClipToAxisRect)
12984 setParentLayerable(mClipAxisRect.data());
12985}
12986
12987/*!
12988 Sets whether the user can (de-)select this item by clicking on the QCustomPlot surface.
12989 (When \ref QCustomPlot::setInteractions contains QCustomPlot::iSelectItems.)
12990
12991 However, even when \a selectable was set to false, it is possible to set the selection manually,
12992 by calling \ref setSelected.
12993
12994 \see QCustomPlot::setInteractions, setSelected
12995*/
12997{
12998 if (mSelectable != selectable)
12999 {
13000 mSelectable = selectable;
13001 emit selectableChanged(mSelectable);
13002 }
13003}
13004
13005/*!
13006 Sets whether this item is selected or not. When selected, it might use a different visual
13007 appearance (e.g. pen and brush), this depends on the specific item though.
13008
13009 The entire selection mechanism for items is handled automatically when \ref
13010 QCustomPlot::setInteractions contains QCustomPlot::iSelectItems. You only need to call this
13011 function when you wish to change the selection state manually.
13012
13013 This function can change the selection state even when \ref setSelectable was set to false.
13014
13015 emits the \ref selectionChanged signal when \a selected is different from the previous selection state.
13016
13017 \see setSelectable, selectTest
13018*/
13020{
13021 if (mSelected != selected)
13022 {
13023 mSelected = selected;
13024 emit selectionChanged(mSelected);
13025 }
13026}
13027
13028/*!
13029 Returns the QCPItemPosition with the specified \a name. If this item doesn't have a position by
13030 that name, returns \c nullptr.
13031
13032 This function provides an alternative way to access item positions. Normally, you access
13033 positions direcly by their member pointers (which typically have the same variable name as \a
13034 name).
13035
13036 \see positions, anchor
13037*/
13039{
13040 foreach (QCPItemPosition *position, mPositions)
13041 {
13042 if (position->name() == name)
13043 return position;
13044 }
13045 qDebug() << Q_FUNC_INFO << "position with name not found:" << name;
13046 return nullptr;
13047}
13048
13049/*!
13050 Returns the QCPItemAnchor with the specified \a name. If this item doesn't have an anchor by
13051 that name, returns \c nullptr.
13052
13053 This function provides an alternative way to access item anchors. Normally, you access
13054 anchors direcly by their member pointers (which typically have the same variable name as \a
13055 name).
13056
13057 \see anchors, position
13058*/
13060{
13061 foreach (QCPItemAnchor *anchor, mAnchors)
13062 {
13063 if (anchor->name() == name)
13064 return anchor;
13065 }
13066 qDebug() << Q_FUNC_INFO << "anchor with name not found:" << name;
13067 return nullptr;
13068}
13069
13070/*!
13071 Returns whether this item has an anchor with the specified \a name.
13072
13073 Note that you can check for positions with this function, too. This is because every position is
13074 also an anchor (QCPItemPosition inherits from QCPItemAnchor).
13075
13076 \see anchor, position
13077*/
13079{
13080 foreach (QCPItemAnchor *anchor, mAnchors)
13081 {
13082 if (anchor->name() == name)
13083 return true;
13084 }
13085 return false;
13086}
13087
13088/*! \internal
13089
13090 Returns the rect the visual representation of this item is clipped to. This depends on the
13091 current setting of \ref setClipToAxisRect as well as the axis rect set with \ref setClipAxisRect.
13092
13093 If the item is not clipped to an axis rect, QCustomPlot's viewport rect is returned.
13094
13095 \see draw
13096*/
13098{
13099 if (mClipToAxisRect && mClipAxisRect)
13100 return mClipAxisRect.data()->rect();
13101 else
13102 return mParentPlot->viewport();
13103}
13104
13105/*! \internal
13106
13107 A convenience function to easily set the QPainter::Antialiased hint on the provided \a painter
13108 before drawing item lines.
13109
13110 This is the antialiasing state the painter passed to the \ref draw method is in by default.
13111
13112 This function takes into account the local setting of the antialiasing flag as well as the
13113 overrides set with \ref QCustomPlot::setAntialiasedElements and \ref
13114 QCustomPlot::setNotAntialiasedElements.
13115
13116 \see setAntialiased
13117*/
13119{
13120 applyAntialiasingHint(painter, mAntialiased, QCP::aeItems);
13121}
13122
13123/*! \internal
13124
13125 A convenience function which returns the selectTest value for a specified \a rect and a specified
13126 click position \a pos. \a filledRect defines whether a click inside the rect should also be
13127 considered a hit or whether only the rect border is sensitive to hits.
13128
13129 This function may be used to help with the implementation of the \ref selectTest function for
13130 specific items.
13131
13132 For example, if your item consists of four rects, call this function four times, once for each
13133 rect, in your \ref selectTest reimplementation. Finally, return the minimum (non -1) of all four
13134 returned values.
13135*/
13136double QCPAbstractItem::rectDistance(const QRectF &rect, const QPointF &pos, bool filledRect) const
13137{
13138 double result = -1;
13139
13140 // distance to border:
13141 const QList<QLineF> lines = QList<QLineF>() << QLineF(rect.topLeft(), rect.topRight()) << QLineF(rect.bottomLeft(), rect.bottomRight())
13142 << QLineF(rect.topLeft(), rect.bottomLeft()) << QLineF(rect.topRight(), rect.bottomRight());
13143 const QCPVector2D posVec(pos);
13144 double minDistSqr = (std::numeric_limits<double>::max)();
13145 foreach (const QLineF &line, lines)
13146 {
13147 double distSqr = posVec.distanceSquaredToLine(line.p1(), line.p2());
13148 if (distSqr < minDistSqr)
13149 minDistSqr = distSqr;
13150 }
13151 result = qSqrt(minDistSqr);
13152
13153 // filled rect, allow click inside to count as hit:
13154 if (filledRect && result > mParentPlot->selectionTolerance()*0.99)
13155 {
13156 if (rect.contains(pos))
13157 result = mParentPlot->selectionTolerance()*0.99;
13158 }
13159 return result;
13160}
13161
13162/*! \internal
13163
13164 Returns the pixel position of the anchor with Id \a anchorId. This function must be reimplemented in
13165 item subclasses if they want to provide anchors (QCPItemAnchor).
13166
13167 For example, if the item has two anchors with id 0 and 1, this function takes one of these anchor
13168 ids and returns the respective pixel points of the specified anchor.
13169
13170 \see createAnchor
13171*/
13173{
13174 qDebug() << Q_FUNC_INFO << "called on item which shouldn't have any anchors (this method not reimplemented). anchorId" << anchorId;
13175 return {};
13176}
13177
13178/*! \internal
13179
13180 Creates a QCPItemPosition, registers it with this item and returns a pointer to it. The specified
13181 \a name must be a unique string that is usually identical to the variable name of the position
13182 member (This is needed to provide the name-based \ref position access to positions).
13183
13184 Don't delete positions created by this function manually, as the item will take care of it.
13185
13186 Use this function in the constructor (initialization list) of the specific item subclass to
13187 create each position member. Don't create QCPItemPositions with \b new yourself, because they
13188 won't be registered with the item properly.
13189
13190 \see createAnchor
13191*/
13193{
13194 if (hasAnchor(name))
13195 qDebug() << Q_FUNC_INFO << "anchor/position with name exists already:" << name;
13196 QCPItemPosition *newPosition = new QCPItemPosition(mParentPlot, this, name);
13197 mPositions.append(newPosition);
13198 mAnchors.append(newPosition); // every position is also an anchor
13199 newPosition->setAxes(mParentPlot->xAxis, mParentPlot->yAxis);
13201 if (mParentPlot->axisRect())
13202 newPosition->setAxisRect(mParentPlot->axisRect());
13203 newPosition->setCoords(0, 0);
13204 return newPosition;
13205}
13206
13207/*! \internal
13208
13209 Creates a QCPItemAnchor, registers it with this item and returns a pointer to it. The specified
13210 \a name must be a unique string that is usually identical to the variable name of the anchor
13211 member (This is needed to provide the name based \ref anchor access to anchors).
13212
13213 The \a anchorId must be a number identifying the created anchor. It is recommended to create an
13214 enum (e.g. "AnchorIndex") for this on each item that uses anchors. This id is used by the anchor
13215 to identify itself when it calls QCPAbstractItem::anchorPixelPosition. That function then returns
13216 the correct pixel coordinates for the passed anchor id.
13217
13218 Don't delete anchors created by this function manually, as the item will take care of it.
13219
13220 Use this function in the constructor (initialization list) of the specific item subclass to
13221 create each anchor member. Don't create QCPItemAnchors with \b new yourself, because then they
13222 won't be registered with the item properly.
13223
13224 \see createPosition
13225*/
13227{
13228 if (hasAnchor(name))
13229 qDebug() << Q_FUNC_INFO << "anchor/position with name exists already:" << name;
13230 QCPItemAnchor *newAnchor = new QCPItemAnchor(mParentPlot, this, name, anchorId);
13231 mAnchors.append(newAnchor);
13232 return newAnchor;
13233}
13234
13235/* inherits documentation from base class */
13236void QCPAbstractItem::selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged)
13237{
13238 Q_UNUSED(event)
13239 Q_UNUSED(details)
13240 if (mSelectable)
13241 {
13242 bool selBefore = mSelected;
13243 setSelected(additive ? !mSelected : true);
13244 if (selectionStateChanged)
13245 *selectionStateChanged = mSelected != selBefore;
13246 }
13247}
13248
13249/* inherits documentation from base class */
13250void QCPAbstractItem::deselectEvent(bool *selectionStateChanged)
13251{
13252 if (mSelectable)
13253 {
13254 bool selBefore = mSelected;
13255 setSelected(false);
13256 if (selectionStateChanged)
13257 *selectionStateChanged = mSelected != selBefore;
13258 }
13259}
13260
13261/* inherits documentation from base class */
13266/* end of 'src/item.cpp' */
13267
13268
13269/* including file 'src/core.cpp' */
13270/* modified 2022-11-06T12:45:56, size 127625 */
13271
13272////////////////////////////////////////////////////////////////////////////////////////////////////
13273//////////////////// QCustomPlot
13274////////////////////////////////////////////////////////////////////////////////////////////////////
13275
13276/*! \class QCustomPlot
13277
13278 \brief The central class of the library. This is the QWidget which displays the plot and
13279 interacts with the user.
13280
13281 For tutorials on how to use QCustomPlot, see the website\n
13282 https://www.qcustomplot.com/
13283*/
13284
13285/* start of documentation of inline functions */
13286
13287/*! \fn QCPSelectionRect *QCustomPlot::selectionRect() const
13288
13289 Allows access to the currently used QCPSelectionRect instance (or subclass thereof), that is used
13290 to handle and draw selection rect interactions (see \ref setSelectionRectMode).
13291
13292 \see setSelectionRect
13293*/
13294
13295/*! \fn QCPLayoutGrid *QCustomPlot::plotLayout() const
13296
13297 Returns the top level layout of this QCustomPlot instance. It is a \ref QCPLayoutGrid, initially containing just
13298 one cell with the main QCPAxisRect inside.
13299*/
13300
13301/* end of documentation of inline functions */
13302/* start of documentation of signals */
13303
13304/*! \fn void QCustomPlot::mouseDoubleClick(QMouseEvent *event)
13305
13306 This signal is emitted when the QCustomPlot receives a mouse double click event.
13307*/
13308
13309/*! \fn void QCustomPlot::mousePress(QMouseEvent *event)
13310
13311 This signal is emitted when the QCustomPlot receives a mouse press event.
13312
13313 It is emitted before QCustomPlot handles any other mechanism like range dragging. So a slot
13314 connected to this signal can still influence the behaviour e.g. with \ref QCPAxisRect::setRangeDrag or \ref
13315 QCPAxisRect::setRangeDragAxes.
13316*/
13317
13318/*! \fn void QCustomPlot::mouseMove(QMouseEvent *event)
13319
13320 This signal is emitted when the QCustomPlot receives a mouse move event.
13321
13322 It is emitted before QCustomPlot handles any other mechanism like range dragging. So a slot
13323 connected to this signal can still influence the behaviour e.g. with \ref QCPAxisRect::setRangeDrag or \ref
13324 QCPAxisRect::setRangeDragAxes.
13325
13326 \warning It is discouraged to change the drag-axes with \ref QCPAxisRect::setRangeDragAxes here,
13327 because the dragging starting point was saved the moment the mouse was pressed. Thus it only has
13328 a meaning for the range drag axes that were set at that moment. If you want to change the drag
13329 axes, consider doing this in the \ref mousePress signal instead.
13330*/
13331
13332/*! \fn void QCustomPlot::mouseRelease(QMouseEvent *event)
13333
13334 This signal is emitted when the QCustomPlot receives a mouse release event.
13335
13336 It is emitted before QCustomPlot handles any other mechanisms like object selection. So a
13337 slot connected to this signal can still influence the behaviour e.g. with \ref setInteractions or
13338 \ref QCPAbstractPlottable::setSelectable.
13339*/
13340
13341/*! \fn void QCustomPlot::mouseWheel(QMouseEvent *event)
13342
13343 This signal is emitted when the QCustomPlot receives a mouse wheel event.
13344
13345 It is emitted before QCustomPlot handles any other mechanisms like range zooming. So a slot
13346 connected to this signal can still influence the behaviour e.g. with \ref QCPAxisRect::setRangeZoom, \ref
13347 QCPAxisRect::setRangeZoomAxes or \ref QCPAxisRect::setRangeZoomFactor.
13348*/
13349
13350/*! \fn void QCustomPlot::plottableClick(QCPAbstractPlottable *plottable, int dataIndex, QMouseEvent *event)
13351
13352 This signal is emitted when a plottable is clicked.
13353
13354 \a event is the mouse event that caused the click and \a plottable is the plottable that received
13355 the click. The parameter \a dataIndex indicates the data point that was closest to the click
13356 position.
13357
13358 \see plottableDoubleClick
13359*/
13360
13361/*! \fn void QCustomPlot::plottableDoubleClick(QCPAbstractPlottable *plottable, int dataIndex, QMouseEvent *event)
13362
13363 This signal is emitted when a plottable is double clicked.
13364
13365 \a event is the mouse event that caused the click and \a plottable is the plottable that received
13366 the click. The parameter \a dataIndex indicates the data point that was closest to the click
13367 position.
13368
13369 \see plottableClick
13370*/
13371
13372/*! \fn void QCustomPlot::itemClick(QCPAbstractItem *item, QMouseEvent *event)
13373
13374 This signal is emitted when an item is clicked.
13375
13376 \a event is the mouse event that caused the click and \a item is the item that received the
13377 click.
13378
13379 \see itemDoubleClick
13380*/
13381
13382/*! \fn void QCustomPlot::itemDoubleClick(QCPAbstractItem *item, QMouseEvent *event)
13383
13384 This signal is emitted when an item is double clicked.
13385
13386 \a event is the mouse event that caused the click and \a item is the item that received the
13387 click.
13388
13389 \see itemClick
13390*/
13391
13392/*! \fn void QCustomPlot::axisClick(QCPAxis *axis, QCPAxis::SelectablePart part, QMouseEvent *event)
13393
13394 This signal is emitted when an axis is clicked.
13395
13396 \a event is the mouse event that caused the click, \a axis is the axis that received the click and
13397 \a part indicates the part of the axis that was clicked.
13398
13399 \see axisDoubleClick
13400*/
13401
13402/*! \fn void QCustomPlot::axisDoubleClick(QCPAxis *axis, QCPAxis::SelectablePart part, QMouseEvent *event)
13403
13404 This signal is emitted when an axis is double clicked.
13405
13406 \a event is the mouse event that caused the click, \a axis is the axis that received the click and
13407 \a part indicates the part of the axis that was clicked.
13408
13409 \see axisClick
13410*/
13411
13412/*! \fn void QCustomPlot::legendClick(QCPLegend *legend, QCPAbstractLegendItem *item, QMouseEvent *event)
13413
13414 This signal is emitted when a legend (item) is clicked.
13415
13416 \a event is the mouse event that caused the click, \a legend is the legend that received the
13417 click and \a item is the legend item that received the click. If only the legend and no item is
13418 clicked, \a item is \c nullptr. This happens for a click inside the legend padding or the space
13419 between two items.
13420
13421 \see legendDoubleClick
13422*/
13423
13424/*! \fn void QCustomPlot::legendDoubleClick(QCPLegend *legend, QCPAbstractLegendItem *item, QMouseEvent *event)
13425
13426 This signal is emitted when a legend (item) is double clicked.
13427
13428 \a event is the mouse event that caused the click, \a legend is the legend that received the
13429 click and \a item is the legend item that received the click. If only the legend and no item is
13430 clicked, \a item is \c nullptr. This happens for a click inside the legend padding or the space
13431 between two items.
13432
13433 \see legendClick
13434*/
13435
13436/*! \fn void QCustomPlot::selectionChangedByUser()
13437
13438 This signal is emitted after the user has changed the selection in the QCustomPlot, e.g. by
13439 clicking. It is not emitted when the selection state of an object has changed programmatically by
13440 a direct call to <tt>setSelected()</tt>/<tt>setSelection()</tt> on an object or by calling \ref
13441 deselectAll.
13442
13443 In addition to this signal, selectable objects also provide individual signals, for example \ref
13444 QCPAxis::selectionChanged or \ref QCPAbstractPlottable::selectionChanged. Note that those signals
13445 are emitted even if the selection state is changed programmatically.
13446
13447 See the documentation of \ref setInteractions for details about the selection mechanism.
13448
13449 \see selectedPlottables, selectedGraphs, selectedItems, selectedAxes, selectedLegends
13450*/
13451
13452/*! \fn void QCustomPlot::beforeReplot()
13453
13454 This signal is emitted immediately before a replot takes place (caused by a call to the slot \ref
13455 replot).
13456
13457 It is safe to mutually connect the replot slot with this signal on two QCustomPlots to make them
13458 replot synchronously, it won't cause an infinite recursion.
13459
13460 \see replot, afterReplot, afterLayout
13461*/
13462
13463/*! \fn void QCustomPlot::afterLayout()
13464
13465 This signal is emitted immediately after the layout step has been completed, which occurs right
13466 before drawing the plot. This is typically during a call to \ref replot, and in such cases this
13467 signal is emitted in between the signals \ref beforeReplot and \ref afterReplot. Unlike those
13468 signals however, this signal is also emitted during off-screen painting, such as when calling
13469 \ref toPixmap or \ref savePdf.
13470
13471 The layout step queries all layouts and layout elements in the plot for their proposed size and
13472 arranges the objects accordingly as preparation for the subsequent drawing step. Through this
13473 signal, you have the opportunity to update certain things in your plot that depend crucially on
13474 the exact dimensions/positioning of layout elements such as axes and axis rects.
13475
13476 \warning However, changing any parameters of this QCustomPlot instance which would normally
13477 affect the layouting (e.g. axis range order of magnitudes, tick label sizes, etc.) will not issue
13478 a second run of the layout step. It will propagate directly to the draw step and may cause
13479 graphical inconsistencies such as overlapping objects, if sizes or positions have changed.
13480
13481 \see updateLayout, beforeReplot, afterReplot
13482*/
13483
13484/*! \fn void QCustomPlot::afterReplot()
13485
13486 This signal is emitted immediately after a replot has taken place (caused by a call to the slot \ref
13487 replot).
13488
13489 It is safe to mutually connect the replot slot with this signal on two QCustomPlots to make them
13490 replot synchronously, it won't cause an infinite recursion.
13491
13492 \see replot, beforeReplot, afterLayout
13493*/
13494
13495/* end of documentation of signals */
13496/* start of documentation of public members */
13497
13498/*! \var QCPAxis *QCustomPlot::xAxis
13499
13500 A pointer to the primary x Axis (bottom) of the main axis rect of the plot.
13501
13502 QCustomPlot offers convenient pointers to the axes (\ref xAxis, \ref yAxis, \ref xAxis2, \ref
13503 yAxis2) and the \ref legend. They make it very easy working with plots that only have a single
13504 axis rect and at most one axis at each axis rect side. If you use \link thelayoutsystem the
13505 layout system\endlink to add multiple axis rects or multiple axes to one side, use the \ref
13506 QCPAxisRect::axis interface to access the new axes. If one of the four default axes or the
13507 default legend is removed due to manipulation of the layout system (e.g. by removing the main
13508 axis rect), the corresponding pointers become \c nullptr.
13509
13510 If an axis convenience pointer is currently \c nullptr and a new axis rect or a corresponding
13511 axis is added in the place of the main axis rect, QCustomPlot resets the convenience pointers to
13512 the according new axes. Similarly the \ref legend convenience pointer will be reset if a legend
13513 is added after the main legend was removed before.
13514*/
13515
13516/*! \var QCPAxis *QCustomPlot::yAxis
13517
13518 A pointer to the primary y Axis (left) of the main axis rect of the plot.
13519
13520 QCustomPlot offers convenient pointers to the axes (\ref xAxis, \ref yAxis, \ref xAxis2, \ref
13521 yAxis2) and the \ref legend. They make it very easy working with plots that only have a single
13522 axis rect and at most one axis at each axis rect side. If you use \link thelayoutsystem the
13523 layout system\endlink to add multiple axis rects or multiple axes to one side, use the \ref
13524 QCPAxisRect::axis interface to access the new axes. If one of the four default axes or the
13525 default legend is removed due to manipulation of the layout system (e.g. by removing the main
13526 axis rect), the corresponding pointers become \c nullptr.
13527
13528 If an axis convenience pointer is currently \c nullptr and a new axis rect or a corresponding
13529 axis is added in the place of the main axis rect, QCustomPlot resets the convenience pointers to
13530 the according new axes. Similarly the \ref legend convenience pointer will be reset if a legend
13531 is added after the main legend was removed before.
13532*/
13533
13534/*! \var QCPAxis *QCustomPlot::xAxis2
13535
13536 A pointer to the secondary x Axis (top) of the main axis rect of the plot. Secondary axes are
13537 invisible by default. Use QCPAxis::setVisible to change this (or use \ref
13538 QCPAxisRect::setupFullAxesBox).
13539
13540 QCustomPlot offers convenient pointers to the axes (\ref xAxis, \ref yAxis, \ref xAxis2, \ref
13541 yAxis2) and the \ref legend. They make it very easy working with plots that only have a single
13542 axis rect and at most one axis at each axis rect side. If you use \link thelayoutsystem the
13543 layout system\endlink to add multiple axis rects or multiple axes to one side, use the \ref
13544 QCPAxisRect::axis interface to access the new axes. If one of the four default axes or the
13545 default legend is removed due to manipulation of the layout system (e.g. by removing the main
13546 axis rect), the corresponding pointers become \c nullptr.
13547
13548 If an axis convenience pointer is currently \c nullptr and a new axis rect or a corresponding
13549 axis is added in the place of the main axis rect, QCustomPlot resets the convenience pointers to
13550 the according new axes. Similarly the \ref legend convenience pointer will be reset if a legend
13551 is added after the main legend was removed before.
13552*/
13553
13554/*! \var QCPAxis *QCustomPlot::yAxis2
13555
13556 A pointer to the secondary y Axis (right) of the main axis rect of the plot. Secondary axes are
13557 invisible by default. Use QCPAxis::setVisible to change this (or use \ref
13558 QCPAxisRect::setupFullAxesBox).
13559
13560 QCustomPlot offers convenient pointers to the axes (\ref xAxis, \ref yAxis, \ref xAxis2, \ref
13561 yAxis2) and the \ref legend. They make it very easy working with plots that only have a single
13562 axis rect and at most one axis at each axis rect side. If you use \link thelayoutsystem the
13563 layout system\endlink to add multiple axis rects or multiple axes to one side, use the \ref
13564 QCPAxisRect::axis interface to access the new axes. If one of the four default axes or the
13565 default legend is removed due to manipulation of the layout system (e.g. by removing the main
13566 axis rect), the corresponding pointers become \c nullptr.
13567
13568 If an axis convenience pointer is currently \c nullptr and a new axis rect or a corresponding
13569 axis is added in the place of the main axis rect, QCustomPlot resets the convenience pointers to
13570 the according new axes. Similarly the \ref legend convenience pointer will be reset if a legend
13571 is added after the main legend was removed before.
13572*/
13573
13574/*! \var QCPLegend *QCustomPlot::legend
13575
13576 A pointer to the default legend of the main axis rect. The legend is invisible by default. Use
13577 QCPLegend::setVisible to change this.
13578
13579 QCustomPlot offers convenient pointers to the axes (\ref xAxis, \ref yAxis, \ref xAxis2, \ref
13580 yAxis2) and the \ref legend. They make it very easy working with plots that only have a single
13581 axis rect and at most one axis at each axis rect side. If you use \link thelayoutsystem the
13582 layout system\endlink to add multiple legends to the plot, use the layout system interface to
13583 access the new legend. For example, legends can be placed inside an axis rect's \ref
13584 QCPAxisRect::insetLayout "inset layout", and must then also be accessed via the inset layout. If
13585 the default legend is removed due to manipulation of the layout system (e.g. by removing the main
13586 axis rect), the corresponding pointer becomes \c nullptr.
13587
13588 If an axis convenience pointer is currently \c nullptr and a new axis rect or a corresponding
13589 axis is added in the place of the main axis rect, QCustomPlot resets the convenience pointers to
13590 the according new axes. Similarly the \ref legend convenience pointer will be reset if a legend
13591 is added after the main legend was removed before.
13592*/
13593
13594/* end of documentation of public members */
13595
13596/*!
13597 Constructs a QCustomPlot and sets reasonable default values.
13598*/
13600 QWidget(parent),
13601 xAxis(nullptr),
13602 yAxis(nullptr),
13603 xAxis2(nullptr),
13604 yAxis2(nullptr),
13605 legend(nullptr),
13606 mBufferDevicePixelRatio(1.0), // will be adapted to true value below
13607 mPlotLayout(nullptr),
13608 mAutoAddPlottableToLegend(true),
13609 mAntialiasedElements(QCP::aeNone),
13610 mNotAntialiasedElements(QCP::aeNone),
13611 mInteractions(QCP::iNone),
13612 mSelectionTolerance(8),
13613 mNoAntialiasingOnDrag(false),
13614 mBackgroundBrush(Qt::white, Qt::SolidPattern),
13615 mBackgroundScaled(true),
13616 mBackgroundScaledMode(Qt::KeepAspectRatioByExpanding),
13617 mCurrentLayer(nullptr),
13618 mPlottingHints(QCP::phCacheLabels|QCP::phImmediateRefresh),
13619 mMultiSelectModifier(Qt::ControlModifier),
13620 mSelectionRectMode(QCP::srmNone),
13621 mSelectionRect(nullptr),
13622 mOpenGl(false),
13623 mMouseHasMoved(false),
13624 mMouseEventLayerable(nullptr),
13625 mMouseSignalLayerable(nullptr),
13626 mReplotting(false),
13627 mReplotQueued(false),
13628 mReplotTime(0),
13629 mReplotTimeAverage(0),
13630 mOpenGlMultisamples(16),
13631 mOpenGlAntialiasedElementsBackup(QCP::aeNone),
13632 mOpenGlCacheLabelsBackup(true)
13633{
13636 setMouseTracking(true);
13637 QLocale currentLocale = locale();
13639 setLocale(currentLocale);
13640#ifdef QCP_DEVICEPIXELRATIO_SUPPORTED
13641# ifdef QCP_DEVICEPIXELRATIO_FLOAT
13643# else
13645# endif
13646#endif
13647
13648 mOpenGlAntialiasedElementsBackup = mAntialiasedElements;
13649 mOpenGlCacheLabelsBackup = mPlottingHints.testFlag(QCP::phCacheLabels);
13650 // create initial layers:
13651 mLayers.append(new QCPLayer(this, QLatin1String("background")));
13652 mLayers.append(new QCPLayer(this, QLatin1String("grid")));
13653 mLayers.append(new QCPLayer(this, QLatin1String("main")));
13654 mLayers.append(new QCPLayer(this, QLatin1String("axes")));
13655 mLayers.append(new QCPLayer(this, QLatin1String("legend")));
13656 mLayers.append(new QCPLayer(this, QLatin1String("overlay")));
13660
13661 // create initial layout, axis rect and legend:
13662 mPlotLayout = new QCPLayoutGrid;
13663 mPlotLayout->initializeParentPlot(this);
13664 mPlotLayout->setParent(this); // important because if parent is QWidget, QCPLayout::sizeConstraintsChanged will call QWidget::updateGeometry
13665 mPlotLayout->setLayer(QLatin1String("main"));
13666 QCPAxisRect *defaultAxisRect = new QCPAxisRect(this, true);
13667 mPlotLayout->addElement(0, 0, defaultAxisRect);
13668 xAxis = defaultAxisRect->axis(QCPAxis::atBottom);
13669 yAxis = defaultAxisRect->axis(QCPAxis::atLeft);
13670 xAxis2 = defaultAxisRect->axis(QCPAxis::atTop);
13671 yAxis2 = defaultAxisRect->axis(QCPAxis::atRight);
13672 legend = new QCPLegend;
13673 legend->setVisible(false);
13675 defaultAxisRect->insetLayout()->setMargins(QMargins(12, 12, 12, 12));
13676
13677 defaultAxisRect->setLayer(QLatin1String("background"));
13678 xAxis->setLayer(QLatin1String("axes"));
13679 yAxis->setLayer(QLatin1String("axes"));
13680 xAxis2->setLayer(QLatin1String("axes"));
13681 yAxis2->setLayer(QLatin1String("axes"));
13682 xAxis->grid()->setLayer(QLatin1String("grid"));
13683 yAxis->grid()->setLayer(QLatin1String("grid"));
13684 xAxis2->grid()->setLayer(QLatin1String("grid"));
13685 yAxis2->grid()->setLayer(QLatin1String("grid"));
13686 legend->setLayer(QLatin1String("legend"));
13687
13688 // create selection rect instance:
13689 mSelectionRect = new QCPSelectionRect(this);
13690 mSelectionRect->setLayer(QLatin1String("overlay"));
13691
13692 setViewport(rect()); // needs to be called after mPlotLayout has been created
13693
13695}
13696
13697QCustomPlot::~QCustomPlot()
13698{
13700 clearItems();
13701
13702 if (mPlotLayout)
13703 {
13704 delete mPlotLayout;
13705 mPlotLayout = nullptr;
13706 }
13707
13708 mCurrentLayer = nullptr;
13709 qDeleteAll(mLayers); // don't use removeLayer, because it would prevent the last layer to be removed
13710 mLayers.clear();
13711}
13712
13713/*!
13714 Sets which elements are forcibly drawn antialiased as an \a or combination of QCP::AntialiasedElement.
13715
13716 This overrides the antialiasing settings for whole element groups, normally controlled with the
13717 \a setAntialiasing function on the individual elements. If an element is neither specified in
13718 \ref setAntialiasedElements nor in \ref setNotAntialiasedElements, the antialiasing setting on
13719 each individual element instance is used.
13720
13721 For example, if \a antialiasedElements contains \ref QCP::aePlottables, all plottables will be
13722 drawn antialiased, no matter what the specific QCPAbstractPlottable::setAntialiased value was set
13723 to.
13724
13725 if an element in \a antialiasedElements is already set in \ref setNotAntialiasedElements, it is
13726 removed from there.
13727
13728 \see setNotAntialiasedElements
13729*/
13731{
13732 mAntialiasedElements = antialiasedElements;
13733
13734 // make sure elements aren't in mNotAntialiasedElements and mAntialiasedElements simultaneously:
13735 if ((mNotAntialiasedElements & mAntialiasedElements) != 0)
13736 mNotAntialiasedElements |= ~mAntialiasedElements;
13737}
13738
13739/*!
13740 Sets whether the specified \a antialiasedElement is forcibly drawn antialiased.
13741
13742 See \ref setAntialiasedElements for details.
13743
13744 \see setNotAntialiasedElement
13745*/
13747{
13748 if (!enabled && mAntialiasedElements.testFlag(antialiasedElement))
13749 mAntialiasedElements &= ~antialiasedElement;
13750 else if (enabled && !mAntialiasedElements.testFlag(antialiasedElement))
13751 mAntialiasedElements |= antialiasedElement;
13752
13753 // make sure elements aren't in mNotAntialiasedElements and mAntialiasedElements simultaneously:
13754 if ((mNotAntialiasedElements & mAntialiasedElements) != 0)
13755 mNotAntialiasedElements |= ~mAntialiasedElements;
13756}
13757
13758/*!
13759 Sets which elements are forcibly drawn not antialiased as an \a or combination of
13760 QCP::AntialiasedElement.
13761
13762 This overrides the antialiasing settings for whole element groups, normally controlled with the
13763 \a setAntialiasing function on the individual elements. If an element is neither specified in
13764 \ref setAntialiasedElements nor in \ref setNotAntialiasedElements, the antialiasing setting on
13765 each individual element instance is used.
13766
13767 For example, if \a notAntialiasedElements contains \ref QCP::aePlottables, no plottables will be
13768 drawn antialiased, no matter what the specific QCPAbstractPlottable::setAntialiased value was set
13769 to.
13770
13771 if an element in \a notAntialiasedElements is already set in \ref setAntialiasedElements, it is
13772 removed from there.
13773
13774 \see setAntialiasedElements
13775*/
13777{
13778 mNotAntialiasedElements = notAntialiasedElements;
13779
13780 // make sure elements aren't in mNotAntialiasedElements and mAntialiasedElements simultaneously:
13781 if ((mNotAntialiasedElements & mAntialiasedElements) != 0)
13782 mAntialiasedElements |= ~mNotAntialiasedElements;
13783}
13784
13785/*!
13786 Sets whether the specified \a notAntialiasedElement is forcibly drawn not antialiased.
13787
13788 See \ref setNotAntialiasedElements for details.
13789
13790 \see setAntialiasedElement
13791*/
13793{
13794 if (!enabled && mNotAntialiasedElements.testFlag(notAntialiasedElement))
13795 mNotAntialiasedElements &= ~notAntialiasedElement;
13796 else if (enabled && !mNotAntialiasedElements.testFlag(notAntialiasedElement))
13797 mNotAntialiasedElements |= notAntialiasedElement;
13798
13799 // make sure elements aren't in mNotAntialiasedElements and mAntialiasedElements simultaneously:
13800 if ((mNotAntialiasedElements & mAntialiasedElements) != 0)
13801 mAntialiasedElements |= ~mNotAntialiasedElements;
13802}
13803
13804/*!
13805 If set to true, adding a plottable (e.g. a graph) to the QCustomPlot automatically also adds the
13806 plottable to the legend (QCustomPlot::legend).
13807
13808 \see addGraph, QCPLegend::addItem
13809*/
13811{
13812 mAutoAddPlottableToLegend = on;
13813}
13814
13815/*!
13816 Sets the possible interactions of this QCustomPlot as an or-combination of \ref QCP::Interaction
13817 enums. There are the following types of interactions:
13818
13819 <b>Axis range manipulation</b> is controlled via \ref QCP::iRangeDrag and \ref QCP::iRangeZoom. When the
13820 respective interaction is enabled, the user may drag axes ranges and zoom with the mouse wheel.
13821 For details how to control which axes the user may drag/zoom and in what orientations, see \ref
13822 QCPAxisRect::setRangeDrag, \ref QCPAxisRect::setRangeZoom, \ref QCPAxisRect::setRangeDragAxes,
13823 \ref QCPAxisRect::setRangeZoomAxes.
13824
13825 <b>Plottable data selection</b> is controlled by \ref QCP::iSelectPlottables. If \ref
13826 QCP::iSelectPlottables is set, the user may select plottables (graphs, curves, bars,...) and
13827 their data by clicking on them or in their vicinity (\ref setSelectionTolerance). Whether the
13828 user can actually select a plottable and its data can further be restricted with the \ref
13829 QCPAbstractPlottable::setSelectable method on the specific plottable. For details, see the
13830 special page about the \ref dataselection "data selection mechanism". To retrieve a list of all
13831 currently selected plottables, call \ref selectedPlottables. If you're only interested in
13832 QCPGraphs, you may use the convenience function \ref selectedGraphs.
13833
13834 <b>Item selection</b> is controlled by \ref QCP::iSelectItems. If \ref QCP::iSelectItems is set, the user
13835 may select items (QCPItemLine, QCPItemText,...) by clicking on them or in their vicinity. To find
13836 out whether a specific item is selected, call QCPAbstractItem::selected(). To retrieve a list of
13837 all currently selected items, call \ref selectedItems.
13838
13839 <b>Axis selection</b> is controlled with \ref QCP::iSelectAxes. If \ref QCP::iSelectAxes is set, the user
13840 may select parts of the axes by clicking on them. What parts exactly (e.g. Axis base line, tick
13841 labels, axis label) are selectable can be controlled via \ref QCPAxis::setSelectableParts for
13842 each axis. To retrieve a list of all axes that currently contain selected parts, call \ref
13843 selectedAxes. Which parts of an axis are selected, can be retrieved with QCPAxis::selectedParts().
13844
13845 <b>Legend selection</b> is controlled with \ref QCP::iSelectLegend. If this is set, the user may
13846 select the legend itself or individual items by clicking on them. What parts exactly are
13847 selectable can be controlled via \ref QCPLegend::setSelectableParts. To find out whether the
13848 legend or any of its child items are selected, check the value of QCPLegend::selectedParts. To
13849 find out which child items are selected, call \ref QCPLegend::selectedItems.
13850
13851 <b>All other selectable elements</b> The selection of all other selectable objects (e.g.
13852 QCPTextElement, or your own layerable subclasses) is controlled with \ref QCP::iSelectOther. If set, the
13853 user may select those objects by clicking on them. To find out which are currently selected, you
13854 need to check their selected state explicitly.
13855
13856 If the selection state has changed by user interaction, the \ref selectionChangedByUser signal is
13857 emitted. Each selectable object additionally emits an individual selectionChanged signal whenever
13858 their selection state has changed, i.e. not only by user interaction.
13859
13860 To allow multiple objects to be selected by holding the selection modifier (\ref
13861 setMultiSelectModifier), set the flag \ref QCP::iMultiSelect.
13862
13863 \note In addition to the selection mechanism presented here, QCustomPlot always emits
13864 corresponding signals, when an object is clicked or double clicked. see \ref plottableClick and
13865 \ref plottableDoubleClick for example.
13866
13867 \see setInteraction, setSelectionTolerance
13868*/
13870{
13871 mInteractions = interactions;
13872}
13873
13874/*!
13875 Sets the single \a interaction of this QCustomPlot to \a enabled.
13876
13877 For details about the interaction system, see \ref setInteractions.
13878
13879 \see setInteractions
13880*/
13881void QCustomPlot::setInteraction(const QCP::Interaction &interaction, bool enabled)
13882{
13883 if (!enabled && mInteractions.testFlag(interaction))
13884 mInteractions &= ~interaction;
13885 else if (enabled && !mInteractions.testFlag(interaction))
13886 mInteractions |= interaction;
13887}
13888
13889/*!
13890 Sets the tolerance that is used to decide whether a click selects an object (e.g. a plottable) or
13891 not.
13892
13893 If the user clicks in the vicinity of the line of e.g. a QCPGraph, it's only regarded as a
13894 potential selection when the minimum distance between the click position and the graph line is
13895 smaller than \a pixels. Objects that are defined by an area (e.g. QCPBars) only react to clicks
13896 directly inside the area and ignore this selection tolerance. In other words, it only has meaning
13897 for parts of objects that are too thin to exactly hit with a click and thus need such a
13898 tolerance.
13899
13900 \see setInteractions, QCPLayerable::selectTest
13901*/
13903{
13904 mSelectionTolerance = pixels;
13905}
13906
13907/*!
13908 Sets whether antialiasing is disabled for this QCustomPlot while the user is dragging axes
13909 ranges. If many objects, especially plottables, are drawn antialiased, this greatly improves
13910 performance during dragging. Thus it creates a more responsive user experience. As soon as the
13911 user stops dragging, the last replot is done with normal antialiasing, to restore high image
13912 quality.
13913
13914 \see setAntialiasedElements, setNotAntialiasedElements
13915*/
13917{
13918 mNoAntialiasingOnDrag = enabled;
13919}
13920
13921/*!
13922 Sets the plotting hints for this QCustomPlot instance as an \a or combination of QCP::PlottingHint.
13923
13924 \see setPlottingHint
13925*/
13927{
13928 mPlottingHints = hints;
13929}
13930
13931/*!
13932 Sets the specified plotting \a hint to \a enabled.
13933
13934 \see setPlottingHints
13935*/
13937{
13938 QCP::PlottingHints newHints = mPlottingHints;
13939 if (!enabled)
13940 newHints &= ~hint;
13941 else
13942 newHints |= hint;
13943
13944 if (newHints != mPlottingHints)
13945 setPlottingHints(newHints);
13946}
13947
13948/*!
13949 Sets the keyboard modifier that will be recognized as multi-select-modifier.
13950
13951 If \ref QCP::iMultiSelect is specified in \ref setInteractions, the user may select multiple
13952 objects (or data points) by clicking on them one after the other while holding down \a modifier.
13953
13954 By default the multi-select-modifier is set to Qt::ControlModifier.
13955
13956 \see setInteractions
13957*/
13959{
13960 mMultiSelectModifier = modifier;
13961}
13962
13963/*!
13964 Sets how QCustomPlot processes mouse click-and-drag interactions by the user.
13965
13966 If \a mode is \ref QCP::srmNone, the mouse drag is forwarded to the underlying objects. For
13967 example, QCPAxisRect may process a mouse drag by dragging axis ranges, see \ref
13968 QCPAxisRect::setRangeDrag. If \a mode is not \ref QCP::srmNone, the current selection rect (\ref
13969 selectionRect) becomes activated and allows e.g. rect zooming and data point selection.
13970
13971 If you wish to provide your user both with axis range dragging and data selection/range zooming,
13972 use this method to switch between the modes just before the interaction is processed, e.g. in
13973 reaction to the \ref mousePress or \ref mouseMove signals. For example you could check whether
13974 the user is holding a certain keyboard modifier, and then decide which \a mode shall be set.
13975
13976 If a selection rect interaction is currently active, and \a mode is set to \ref QCP::srmNone, the
13977 interaction is canceled (\ref QCPSelectionRect::cancel). Switching between any of the other modes
13978 will keep the selection rect active. Upon completion of the interaction, the behaviour is as
13979 defined by the currently set \a mode, not the mode that was set when the interaction started.
13980
13981 \see setInteractions, setSelectionRect, QCPSelectionRect
13982*/
13984{
13985 if (mSelectionRect)
13986 {
13987 if (mode == QCP::srmNone)
13988 mSelectionRect->cancel(); // when switching to none, we immediately want to abort a potentially active selection rect
13989
13990 // disconnect old connections:
13991 if (mSelectionRectMode == QCP::srmSelect)
13992 disconnect(mSelectionRect, SIGNAL(accepted(QRect,QMouseEvent*)), this, SLOT(processRectSelection(QRect,QMouseEvent*)));
13993 else if (mSelectionRectMode == QCP::srmZoom)
13994 disconnect(mSelectionRect, SIGNAL(accepted(QRect,QMouseEvent*)), this, SLOT(processRectZoom(QRect,QMouseEvent*)));
13995
13996 // establish new ones:
13997 if (mode == QCP::srmSelect)
13998 connect(mSelectionRect, SIGNAL(accepted(QRect,QMouseEvent*)), this, SLOT(processRectSelection(QRect,QMouseEvent*)));
13999 else if (mode == QCP::srmZoom)
14000 connect(mSelectionRect, SIGNAL(accepted(QRect,QMouseEvent*)), this, SLOT(processRectZoom(QRect,QMouseEvent*)));
14001 }
14002
14003 mSelectionRectMode = mode;
14004}
14005
14006/*!
14007 Sets the \ref QCPSelectionRect instance that QCustomPlot will use if \a mode is not \ref
14008 QCP::srmNone and the user performs a click-and-drag interaction. QCustomPlot takes ownership of
14009 the passed \a selectionRect. It can be accessed later via \ref selectionRect.
14010
14011 This method is useful if you wish to replace the default QCPSelectionRect instance with an
14012 instance of a QCPSelectionRect subclass, to introduce custom behaviour of the selection rect.
14013
14014 \see setSelectionRectMode
14015*/
14017{
14018 delete mSelectionRect;
14019
14020 mSelectionRect = selectionRect;
14021
14022 if (mSelectionRect)
14023 {
14024 // establish connections with new selection rect:
14025 if (mSelectionRectMode == QCP::srmSelect)
14026 connect(mSelectionRect, SIGNAL(accepted(QRect,QMouseEvent*)), this, SLOT(processRectSelection(QRect,QMouseEvent*)));
14027 else if (mSelectionRectMode == QCP::srmZoom)
14028 connect(mSelectionRect, SIGNAL(accepted(QRect,QMouseEvent*)), this, SLOT(processRectZoom(QRect,QMouseEvent*)));
14029 }
14030}
14031
14032/*!
14033 \warning This is still an experimental feature and its performance depends on the system that it
14034 runs on. Having multiple QCustomPlot widgets in one application with enabled OpenGL rendering
14035 might cause context conflicts on some systems.
14036
14037 This method allows to enable OpenGL plot rendering, for increased plotting performance of
14038 graphically demanding plots (thick lines, translucent fills, etc.).
14039
14040 If \a enabled is set to true, QCustomPlot will try to initialize OpenGL and, if successful,
14041 continue plotting with hardware acceleration. The parameter \a multisampling controls how many
14042 samples will be used per pixel, it essentially controls the antialiasing quality. If \a
14043 multisampling is set too high for the current graphics hardware, the maximum allowed value will
14044 be used.
14045
14046 You can test whether switching to OpenGL rendering was successful by checking whether the
14047 according getter \a QCustomPlot::openGl() returns true. If the OpenGL initialization fails,
14048 rendering continues with the regular software rasterizer, and an according qDebug output is
14049 generated.
14050
14051 If switching to OpenGL was successful, this method disables label caching (\ref setPlottingHint
14052 "setPlottingHint(QCP::phCacheLabels, false)") and turns on QCustomPlot's antialiasing override
14053 for all elements (\ref setAntialiasedElements "setAntialiasedElements(QCP::aeAll)"), leading to a
14054 higher quality output. The antialiasing override allows for pixel-grid aligned drawing in the
14055 OpenGL paint device. As stated before, in OpenGL rendering the actual antialiasing of the plot is
14056 controlled with \a multisampling. If \a enabled is set to false, the antialiasing/label caching
14057 settings are restored to what they were before OpenGL was enabled, if they weren't altered in the
14058 meantime.
14059
14060 \note OpenGL support is only enabled if QCustomPlot is compiled with the macro \c QCUSTOMPLOT_USE_OPENGL
14061 defined. This define must be set before including the QCustomPlot header both during compilation
14062 of the QCustomPlot library as well as when compiling your application. It is best to just include
14063 the line <tt>DEFINES += QCUSTOMPLOT_USE_OPENGL</tt> in the respective qmake project files.
14064 \note If you are using a Qt version before 5.0, you must also add the module "opengl" to your \c
14065 QT variable in the qmake project files. For Qt versions 5.0 and higher, QCustomPlot switches to a
14066 newer OpenGL interface which is already in the "gui" module.
14067*/
14068void QCustomPlot::setOpenGl(bool enabled, int multisampling)
14069{
14070 mOpenGlMultisamples = qMax(0, multisampling);
14071#ifdef QCUSTOMPLOT_USE_OPENGL
14072 mOpenGl = enabled;
14073 if (mOpenGl)
14074 {
14075 if (setupOpenGl())
14076 {
14077 // backup antialiasing override and labelcaching setting so we can restore upon disabling OpenGL
14078 mOpenGlAntialiasedElementsBackup = mAntialiasedElements;
14079 mOpenGlCacheLabelsBackup = mPlottingHints.testFlag(QCP::phCacheLabels);
14080 // set antialiasing override to antialias all (aligns gl pixel grid properly), and disable label caching (would use software rasterizer for pixmap caches):
14083 } else
14084 {
14085 qDebug() << Q_FUNC_INFO << "Failed to enable OpenGL, continuing plotting without hardware acceleration.";
14086 mOpenGl = false;
14087 }
14088 } else
14089 {
14090 // restore antialiasing override and labelcaching to what it was before enabling OpenGL, if nobody changed it in the meantime:
14091 if (mAntialiasedElements == QCP::aeAll)
14092 setAntialiasedElements(mOpenGlAntialiasedElementsBackup);
14093 if (!mPlottingHints.testFlag(QCP::phCacheLabels))
14094 setPlottingHint(QCP::phCacheLabels, mOpenGlCacheLabelsBackup);
14095 freeOpenGl();
14096 }
14097 // recreate all paint buffers:
14098 mPaintBuffers.clear();
14100#else
14101 Q_UNUSED(enabled)
14102 qDebug() << Q_FUNC_INFO << "QCustomPlot can't use OpenGL because QCUSTOMPLOT_USE_OPENGL was not defined during compilation (add 'DEFINES += QCUSTOMPLOT_USE_OPENGL' to your qmake .pro file)";
14103#endif
14104}
14105
14106/*!
14107 Sets the viewport of this QCustomPlot. Usually users of QCustomPlot don't need to change the
14108 viewport manually.
14109
14110 The viewport is the area in which the plot is drawn. All mechanisms, e.g. margin calculation take
14111 the viewport to be the outer border of the plot. The viewport normally is the rect() of the
14112 QCustomPlot widget, i.e. a rect with top left (0, 0) and size of the QCustomPlot widget.
14113
14114 Don't confuse the viewport with the axis rect (QCustomPlot::axisRect). An axis rect is typically
14115 an area enclosed by four axes, where the graphs/plottables are drawn in. The viewport is larger
14116 and contains also the axes themselves, their tick numbers, their labels, or even additional axis
14117 rects, color scales and other layout elements.
14118
14119 This function is used to allow arbitrary size exports with \ref toPixmap, \ref savePng, \ref
14120 savePdf, etc. by temporarily changing the viewport size.
14121*/
14123{
14124 mViewport = rect;
14125 if (mPlotLayout)
14126 mPlotLayout->setOuterRect(mViewport);
14127}
14128
14129/*!
14130 Sets the device pixel ratio used by the paint buffers of this QCustomPlot instance.
14131
14132 Normally, this doesn't need to be set manually, because it is initialized with the regular \a
14133 QWidget::devicePixelRatio which is configured by Qt to fit the display device (e.g. 1 for normal
14134 displays, 2 for High-DPI displays).
14135
14136 Device pixel ratios are supported by Qt only for Qt versions since 5.4. If this method is called
14137 when QCustomPlot is being used with older Qt versions, outputs an according qDebug message and
14138 leaves the internal buffer device pixel ratio at 1.0.
14139*/
14141{
14142 if (!qFuzzyCompare(ratio, mBufferDevicePixelRatio))
14143 {
14144#ifdef QCP_DEVICEPIXELRATIO_SUPPORTED
14145 mBufferDevicePixelRatio = ratio;
14146 foreach (QSharedPointer<QCPAbstractPaintBuffer> buffer, mPaintBuffers)
14147 buffer->setDevicePixelRatio(mBufferDevicePixelRatio);
14148 // Note: axis label cache has devicePixelRatio as part of cache hash, so no need to manually clear cache here
14149#else
14150 qDebug() << Q_FUNC_INFO << "Device pixel ratios not supported for Qt versions before 5.4";
14151 mBufferDevicePixelRatio = 1.0;
14152#endif
14153 }
14154}
14155
14156/*!
14157 Sets \a pm as the viewport background pixmap (see \ref setViewport). The pixmap is always drawn
14158 below all other objects in the plot.
14159
14160 For cases where the provided pixmap doesn't have the same size as the viewport, scaling can be
14161 enabled with \ref setBackgroundScaled and the scaling mode (whether and how the aspect ratio is
14162 preserved) can be set with \ref setBackgroundScaledMode. To set all these options in one call,
14163 consider using the overloaded version of this function.
14164
14165 If a background brush was set with \ref setBackground(const QBrush &brush), the viewport will
14166 first be filled with that brush, before drawing the background pixmap. This can be useful for
14167 background pixmaps with translucent areas.
14168
14169 \see setBackgroundScaled, setBackgroundScaledMode
14170*/
14172{
14173 mBackgroundPixmap = pm;
14174 mScaledBackgroundPixmap = QPixmap();
14175}
14176
14177/*!
14178 Sets the background brush of the viewport (see \ref setViewport).
14179
14180 Before drawing everything else, the background is filled with \a brush. If a background pixmap
14181 was set with \ref setBackground(const QPixmap &pm), this brush will be used to fill the viewport
14182 before the background pixmap is drawn. This can be useful for background pixmaps with translucent
14183 areas.
14184
14185 Set \a brush to Qt::NoBrush or Qt::Transparent to leave background transparent. This can be
14186 useful for exporting to image formats which support transparency, e.g. \ref savePng.
14187
14188 \see setBackgroundScaled, setBackgroundScaledMode
14189*/
14191{
14192 mBackgroundBrush = brush;
14193}
14194
14195/*! \overload
14196
14197 Allows setting the background pixmap of the viewport, whether it shall be scaled and how it
14198 shall be scaled in one call.
14199
14200 \see setBackground(const QPixmap &pm), setBackgroundScaled, setBackgroundScaledMode
14201*/
14203{
14204 mBackgroundPixmap = pm;
14205 mScaledBackgroundPixmap = QPixmap();
14206 mBackgroundScaled = scaled;
14207 mBackgroundScaledMode = mode;
14208}
14209
14210/*!
14211 Sets whether the viewport background pixmap shall be scaled to fit the viewport. If \a scaled is
14212 set to true, control whether and how the aspect ratio of the original pixmap is preserved with
14213 \ref setBackgroundScaledMode.
14214
14215 Note that the scaled version of the original pixmap is buffered, so there is no performance
14216 penalty on replots. (Except when the viewport dimensions are changed continuously.)
14217
14218 \see setBackground, setBackgroundScaledMode
14219*/
14221{
14222 mBackgroundScaled = scaled;
14223}
14224
14225/*!
14226 If scaling of the viewport background pixmap is enabled (\ref setBackgroundScaled), use this
14227 function to define whether and how the aspect ratio of the original pixmap is preserved.
14228
14229 \see setBackground, setBackgroundScaled
14230*/
14232{
14233 mBackgroundScaledMode = mode;
14234}
14235
14236/*!
14237 Returns the plottable with \a index. If the index is invalid, returns \c nullptr.
14238
14239 There is an overloaded version of this function with no parameter which returns the last added
14240 plottable, see QCustomPlot::plottable()
14241
14242 \see plottableCount
14243*/
14245{
14246 if (index >= 0 && index < mPlottables.size())
14247 {
14248 return mPlottables.at(index);
14249 } else
14250 {
14251 qDebug() << Q_FUNC_INFO << "index out of bounds:" << index;
14252 return nullptr;
14253 }
14254}
14255
14256/*! \overload
14257
14258 Returns the last plottable that was added to the plot. If there are no plottables in the plot,
14259 returns \c nullptr.
14260
14261 \see plottableCount
14262*/
14264{
14265 if (!mPlottables.isEmpty())
14266 {
14267 return mPlottables.last();
14268 } else
14269 return nullptr;
14270}
14271
14272/*!
14273 Removes the specified plottable from the plot and deletes it. If necessary, the corresponding
14274 legend item is also removed from the default legend (QCustomPlot::legend).
14275
14276 Returns true on success.
14277
14278 \see clearPlottables
14279*/
14281{
14282 if (!mPlottables.contains(plottable))
14283 {
14284 qDebug() << Q_FUNC_INFO << "plottable not in list:" << reinterpret_cast<quintptr>(plottable);
14285 return false;
14286 }
14287
14288 // remove plottable from legend:
14290 // special handling for QCPGraphs to maintain the simple graph interface:
14292 mGraphs.removeOne(graph);
14293 // remove plottable:
14294 delete plottable;
14295 mPlottables.removeOne(plottable);
14296 return true;
14297}
14298
14299/*! \overload
14300
14301 Removes and deletes the plottable by its \a index.
14302*/
14304{
14305 if (index >= 0 && index < mPlottables.size())
14306 return removePlottable(mPlottables[index]);
14307 else
14308 {
14309 qDebug() << Q_FUNC_INFO << "index out of bounds:" << index;
14310 return false;
14311 }
14312}
14313
14314/*!
14315 Removes all plottables from the plot and deletes them. Corresponding legend items are also
14316 removed from the default legend (QCustomPlot::legend).
14317
14318 Returns the number of plottables removed.
14319
14320 \see removePlottable
14321*/
14323{
14324 int c = mPlottables.size();
14325 for (int i=c-1; i >= 0; --i)
14326 removePlottable(mPlottables[i]);
14327 return c;
14328}
14329
14330/*!
14331 Returns the number of currently existing plottables in the plot
14332
14333 \see plottable
14334*/
14336{
14337 return mPlottables.size();
14338}
14339
14340/*!
14341 Returns a list of the selected plottables. If no plottables are currently selected, the list is empty.
14342
14343 There is a convenience function if you're only interested in selected graphs, see \ref selectedGraphs.
14344
14345 \see setInteractions, QCPAbstractPlottable::setSelectable, QCPAbstractPlottable::setSelection
14346*/
14348{
14350 foreach (QCPAbstractPlottable *plottable, mPlottables)
14351 {
14352 if (plottable->selected())
14353 result.append(plottable);
14354 }
14355 return result;
14356}
14357
14358/*!
14359 Returns any plottable at the pixel position \a pos. Since it can capture all plottables, the
14360 return type is the abstract base class of all plottables, QCPAbstractPlottable.
14361
14362 For details, and if you wish to specify a certain plottable type (e.g. QCPGraph), see the
14363 template method plottableAt<PlottableType>()
14364
14365 \see plottableAt<PlottableType>(), itemAt, layoutElementAt
14366*/
14367QCPAbstractPlottable *QCustomPlot::plottableAt(const QPointF &pos, bool onlySelectable, int *dataIndex) const
14368{
14369 return plottableAt<QCPAbstractPlottable>(pos, onlySelectable, dataIndex);
14370}
14371
14372/*!
14373 Returns whether this QCustomPlot instance contains the \a plottable.
14374*/
14376{
14377 return mPlottables.contains(plottable);
14378}
14379
14380/*!
14381 Returns the graph with \a index. If the index is invalid, returns \c nullptr.
14382
14383 There is an overloaded version of this function with no parameter which returns the last created
14384 graph, see QCustomPlot::graph()
14385
14386 \see graphCount, addGraph
14387*/
14389{
14390 if (index >= 0 && index < mGraphs.size())
14391 {
14392 return mGraphs.at(index);
14393 } else
14394 {
14395 qDebug() << Q_FUNC_INFO << "index out of bounds:" << index;
14396 return nullptr;
14397 }
14398}
14399
14400/*! \overload
14401
14402 Returns the last graph, that was created with \ref addGraph. If there are no graphs in the plot,
14403 returns \c nullptr.
14404
14405 \see graphCount, addGraph
14406*/
14408{
14409 if (!mGraphs.isEmpty())
14410 {
14411 return mGraphs.last();
14412 } else
14413 return nullptr;
14414}
14415
14416/*!
14417 Creates a new graph inside the plot. If \a keyAxis and \a valueAxis are left unspecified (0), the
14418 bottom (xAxis) is used as key and the left (yAxis) is used as value axis. If specified, \a
14419 keyAxis and \a valueAxis must reside in this QCustomPlot.
14420
14421 \a keyAxis will be used as key axis (typically "x") and \a valueAxis as value axis (typically
14422 "y") for the graph.
14423
14424 Returns a pointer to the newly created graph, or \c nullptr if adding the graph failed.
14425
14426 \see graph, graphCount, removeGraph, clearGraphs
14427*/
14429{
14430 if (!keyAxis) keyAxis = xAxis;
14431 if (!valueAxis) valueAxis = yAxis;
14432 if (!keyAxis || !valueAxis)
14433 {
14434 qDebug() << Q_FUNC_INFO << "can't use default QCustomPlot xAxis or yAxis, because at least one is invalid (has been deleted)";
14435 return nullptr;
14436 }
14437 if (keyAxis->parentPlot() != this || valueAxis->parentPlot() != this)
14438 {
14439 qDebug() << Q_FUNC_INFO << "passed keyAxis or valueAxis doesn't have this QCustomPlot as parent";
14440 return nullptr;
14441 }
14442
14443 QCPGraph *newGraph = new QCPGraph(keyAxis, valueAxis);
14444 newGraph->setName(QLatin1String("Graph ")+QString::number(mGraphs.size()));
14445 return newGraph;
14446}
14447
14448/*!
14449 Removes the specified \a graph from the plot and deletes it. If necessary, the corresponding
14450 legend item is also removed from the default legend (QCustomPlot::legend). If any other graphs in
14451 the plot have a channel fill set towards the removed graph, the channel fill property of those
14452 graphs is reset to \c nullptr (no channel fill).
14453
14454 Returns true on success.
14455
14456 \see clearGraphs
14457*/
14459{
14460 return removePlottable(graph);
14461}
14462
14463/*! \overload
14464
14465 Removes and deletes the graph by its \a index.
14466*/
14468{
14469 if (index >= 0 && index < mGraphs.size())
14470 return removeGraph(mGraphs[index]);
14471 else
14472 return false;
14473}
14474
14475/*!
14476 Removes all graphs from the plot and deletes them. Corresponding legend items are also removed
14477 from the default legend (QCustomPlot::legend).
14478
14479 Returns the number of graphs removed.
14480
14481 \see removeGraph
14482*/
14484{
14485 int c = mGraphs.size();
14486 for (int i=c-1; i >= 0; --i)
14487 removeGraph(mGraphs[i]);
14488 return c;
14489}
14490
14491/*!
14492 Returns the number of currently existing graphs in the plot
14493
14494 \see graph, addGraph
14495*/
14497{
14498 return mGraphs.size();
14499}
14500
14501/*!
14502 Returns a list of the selected graphs. If no graphs are currently selected, the list is empty.
14503
14504 If you are not only interested in selected graphs but other plottables like QCPCurve, QCPBars,
14505 etc., use \ref selectedPlottables.
14506
14507 \see setInteractions, selectedPlottables, QCPAbstractPlottable::setSelectable, QCPAbstractPlottable::setSelection
14508*/
14510{
14511 QList<QCPGraph*> result;
14512 foreach (QCPGraph *graph, mGraphs)
14513 {
14514 if (graph->selected())
14515 result.append(graph);
14516 }
14517 return result;
14518}
14519
14520/*!
14521 Returns the item with \a index. If the index is invalid, returns \c nullptr.
14522
14523 There is an overloaded version of this function with no parameter which returns the last added
14524 item, see QCustomPlot::item()
14525
14526 \see itemCount
14527*/
14529{
14530 if (index >= 0 && index < mItems.size())
14531 {
14532 return mItems.at(index);
14533 } else
14534 {
14535 qDebug() << Q_FUNC_INFO << "index out of bounds:" << index;
14536 return nullptr;
14537 }
14538}
14539
14540/*! \overload
14541
14542 Returns the last item that was added to this plot. If there are no items in the plot,
14543 returns \c nullptr.
14544
14545 \see itemCount
14546*/
14548{
14549 if (!mItems.isEmpty())
14550 {
14551 return mItems.last();
14552 } else
14553 return nullptr;
14554}
14555
14556/*!
14557 Removes the specified item from the plot and deletes it.
14558
14559 Returns true on success.
14560
14561 \see clearItems
14562*/
14564{
14565 if (mItems.contains(item))
14566 {
14567 delete item;
14568 mItems.removeOne(item);
14569 return true;
14570 } else
14571 {
14572 qDebug() << Q_FUNC_INFO << "item not in list:" << reinterpret_cast<quintptr>(item);
14573 return false;
14574 }
14575}
14576
14577/*! \overload
14578
14579 Removes and deletes the item by its \a index.
14580*/
14582{
14583 if (index >= 0 && index < mItems.size())
14584 return removeItem(mItems[index]);
14585 else
14586 {
14587 qDebug() << Q_FUNC_INFO << "index out of bounds:" << index;
14588 return false;
14589 }
14590}
14591
14592/*!
14593 Removes all items from the plot and deletes them.
14594
14595 Returns the number of items removed.
14596
14597 \see removeItem
14598*/
14600{
14601 int c = mItems.size();
14602 for (int i=c-1; i >= 0; --i)
14603 removeItem(mItems[i]);
14604 return c;
14605}
14606
14607/*!
14608 Returns the number of currently existing items in the plot
14609
14610 \see item
14611*/
14613{
14614 return mItems.size();
14615}
14616
14617/*!
14618 Returns a list of the selected items. If no items are currently selected, the list is empty.
14619
14620 \see setInteractions, QCPAbstractItem::setSelectable, QCPAbstractItem::setSelected
14621*/
14623{
14625 foreach (QCPAbstractItem *item, mItems)
14626 {
14627 if (item->selected())
14628 result.append(item);
14629 }
14630 return result;
14631}
14632
14633/*!
14634 Returns the item at the pixel position \a pos. Since it can capture all items, the
14635 return type is the abstract base class of all items, QCPAbstractItem.
14636
14637 For details, and if you wish to specify a certain item type (e.g. QCPItemLine), see the
14638 template method itemAt<ItemType>()
14639
14640 \see itemAt<ItemType>(), plottableAt, layoutElementAt
14641*/
14642QCPAbstractItem *QCustomPlot::itemAt(const QPointF &pos, bool onlySelectable) const
14643{
14644 return itemAt<QCPAbstractItem>(pos, onlySelectable);
14645}
14646
14647/*!
14648 Returns whether this QCustomPlot contains the \a item.
14649
14650 \see item
14651*/
14653{
14654 return mItems.contains(item);
14655}
14656
14657/*!
14658 Returns the layer with the specified \a name. If there is no layer with the specified name, \c
14659 nullptr is returned.
14660
14661 Layer names are case-sensitive.
14662
14663 \see addLayer, moveLayer, removeLayer
14664*/
14666{
14667 foreach (QCPLayer *layer, mLayers)
14668 {
14669 if (layer->name() == name)
14670 return layer;
14671 }
14672 return nullptr;
14673}
14674
14675/*! \overload
14676
14677 Returns the layer by \a index. If the index is invalid, \c nullptr is returned.
14678
14679 \see addLayer, moveLayer, removeLayer
14680*/
14682{
14683 if (index >= 0 && index < mLayers.size())
14684 {
14685 return mLayers.at(index);
14686 } else
14687 {
14688 qDebug() << Q_FUNC_INFO << "index out of bounds:" << index;
14689 return nullptr;
14690 }
14691}
14692
14693/*!
14694 Returns the layer that is set as current layer (see \ref setCurrentLayer).
14695*/
14697{
14698 return mCurrentLayer;
14699}
14700
14701/*!
14702 Sets the layer with the specified \a name to be the current layer. All layerables (\ref
14703 QCPLayerable), e.g. plottables and items, are created on the current layer.
14704
14705 Returns true on success, i.e. if there is a layer with the specified \a name in the QCustomPlot.
14706
14707 Layer names are case-sensitive.
14708
14709 \see addLayer, moveLayer, removeLayer, QCPLayerable::setLayer
14710*/
14712{
14713 if (QCPLayer *newCurrentLayer = layer(name))
14714 {
14715 return setCurrentLayer(newCurrentLayer);
14716 } else
14717 {
14718 qDebug() << Q_FUNC_INFO << "layer with name doesn't exist:" << name;
14719 return false;
14720 }
14721}
14722
14723/*! \overload
14724
14725 Sets the provided \a layer to be the current layer.
14726
14727 Returns true on success, i.e. when \a layer is a valid layer in the QCustomPlot.
14728
14729 \see addLayer, moveLayer, removeLayer
14730*/
14732{
14733 if (!mLayers.contains(layer))
14734 {
14735 qDebug() << Q_FUNC_INFO << "layer not a layer of this QCustomPlot:" << reinterpret_cast<quintptr>(layer);
14736 return false;
14737 }
14738
14739 mCurrentLayer = layer;
14740 return true;
14741}
14742
14743/*!
14744 Returns the number of currently existing layers in the plot
14745
14746 \see layer, addLayer
14747*/
14749{
14750 return mLayers.size();
14751}
14752
14753/*!
14754 Adds a new layer to this QCustomPlot instance. The new layer will have the name \a name, which
14755 must be unique. Depending on \a insertMode, it is positioned either below or above \a otherLayer.
14756
14757 Returns true on success, i.e. if there is no other layer named \a name and \a otherLayer is a
14758 valid layer inside this QCustomPlot.
14759
14760 If \a otherLayer is 0, the highest layer in the QCustomPlot will be used.
14761
14762 For an explanation of what layers are in QCustomPlot, see the documentation of \ref QCPLayer.
14763
14764 \see layer, moveLayer, removeLayer
14765*/
14766bool QCustomPlot::addLayer(const QString &name, QCPLayer *otherLayer, QCustomPlot::LayerInsertMode insertMode)
14767{
14768 if (!otherLayer)
14769 otherLayer = mLayers.last();
14770 if (!mLayers.contains(otherLayer))
14771 {
14772 qDebug() << Q_FUNC_INFO << "otherLayer not a layer of this QCustomPlot:" << reinterpret_cast<quintptr>(otherLayer);
14773 return false;
14774 }
14775 if (layer(name))
14776 {
14777 qDebug() << Q_FUNC_INFO << "A layer exists already with the name" << name;
14778 return false;
14779 }
14780
14781 QCPLayer *newLayer = new QCPLayer(this, name);
14782 mLayers.insert(otherLayer->index() + (insertMode==limAbove ? 1:0), newLayer);
14784 setupPaintBuffers(); // associates new layer with the appropriate paint buffer
14785 return true;
14786}
14787
14788/*!
14789 Removes the specified \a layer and returns true on success.
14790
14791 All layerables (e.g. plottables and items) on the removed layer will be moved to the layer below
14792 \a layer. If \a layer is the bottom layer, the layerables are moved to the layer above. In both
14793 cases, the total rendering order of all layerables in the QCustomPlot is preserved.
14794
14795 If \a layer is the current layer (\ref setCurrentLayer), the layer below (or above, if bottom
14796 layer) becomes the new current layer.
14797
14798 It is not possible to remove the last layer of the plot.
14799
14800 \see layer, addLayer, moveLayer
14801*/
14803{
14804 if (!mLayers.contains(layer))
14805 {
14806 qDebug() << Q_FUNC_INFO << "layer not a layer of this QCustomPlot:" << reinterpret_cast<quintptr>(layer);
14807 return false;
14808 }
14809 if (mLayers.size() < 2)
14810 {
14811 qDebug() << Q_FUNC_INFO << "can't remove last layer";
14812 return false;
14813 }
14814
14815 // append all children of this layer to layer below (if this is lowest layer, prepend to layer above)
14816 int removedIndex = layer->index();
14817 bool isFirstLayer = removedIndex==0;
14818 QCPLayer *targetLayer = isFirstLayer ? mLayers.at(removedIndex+1) : mLayers.at(removedIndex-1);
14820 if (isFirstLayer) // prepend in reverse order (such that relative order stays the same)
14821 std::reverse(children.begin(), children.end());
14822 foreach (QCPLayerable *child, children)
14823 child->moveToLayer(targetLayer, isFirstLayer); // prepend if isFirstLayer, otherwise append
14824
14825 // if removed layer is current layer, change current layer to layer below/above:
14826 if (layer == mCurrentLayer)
14827 setCurrentLayer(targetLayer);
14828
14829 // invalidate the paint buffer that was responsible for this layer:
14831 pb->setInvalidated();
14832
14833 // remove layer:
14834 delete layer;
14835 mLayers.removeOne(layer);
14837 return true;
14838}
14839
14840/*!
14841 Moves the specified \a layer either above or below \a otherLayer. Whether it's placed above or
14842 below is controlled with \a insertMode.
14843
14844 Returns true on success, i.e. when both \a layer and \a otherLayer are valid layers in the
14845 QCustomPlot.
14846
14847 \see layer, addLayer, moveLayer
14848*/
14850{
14851 if (!mLayers.contains(layer))
14852 {
14853 qDebug() << Q_FUNC_INFO << "layer not a layer of this QCustomPlot:" << reinterpret_cast<quintptr>(layer);
14854 return false;
14855 }
14856 if (!mLayers.contains(otherLayer))
14857 {
14858 qDebug() << Q_FUNC_INFO << "otherLayer not a layer of this QCustomPlot:" << reinterpret_cast<quintptr>(otherLayer);
14859 return false;
14860 }
14861
14862 if (layer->index() > otherLayer->index())
14863 mLayers.move(layer->index(), otherLayer->index() + (insertMode==limAbove ? 1:0));
14864 else if (layer->index() < otherLayer->index())
14865 mLayers.move(layer->index(), otherLayer->index() + (insertMode==limAbove ? 0:-1));
14866
14867 // invalidate the paint buffers that are responsible for the layers:
14869 pb->setInvalidated();
14870 if (QSharedPointer<QCPAbstractPaintBuffer> pb = otherLayer->mPaintBuffer.toStrongRef())
14871 pb->setInvalidated();
14872
14874 return true;
14875}
14876
14877/*!
14878 Returns the number of axis rects in the plot.
14879
14880 All axis rects can be accessed via QCustomPlot::axisRect().
14881
14882 Initially, only one axis rect exists in the plot.
14883
14884 \see axisRect, axisRects
14885*/
14887{
14888 return axisRects().size();
14889}
14890
14891/*!
14892 Returns the axis rect with \a index.
14893
14894 Initially, only one axis rect (with index 0) exists in the plot. If multiple axis rects were
14895 added, all of them may be accessed with this function in a linear fashion (even when they are
14896 nested in a layout hierarchy or inside other axis rects via QCPAxisRect::insetLayout).
14897
14898 The order of the axis rects is given by the fill order of the \ref QCPLayout that is holding
14899 them. For example, if the axis rects are in the top level grid layout (accessible via \ref
14900 QCustomPlot::plotLayout), they are ordered from left to right, top to bottom, if the layout's
14901 default \ref QCPLayoutGrid::setFillOrder "setFillOrder" of \ref QCPLayoutGrid::foColumnsFirst
14902 "foColumnsFirst" wasn't changed.
14903
14904 If you want to access axis rects by their row and column index, use the layout interface. For
14905 example, use \ref QCPLayoutGrid::element of the top level grid layout, and \c qobject_cast the
14906 returned layout element to \ref QCPAxisRect. (See also \ref thelayoutsystem.)
14907
14908 \see axisRectCount, axisRects, QCPLayoutGrid::setFillOrder
14909*/
14911{
14912 const QList<QCPAxisRect*> rectList = axisRects();
14913 if (index >= 0 && index < rectList.size())
14914 {
14915 return rectList.at(index);
14916 } else
14917 {
14918 qDebug() << Q_FUNC_INFO << "invalid axis rect index" << index;
14919 return nullptr;
14920 }
14921}
14922
14923/*!
14924 Returns all axis rects in the plot.
14925
14926 The order of the axis rects is given by the fill order of the \ref QCPLayout that is holding
14927 them. For example, if the axis rects are in the top level grid layout (accessible via \ref
14928 QCustomPlot::plotLayout), they are ordered from left to right, top to bottom, if the layout's
14929 default \ref QCPLayoutGrid::setFillOrder "setFillOrder" of \ref QCPLayoutGrid::foColumnsFirst
14930 "foColumnsFirst" wasn't changed.
14931
14932 \see axisRectCount, axisRect, QCPLayoutGrid::setFillOrder
14933*/
14935{
14936 QList<QCPAxisRect*> result;
14937 QStack<QCPLayoutElement*> elementStack;
14938 if (mPlotLayout)
14939 elementStack.push(mPlotLayout);
14940
14941 while (!elementStack.isEmpty())
14942 {
14943 foreach (QCPLayoutElement *element, elementStack.pop()->elements(false))
14944 {
14945 if (element)
14946 {
14947 elementStack.push(element);
14948 if (QCPAxisRect *ar = qobject_cast<QCPAxisRect*>(element))
14949 result.append(ar);
14950 }
14951 }
14952 }
14953
14954 return result;
14955}
14956
14957/*!
14958 Returns the layout element at pixel position \a pos. If there is no element at that position,
14959 returns \c nullptr.
14960
14961 Only visible elements are used. If \ref QCPLayoutElement::setVisible on the element itself or on
14962 any of its parent elements is set to false, it will not be considered.
14963
14964 \see itemAt, plottableAt
14965*/
14967{
14968 QCPLayoutElement *currentElement = mPlotLayout;
14969 bool searchSubElements = true;
14970 while (searchSubElements && currentElement)
14971 {
14972 searchSubElements = false;
14973 foreach (QCPLayoutElement *subElement, currentElement->elements(false))
14974 {
14975 if (subElement && subElement->realVisibility() && subElement->selectTest(pos, false) >= 0)
14976 {
14977 currentElement = subElement;
14978 searchSubElements = true;
14979 break;
14980 }
14981 }
14982 }
14983 return currentElement;
14984}
14985
14986/*!
14987 Returns the layout element of type \ref QCPAxisRect at pixel position \a pos. This method ignores
14988 other layout elements even if they are visually in front of the axis rect (e.g. a \ref
14989 QCPLegend). If there is no axis rect at that position, returns \c nullptr.
14990
14991 Only visible axis rects are used. If \ref QCPLayoutElement::setVisible on the axis rect itself or
14992 on any of its parent elements is set to false, it will not be considered.
14993
14994 \see layoutElementAt
14995*/
14997{
14998 QCPAxisRect *result = nullptr;
14999 QCPLayoutElement *currentElement = mPlotLayout;
15000 bool searchSubElements = true;
15001 while (searchSubElements && currentElement)
15002 {
15003 searchSubElements = false;
15004 foreach (QCPLayoutElement *subElement, currentElement->elements(false))
15005 {
15006 if (subElement && subElement->realVisibility() && subElement->selectTest(pos, false) >= 0)
15007 {
15008 currentElement = subElement;
15009 searchSubElements = true;
15010 if (QCPAxisRect *ar = qobject_cast<QCPAxisRect*>(currentElement))
15011 result = ar;
15012 break;
15013 }
15014 }
15015 }
15016 return result;
15017}
15018
15019/*!
15020 Returns the axes that currently have selected parts, i.e. whose selection state is not \ref
15021 QCPAxis::spNone.
15022
15023 \see selectedPlottables, selectedLegends, setInteractions, QCPAxis::setSelectedParts,
15024 QCPAxis::setSelectableParts
15025*/
15027{
15028 QList<QCPAxis*> result, allAxes;
15029 foreach (QCPAxisRect *rect, axisRects())
15030 allAxes << rect->axes();
15031
15032 foreach (QCPAxis *axis, allAxes)
15033 {
15034 if (axis->selectedParts() != QCPAxis::spNone)
15035 result.append(axis);
15036 }
15037
15038 return result;
15039}
15040
15041/*!
15042 Returns the legends that currently have selected parts, i.e. whose selection state is not \ref
15043 QCPLegend::spNone.
15044
15045 \see selectedPlottables, selectedAxes, setInteractions, QCPLegend::setSelectedParts,
15046 QCPLegend::setSelectableParts, QCPLegend::selectedItems
15047*/
15049{
15050 QList<QCPLegend*> result;
15051
15052 QStack<QCPLayoutElement*> elementStack;
15053 if (mPlotLayout)
15054 elementStack.push(mPlotLayout);
15055
15056 while (!elementStack.isEmpty())
15057 {
15058 foreach (QCPLayoutElement *subElement, elementStack.pop()->elements(false))
15059 {
15060 if (subElement)
15061 {
15062 elementStack.push(subElement);
15063 if (QCPLegend *leg = qobject_cast<QCPLegend*>(subElement))
15064 {
15065 if (leg->selectedParts() != QCPLegend::spNone)
15066 result.append(leg);
15067 }
15068 }
15069 }
15070 }
15071
15072 return result;
15073}
15074
15075/*!
15076 Deselects all layerables (plottables, items, axes, legends,...) of the QCustomPlot.
15077
15078 Since calling this function is not a user interaction, this does not emit the \ref
15079 selectionChangedByUser signal. The individual selectionChanged signals are emitted though, if the
15080 objects were previously selected.
15081
15082 \see setInteractions, selectedPlottables, selectedItems, selectedAxes, selectedLegends
15083*/
15085{
15086 foreach (QCPLayer *layer, mLayers)
15087 {
15088 foreach (QCPLayerable *layerable, layer->children())
15089 layerable->deselectEvent(nullptr);
15090 }
15091}
15092
15093/*!
15094 Causes a complete replot into the internal paint buffer(s). Finally, the widget surface is
15095 refreshed with the new buffer contents. This is the method that must be called to make changes to
15096 the plot, e.g. on the axis ranges or data points of graphs, visible.
15097
15098 The parameter \a refreshPriority can be used to fine-tune the timing of the replot. For example
15099 if your application calls \ref replot very quickly in succession (e.g. multiple independent
15100 functions change some aspects of the plot and each wants to make sure the change gets replotted),
15101 it is advisable to set \a refreshPriority to \ref QCustomPlot::rpQueuedReplot. This way, the
15102 actual replotting is deferred to the next event loop iteration. Multiple successive calls of \ref
15103 replot with this priority will only cause a single replot, avoiding redundant replots and
15104 improving performance.
15105
15106 Under a few circumstances, QCustomPlot causes a replot by itself. Those are resize events of the
15107 QCustomPlot widget and user interactions (object selection and range dragging/zooming).
15108
15109 Before the replot happens, the signal \ref beforeReplot is emitted. After the replot, \ref
15110 afterReplot is emitted. It is safe to mutually connect the replot slot with any of those two
15111 signals on two QCustomPlots to make them replot synchronously, it won't cause an infinite
15112 recursion.
15113
15114 If a layer is in mode \ref QCPLayer::lmBuffered (\ref QCPLayer::setMode), it is also possible to
15115 replot only that specific layer via \ref QCPLayer::replot. See the documentation there for
15116 details.
15117
15118 \see replotTime
15119*/
15121{
15122 if (refreshPriority == QCustomPlot::rpQueuedReplot)
15123 {
15124 if (!mReplotQueued)
15125 {
15126 mReplotQueued = true;
15127 QTimer::singleShot(0, this, SLOT(replot()));
15128 }
15129 return;
15130 }
15131
15132 if (mReplotting) // incase signals loop back to replot slot
15133 return;
15134 mReplotting = true;
15135 mReplotQueued = false;
15136 emit beforeReplot();
15137
15138# if QT_VERSION < QT_VERSION_CHECK(4, 8, 0)
15139 QTime replotTimer;
15140 replotTimer.start();
15141# else
15142 QElapsedTimer replotTimer;
15143 replotTimer.start();
15144# endif
15145
15146 updateLayout();
15147 // draw all layered objects (grid, axes, plottables, items, legend,...) into their buffers:
15149 foreach (QCPLayer *layer, mLayers)
15151 foreach (QSharedPointer<QCPAbstractPaintBuffer> buffer, mPaintBuffers)
15152 buffer->setInvalidated(false);
15153
15154 if ((refreshPriority == rpRefreshHint && mPlottingHints.testFlag(QCP::phImmediateRefresh)) || refreshPriority==rpImmediateRefresh)
15155 repaint();
15156 else
15157 update();
15158
15159# if QT_VERSION < QT_VERSION_CHECK(4, 8, 0)
15160 mReplotTime = replotTimer.elapsed();
15161# else
15162 mReplotTime = replotTimer.nsecsElapsed()*1e-6;
15163# endif
15164 if (!qFuzzyIsNull(mReplotTimeAverage))
15165 mReplotTimeAverage = mReplotTimeAverage*0.9 + mReplotTime*0.1; // exponential moving average with a time constant of 10 last replots
15166 else
15167 mReplotTimeAverage = mReplotTime; // no previous replots to average with, so initialize with replot time
15168
15169 emit afterReplot();
15170 mReplotting = false;
15171}
15172
15173/*!
15174 Returns the time in milliseconds that the last replot took. If \a average is set to true, an
15175 exponential moving average over the last couple of replots is returned.
15176
15177 \see replot
15178*/
15179double QCustomPlot::replotTime(bool average) const
15180{
15181 return average ? mReplotTimeAverage : mReplotTime;
15182}
15183
15184/*!
15185 Rescales the axes such that all plottables (like graphs) in the plot are fully visible.
15186
15187 if \a onlyVisiblePlottables is set to true, only the plottables that have their visibility set to true
15188 (QCPLayerable::setVisible), will be used to rescale the axes.
15189
15190 \see QCPAbstractPlottable::rescaleAxes, QCPAxis::rescale
15191*/
15192void QCustomPlot::rescaleAxes(bool onlyVisiblePlottables)
15193{
15194 QList<QCPAxis*> allAxes;
15195 foreach (QCPAxisRect *rect, axisRects())
15196 allAxes << rect->axes();
15197
15198 foreach (QCPAxis *axis, allAxes)
15199 axis->rescale(onlyVisiblePlottables);
15200}
15201
15202/*!
15203 Saves a PDF with the vectorized plot to the file \a fileName. The axis ratio as well as the scale
15204 of texts and lines will be derived from the specified \a width and \a height. This means, the
15205 output will look like the normal on-screen output of a QCustomPlot widget with the corresponding
15206 pixel width and height. If either \a width or \a height is zero, the exported image will have the
15207 same dimensions as the QCustomPlot widget currently has.
15208
15209 Setting \a exportPen to \ref QCP::epNoCosmetic allows to disable the use of cosmetic pens when
15210 drawing to the PDF file. Cosmetic pens are pens with numerical width 0, which are always drawn as
15211 a one pixel wide line, no matter what zoom factor is set in the PDF-Viewer. For more information
15212 about cosmetic pens, see the QPainter and QPen documentation.
15213
15214 The objects of the plot will appear in the current selection state. If you don't want any
15215 selected objects to be painted in their selected look, deselect everything with \ref deselectAll
15216 before calling this function.
15217
15218 Returns true on success.
15219
15220 \warning
15221 \li If you plan on editing the exported PDF file with a vector graphics editor like Inkscape, it
15222 is advised to set \a exportPen to \ref QCP::epNoCosmetic to avoid losing those cosmetic lines
15223 (which might be quite many, because cosmetic pens are the default for e.g. axes and tick marks).
15224 \li If calling this function inside the constructor of the parent of the QCustomPlot widget
15225 (i.e. the MainWindow constructor, if QCustomPlot is inside the MainWindow), always provide
15226 explicit non-zero widths and heights. If you leave \a width or \a height as 0 (default), this
15227 function uses the current width and height of the QCustomPlot widget. However, in Qt, these
15228 aren't defined yet inside the constructor, so you would get an image that has strange
15229 widths/heights.
15230
15231 \a pdfCreator and \a pdfTitle may be used to set the according metadata fields in the resulting
15232 PDF file.
15233
15234 \note On Android systems, this method does nothing and issues an according qDebug warning
15235 message. This is also the case if for other reasons the define flag \c QT_NO_PRINTER is set.
15236
15237 \see savePng, saveBmp, saveJpg, saveRastered
15238*/
15239bool QCustomPlot::savePdf(const QString &fileName, int width, int height, QCP::ExportPen exportPen, const QString &pdfCreator, const QString &pdfTitle)
15240{
15241 bool success = false;
15242#ifdef QT_NO_PRINTER
15243 Q_UNUSED(fileName)
15244 Q_UNUSED(exportPen)
15245 Q_UNUSED(width)
15246 Q_UNUSED(height)
15247 Q_UNUSED(pdfCreator)
15248 Q_UNUSED(pdfTitle)
15249 qDebug() << Q_FUNC_INFO << "Qt was built without printer support (QT_NO_PRINTER). PDF not created.";
15250#else
15251 int newWidth, newHeight;
15252 if (width == 0 || height == 0)
15253 {
15254 newWidth = this->width();
15255 newHeight = this->height();
15256 } else
15257 {
15258 newWidth = width;
15259 newHeight = height;
15260 }
15261
15263 printer.setOutputFileName(fileName);
15266 printer.printEngine()->setProperty(QPrintEngine::PPK_Creator, pdfCreator);
15268 QRect oldViewport = viewport();
15269 setViewport(QRect(0, 0, newWidth, newHeight));
15270#if QT_VERSION < QT_VERSION_CHECK(5, 3, 0)
15271 printer.setFullPage(true);
15272 printer.setPaperSize(viewport().size(), QPrinter::DevicePixel);
15273#else
15274 QPageLayout pageLayout;
15277 pageLayout.setMargins(QMarginsF(0, 0, 0, 0));
15279 printer.setPageLayout(pageLayout);
15280#endif
15281 QCPPainter printpainter;
15282 if (printpainter.begin(&printer))
15283 {
15284 printpainter.setMode(QCPPainter::pmVectorized);
15285 printpainter.setMode(QCPPainter::pmNoCaching);
15286 printpainter.setMode(QCPPainter::pmNonCosmetic, exportPen==QCP::epNoCosmetic);
15287 printpainter.setWindow(mViewport);
15288 if (mBackgroundBrush.style() != Qt::NoBrush &&
15289 mBackgroundBrush.color() != Qt::white &&
15290 mBackgroundBrush.color() != Qt::transparent &&
15291 mBackgroundBrush.color().alpha() > 0) // draw pdf background color if not white/transparent
15292 printpainter.fillRect(viewport(), mBackgroundBrush);
15293 draw(&printpainter);
15294 printpainter.end();
15295 success = true;
15296 }
15297 setViewport(oldViewport);
15298#endif // QT_NO_PRINTER
15299 return success;
15300}
15301
15302/*!
15303 Saves a PNG image file to \a fileName on disc. The output plot will have the dimensions \a width
15304 and \a height in pixels, multiplied by \a scale. If either \a width or \a height is zero, the
15305 current width and height of the QCustomPlot widget is used instead. Line widths and texts etc.
15306 are not scaled up when larger widths/heights are used. If you want that effect, use the \a scale
15307 parameter.
15308
15309 For example, if you set both \a width and \a height to 100 and \a scale to 2, you will end up with an
15310 image file of size 200*200 in which all graphical elements are scaled up by factor 2 (line widths,
15311 texts, etc.). This scaling is not done by stretching a 100*100 image, the result will have full
15312 200*200 pixel resolution.
15313
15314 If you use a high scaling factor, it is recommended to enable antialiasing for all elements by
15315 temporarily setting \ref QCustomPlot::setAntialiasedElements to \ref QCP::aeAll as this allows
15316 QCustomPlot to place objects with sub-pixel accuracy.
15317
15318 image compression can be controlled with the \a quality parameter which must be between 0 and 100
15319 or -1 to use the default setting.
15320
15321 The \a resolution will be written to the image file header and has no direct consequence for the
15322 quality or the pixel size. However, if opening the image with a tool which respects the metadata,
15323 it will be able to scale the image to match either a given size in real units of length (inch,
15324 centimeters, etc.), or the target display DPI. You can specify in which units \a resolution is
15325 given, by setting \a resolutionUnit. The \a resolution is converted to the format's expected
15326 resolution unit internally.
15327
15328 Returns true on success. If this function fails, most likely the PNG format isn't supported by
15329 the system, see Qt docs about QImageWriter::supportedImageFormats().
15330
15331 The objects of the plot will appear in the current selection state. If you don't want any selected
15332 objects to be painted in their selected look, deselect everything with \ref deselectAll before calling
15333 this function.
15334
15335 If you want the PNG to have a transparent background, call \ref setBackground(const QBrush &brush)
15336 with no brush (Qt::NoBrush) or a transparent color (Qt::transparent), before saving.
15337
15338 \warning If calling this function inside the constructor of the parent of the QCustomPlot widget
15339 (i.e. the MainWindow constructor, if QCustomPlot is inside the MainWindow), always provide
15340 explicit non-zero widths and heights. If you leave \a width or \a height as 0 (default), this
15341 function uses the current width and height of the QCustomPlot widget. However, in Qt, these
15342 aren't defined yet inside the constructor, so you would get an image that has strange
15343 widths/heights.
15344
15345 \see savePdf, saveBmp, saveJpg, saveRastered
15346*/
15347bool QCustomPlot::savePng(const QString &fileName, int width, int height, double scale, int quality, int resolution, QCP::ResolutionUnit resolutionUnit)
15348{
15349 return saveRastered(fileName, width, height, scale, "PNG", quality, resolution, resolutionUnit);
15350}
15351
15352/*!
15353 Saves a JPEG image file to \a fileName on disc. The output plot will have the dimensions \a width
15354 and \a height in pixels, multiplied by \a scale. If either \a width or \a height is zero, the
15355 current width and height of the QCustomPlot widget is used instead. Line widths and texts etc.
15356 are not scaled up when larger widths/heights are used. If you want that effect, use the \a scale
15357 parameter.
15358
15359 For example, if you set both \a width and \a height to 100 and \a scale to 2, you will end up with an
15360 image file of size 200*200 in which all graphical elements are scaled up by factor 2 (line widths,
15361 texts, etc.). This scaling is not done by stretching a 100*100 image, the result will have full
15362 200*200 pixel resolution.
15363
15364 If you use a high scaling factor, it is recommended to enable antialiasing for all elements by
15365 temporarily setting \ref QCustomPlot::setAntialiasedElements to \ref QCP::aeAll as this allows
15366 QCustomPlot to place objects with sub-pixel accuracy.
15367
15368 image compression can be controlled with the \a quality parameter which must be between 0 and 100
15369 or -1 to use the default setting.
15370
15371 The \a resolution will be written to the image file header and has no direct consequence for the
15372 quality or the pixel size. However, if opening the image with a tool which respects the metadata,
15373 it will be able to scale the image to match either a given size in real units of length (inch,
15374 centimeters, etc.), or the target display DPI. You can specify in which units \a resolution is
15375 given, by setting \a resolutionUnit. The \a resolution is converted to the format's expected
15376 resolution unit internally.
15377
15378 Returns true on success. If this function fails, most likely the JPEG format isn't supported by
15379 the system, see Qt docs about QImageWriter::supportedImageFormats().
15380
15381 The objects of the plot will appear in the current selection state. If you don't want any selected
15382 objects to be painted in their selected look, deselect everything with \ref deselectAll before calling
15383 this function.
15384
15385 \warning If calling this function inside the constructor of the parent of the QCustomPlot widget
15386 (i.e. the MainWindow constructor, if QCustomPlot is inside the MainWindow), always provide
15387 explicit non-zero widths and heights. If you leave \a width or \a height as 0 (default), this
15388 function uses the current width and height of the QCustomPlot widget. However, in Qt, these
15389 aren't defined yet inside the constructor, so you would get an image that has strange
15390 widths/heights.
15391
15392 \see savePdf, savePng, saveBmp, saveRastered
15393*/
15394bool QCustomPlot::saveJpg(const QString &fileName, int width, int height, double scale, int quality, int resolution, QCP::ResolutionUnit resolutionUnit)
15395{
15396 return saveRastered(fileName, width, height, scale, "JPG", quality, resolution, resolutionUnit);
15397}
15398
15399/*!
15400 Saves a BMP image file to \a fileName on disc. The output plot will have the dimensions \a width
15401 and \a height in pixels, multiplied by \a scale. If either \a width or \a height is zero, the
15402 current width and height of the QCustomPlot widget is used instead. Line widths and texts etc.
15403 are not scaled up when larger widths/heights are used. If you want that effect, use the \a scale
15404 parameter.
15405
15406 For example, if you set both \a width and \a height to 100 and \a scale to 2, you will end up with an
15407 image file of size 200*200 in which all graphical elements are scaled up by factor 2 (line widths,
15408 texts, etc.). This scaling is not done by stretching a 100*100 image, the result will have full
15409 200*200 pixel resolution.
15410
15411 If you use a high scaling factor, it is recommended to enable antialiasing for all elements by
15412 temporarily setting \ref QCustomPlot::setAntialiasedElements to \ref QCP::aeAll as this allows
15413 QCustomPlot to place objects with sub-pixel accuracy.
15414
15415 The \a resolution will be written to the image file header and has no direct consequence for the
15416 quality or the pixel size. However, if opening the image with a tool which respects the metadata,
15417 it will be able to scale the image to match either a given size in real units of length (inch,
15418 centimeters, etc.), or the target display DPI. You can specify in which units \a resolution is
15419 given, by setting \a resolutionUnit. The \a resolution is converted to the format's expected
15420 resolution unit internally.
15421
15422 Returns true on success. If this function fails, most likely the BMP format isn't supported by
15423 the system, see Qt docs about QImageWriter::supportedImageFormats().
15424
15425 The objects of the plot will appear in the current selection state. If you don't want any selected
15426 objects to be painted in their selected look, deselect everything with \ref deselectAll before calling
15427 this function.
15428
15429 \warning If calling this function inside the constructor of the parent of the QCustomPlot widget
15430 (i.e. the MainWindow constructor, if QCustomPlot is inside the MainWindow), always provide
15431 explicit non-zero widths and heights. If you leave \a width or \a height as 0 (default), this
15432 function uses the current width and height of the QCustomPlot widget. However, in Qt, these
15433 aren't defined yet inside the constructor, so you would get an image that has strange
15434 widths/heights.
15435
15436 \see savePdf, savePng, saveJpg, saveRastered
15437*/
15438bool QCustomPlot::saveBmp(const QString &fileName, int width, int height, double scale, int resolution, QCP::ResolutionUnit resolutionUnit)
15439{
15440 return saveRastered(fileName, width, height, scale, "BMP", -1, resolution, resolutionUnit);
15441}
15442
15443/*! \internal
15444
15445 Returns a minimum size hint that corresponds to the minimum size of the top level layout
15446 (\ref plotLayout). To prevent QCustomPlot from being collapsed to size/width zero, set a minimum
15447 size (setMinimumSize) either on the whole QCustomPlot or on any layout elements inside the plot.
15448 This is especially important, when placed in a QLayout where other components try to take in as
15449 much space as possible (e.g. QMdiArea).
15450*/
15452{
15453 return mPlotLayout->minimumOuterSizeHint();
15454}
15455
15456/*! \internal
15457
15458 Returns a size hint that is the same as \ref minimumSizeHint.
15459
15460*/
15462{
15463 return mPlotLayout->minimumOuterSizeHint();
15464}
15465
15466/*! \internal
15467
15468 Event handler for when the QCustomPlot widget needs repainting. This does not cause a \ref replot, but
15469 draws the internal buffer on the widget surface.
15470*/
15472{
15473 Q_UNUSED(event)
15474
15475 // detect if the device pixel ratio has changed (e.g. moving window between different DPI screens), and adapt buffers if necessary:
15476#ifdef QCP_DEVICEPIXELRATIO_SUPPORTED
15477# ifdef QCP_DEVICEPIXELRATIO_FLOAT
15478 double newDpr = devicePixelRatioF();
15479# else
15480 double newDpr = devicePixelRatio();
15481# endif
15482 if (!qFuzzyCompare(mBufferDevicePixelRatio, newDpr))
15483 {
15486 return;
15487 }
15488#endif
15489
15490 QCPPainter painter(this);
15491 if (painter.isActive())
15492 {
15493#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
15494 painter.setRenderHint(QPainter::HighQualityAntialiasing); // to make Antialiasing look good if using the OpenGL graphicssystem
15495#endif
15496 if (mBackgroundBrush.style() != Qt::NoBrush)
15497 painter.fillRect(mViewport, mBackgroundBrush);
15498 drawBackground(&painter);
15499 foreach (QSharedPointer<QCPAbstractPaintBuffer> buffer, mPaintBuffers)
15500 buffer->draw(&painter);
15501 }
15502}
15503
15504/*! \internal
15505
15506 Event handler for a resize of the QCustomPlot widget. The viewport (which becomes the outer rect
15507 of mPlotLayout) is resized appropriately. Finally a \ref replot is performed.
15508*/
15510{
15511 Q_UNUSED(event)
15512 // resize and repaint the buffer:
15513 setViewport(rect());
15514 replot(rpQueuedRefresh); // queued refresh is important here, to prevent painting issues in some contexts (e.g. MDI subwindow)
15515}
15516
15517/*! \internal
15518
15519 Event handler for when a double click occurs. Emits the \ref mouseDoubleClick signal, then
15520 determines the layerable under the cursor and forwards the event to it. Finally, emits the
15521 specialized signals when certain objecs are clicked (e.g. \ref plottableDoubleClick, \ref
15522 axisDoubleClick, etc.).
15523
15524 \see mousePressEvent, mouseReleaseEvent
15525*/
15527{
15528 emit mouseDoubleClick(event);
15529 mMouseHasMoved = false;
15530 mMousePressPos = event->pos();
15531
15532 // determine layerable under the cursor (this event is called instead of the second press event in a double-click):
15533 QList<QVariant> details;
15534 QList<QCPLayerable*> candidates = layerableListAt(mMousePressPos, false, &details);
15535 for (int i=0; i<candidates.size(); ++i)
15536 {
15537 event->accept(); // default impl of QCPLayerable's mouse events ignore the event, in that case propagate to next candidate in list
15538 candidates.at(i)->mouseDoubleClickEvent(event, details.at(i));
15539 if (event->isAccepted())
15540 {
15541 mMouseEventLayerable = candidates.at(i);
15542 mMouseEventLayerableDetails = details.at(i);
15543 break;
15544 }
15545 }
15546
15547 // emit specialized object double click signals:
15548 if (!candidates.isEmpty())
15549 {
15551 {
15552 int dataIndex = 0;
15553 if (!details.first().value<QCPDataSelection>().isEmpty())
15554 dataIndex = details.first().value<QCPDataSelection>().dataRange().begin();
15555 emit plottableDoubleClick(ap, dataIndex, event);
15556 } else if (QCPAxis *ax = qobject_cast<QCPAxis*>(candidates.first()))
15557 emit axisDoubleClick(ax, details.first().value<QCPAxis::SelectablePart>(), event);
15558 else if (QCPAbstractItem *ai = qobject_cast<QCPAbstractItem*>(candidates.first()))
15559 emit itemDoubleClick(ai, event);
15560 else if (QCPLegend *lg = qobject_cast<QCPLegend*>(candidates.first()))
15561 emit legendDoubleClick(lg, nullptr, event);
15563 emit legendDoubleClick(li->parentLegend(), li, event);
15564 }
15565
15566 event->accept(); // in case QCPLayerable reimplementation manipulates event accepted state. In QWidget event system, QCustomPlot wants to accept the event.
15567}
15568
15569/*! \internal
15570
15571 Event handler for when a mouse button is pressed. Emits the mousePress signal.
15572
15573 If the current \ref setSelectionRectMode is not \ref QCP::srmNone, passes the event to the
15574 selection rect. Otherwise determines the layerable under the cursor and forwards the event to it.
15575
15576 \see mouseMoveEvent, mouseReleaseEvent
15577*/
15579{
15580 emit mousePress(event);
15581 // save some state to tell in releaseEvent whether it was a click:
15582 mMouseHasMoved = false;
15583 mMousePressPos = event->pos();
15584
15585 if (mSelectionRect && mSelectionRectMode != QCP::srmNone)
15586 {
15587 if (mSelectionRectMode != QCP::srmZoom || qobject_cast<QCPAxisRect*>(axisRectAt(mMousePressPos))) // in zoom mode only activate selection rect if on an axis rect
15588 mSelectionRect->startSelection(event);
15589 } else
15590 {
15591 // no selection rect interaction, prepare for click signal emission and forward event to layerable under the cursor:
15592 QList<QVariant> details;
15593 QList<QCPLayerable*> candidates = layerableListAt(mMousePressPos, false, &details);
15594 if (!candidates.isEmpty())
15595 {
15596 mMouseSignalLayerable = candidates.first(); // candidate for signal emission is always topmost hit layerable (signal emitted in release event)
15597 mMouseSignalLayerableDetails = details.first();
15598 }
15599 // forward event to topmost candidate which accepts the event:
15600 for (int i=0; i<candidates.size(); ++i)
15601 {
15602 event->accept(); // default impl of QCPLayerable's mouse events call ignore() on the event, in that case propagate to next candidate in list
15603 candidates.at(i)->mousePressEvent(event, details.at(i));
15604 if (event->isAccepted())
15605 {
15606 mMouseEventLayerable = candidates.at(i);
15607 mMouseEventLayerableDetails = details.at(i);
15608 break;
15609 }
15610 }
15611 }
15612
15613 event->accept(); // in case QCPLayerable reimplementation manipulates event accepted state. In QWidget event system, QCustomPlot wants to accept the event.
15614}
15615
15616/*! \internal
15617
15618 Event handler for when the cursor is moved. Emits the \ref mouseMove signal.
15619
15620 If the selection rect (\ref setSelectionRect) is currently active, the event is forwarded to it
15621 in order to update the rect geometry.
15622
15623 Otherwise, if a layout element has mouse capture focus (a mousePressEvent happened on top of the
15624 layout element before), the mouseMoveEvent is forwarded to that element.
15625
15626 \see mousePressEvent, mouseReleaseEvent
15627*/
15629{
15630 emit mouseMove(event);
15631
15632 if (!mMouseHasMoved && (mMousePressPos-event->pos()).manhattanLength() > 3)
15633 mMouseHasMoved = true; // moved too far from mouse press position, don't handle as click on mouse release
15634
15635 if (mSelectionRect && mSelectionRect->isActive())
15636 mSelectionRect->moveSelection(event);
15637 else if (mMouseEventLayerable) // call event of affected layerable:
15638 mMouseEventLayerable->mouseMoveEvent(event, mMousePressPos);
15639
15640 event->accept(); // in case QCPLayerable reimplementation manipulates event accepted state. In QWidget event system, QCustomPlot wants to accept the event.
15641}
15642
15643/*! \internal
15644
15645 Event handler for when a mouse button is released. Emits the \ref mouseRelease signal.
15646
15647 If the mouse was moved less than a certain threshold in any direction since the \ref
15648 mousePressEvent, it is considered a click which causes the selection mechanism (if activated via
15649 \ref setInteractions) to possibly change selection states accordingly. Further, specialized mouse
15650 click signals are emitted (e.g. \ref plottableClick, \ref axisClick, etc.)
15651
15652 If a layerable is the mouse capturer (a \ref mousePressEvent happened on top of the layerable
15653 before), the \ref mouseReleaseEvent is forwarded to that element.
15654
15655 \see mousePressEvent, mouseMoveEvent
15656*/
15658{
15659 emit mouseRelease(event);
15660
15661 if (!mMouseHasMoved) // mouse hasn't moved (much) between press and release, so handle as click
15662 {
15663 if (mSelectionRect && mSelectionRect->isActive()) // a simple click shouldn't successfully finish a selection rect, so cancel it here
15664 mSelectionRect->cancel();
15665 if (event->button() == Qt::LeftButton)
15667
15668 // emit specialized click signals of QCustomPlot instance:
15669 if (QCPAbstractPlottable *ap = qobject_cast<QCPAbstractPlottable*>(mMouseSignalLayerable))
15670 {
15671 int dataIndex = 0;
15672 if (!mMouseSignalLayerableDetails.value<QCPDataSelection>().isEmpty())
15673 dataIndex = mMouseSignalLayerableDetails.value<QCPDataSelection>().dataRange().begin();
15674 emit plottableClick(ap, dataIndex, event);
15675 } else if (QCPAxis *ax = qobject_cast<QCPAxis*>(mMouseSignalLayerable))
15676 emit axisClick(ax, mMouseSignalLayerableDetails.value<QCPAxis::SelectablePart>(), event);
15677 else if (QCPAbstractItem *ai = qobject_cast<QCPAbstractItem*>(mMouseSignalLayerable))
15678 emit itemClick(ai, event);
15679 else if (QCPLegend *lg = qobject_cast<QCPLegend*>(mMouseSignalLayerable))
15680 emit legendClick(lg, nullptr, event);
15681 else if (QCPAbstractLegendItem *li = qobject_cast<QCPAbstractLegendItem*>(mMouseSignalLayerable))
15682 emit legendClick(li->parentLegend(), li, event);
15683 mMouseSignalLayerable = nullptr;
15684 }
15685
15686 if (mSelectionRect && mSelectionRect->isActive()) // Note: if a click was detected above, the selection rect is canceled there
15687 {
15688 // finish selection rect, the appropriate action will be taken via signal-slot connection:
15689 mSelectionRect->endSelection(event);
15690 } else
15691 {
15692 // call event of affected layerable:
15693 if (mMouseEventLayerable)
15694 {
15695 mMouseEventLayerable->mouseReleaseEvent(event, mMousePressPos);
15696 mMouseEventLayerable = nullptr;
15697 }
15698 }
15699
15700 if (noAntialiasingOnDrag())
15702
15703 event->accept(); // in case QCPLayerable reimplementation manipulates event accepted state. In QWidget event system, QCustomPlot wants to accept the event.
15704}
15705
15706/*! \internal
15707
15708 Event handler for mouse wheel events. First, the \ref mouseWheel signal is emitted. Then
15709 determines the affected layerable and forwards the event to it.
15710*/
15712{
15713 emit mouseWheel(event);
15714
15715#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
15716 const QPointF pos = event->pos();
15717#else
15718 const QPointF pos = event->position();
15719#endif
15720
15721 // forward event to layerable under cursor:
15722 foreach (QCPLayerable *candidate, layerableListAt(pos, false))
15723 {
15724 event->accept(); // default impl of QCPLayerable's mouse events ignore the event, in that case propagate to next candidate in list
15725 candidate->wheelEvent(event);
15726 if (event->isAccepted())
15727 break;
15728 }
15729 event->accept(); // in case QCPLayerable reimplementation manipulates event accepted state. In QWidget event system, QCustomPlot wants to accept the event.
15730}
15731
15732/*! \internal
15733
15734 This function draws the entire plot, including background pixmap, with the specified \a painter.
15735 It does not make use of the paint buffers like \ref replot, so this is the function typically
15736 used by saving/exporting methods such as \ref savePdf or \ref toPainter.
15737
15738 Note that it does not fill the background with the background brush (as the user may specify with
15739 \ref setBackground(const QBrush &brush)), this is up to the respective functions calling this
15740 method.
15741*/
15743{
15744 updateLayout();
15745
15746 // draw viewport background pixmap:
15747 drawBackground(painter);
15748
15749 // draw all layered objects (grid, axes, plottables, items, legend,...):
15750 foreach (QCPLayer *layer, mLayers)
15751 layer->draw(painter);
15752
15753 /* Debug code to draw all layout element rects
15754 foreach (QCPLayoutElement *el, findChildren<QCPLayoutElement*>())
15755 {
15756 painter->setBrush(Qt::NoBrush);
15757 painter->setPen(QPen(QColor(0, 0, 0, 100), 0, Qt::DashLine));
15758 painter->drawRect(el->rect());
15759 painter->setPen(QPen(QColor(255, 0, 0, 100), 0, Qt::DashLine));
15760 painter->drawRect(el->outerRect());
15761 }
15762 */
15763}
15764
15765/*! \internal
15766
15767 Performs the layout update steps defined by \ref QCPLayoutElement::UpdatePhase, by calling \ref
15768 QCPLayoutElement::update on the main plot layout.
15769
15770 Here, the layout elements calculate their positions and margins, and prepare for the following
15771 draw call.
15772*/
15774{
15775 // run through layout phases:
15777 mPlotLayout->update(QCPLayoutElement::upMargins);
15778 mPlotLayout->update(QCPLayoutElement::upLayout);
15779
15780 emit afterLayout();
15781}
15782
15783/*! \internal
15784
15785 Draws the viewport background pixmap of the plot.
15786
15787 If a pixmap was provided via \ref setBackground, this function buffers the scaled version
15788 depending on \ref setBackgroundScaled and \ref setBackgroundScaledMode and then draws it inside
15789 the viewport with the provided \a painter. The scaled version is buffered in
15790 mScaledBackgroundPixmap to prevent expensive rescaling at every redraw. It is only updated, when
15791 the axis rect has changed in a way that requires a rescale of the background pixmap (this is
15792 dependent on the \ref setBackgroundScaledMode), or when a differend axis background pixmap was
15793 set.
15794
15795 Note that this function does not draw a fill with the background brush
15796 (\ref setBackground(const QBrush &brush)) beneath the pixmap.
15797
15798 \see setBackground, setBackgroundScaled, setBackgroundScaledMode
15799*/
15801{
15802 // Note: background color is handled in individual replot/save functions
15803
15804 // draw background pixmap (on top of fill, if brush specified):
15805 if (!mBackgroundPixmap.isNull())
15806 {
15807 if (mBackgroundScaled)
15808 {
15809 // check whether mScaledBackground needs to be updated:
15810 QSize scaledSize(mBackgroundPixmap.size());
15811 scaledSize.scale(mViewport.size(), mBackgroundScaledMode);
15812 if (mScaledBackgroundPixmap.size() != scaledSize)
15813 mScaledBackgroundPixmap = mBackgroundPixmap.scaled(mViewport.size(), mBackgroundScaledMode, Qt::SmoothTransformation);
15814 painter->drawPixmap(mViewport.topLeft(), mScaledBackgroundPixmap, QRect(0, 0, mViewport.width(), mViewport.height()) & mScaledBackgroundPixmap.rect());
15815 } else
15816 {
15817 painter->drawPixmap(mViewport.topLeft(), mBackgroundPixmap, QRect(0, 0, mViewport.width(), mViewport.height()));
15818 }
15819 }
15820}
15821
15822/*! \internal
15823
15824 Goes through the layers and makes sure this QCustomPlot instance holds the correct number of
15825 paint buffers and that they have the correct configuration (size, pixel ratio, etc.).
15826 Allocations, reallocations and deletions of paint buffers are performed as necessary. It also
15827 associates the paint buffers with the layers, so they draw themselves into the right buffer when
15828 \ref QCPLayer::drawToPaintBuffer is called. This means it associates adjacent \ref
15829 QCPLayer::lmLogical layers to a mutual paint buffer and creates dedicated paint buffers for
15830 layers in \ref QCPLayer::lmBuffered mode.
15831
15832 This method uses \ref createPaintBuffer to create new paint buffers.
15833
15834 After this method, the paint buffers are empty (filled with \c Qt::transparent) and invalidated
15835 (so an attempt to replot only a single buffered layer causes a full replot).
15836
15837 This method is called in every \ref replot call, prior to actually drawing the layers (into their
15838 associated paint buffer). If the paint buffers don't need changing/reallocating, this method
15839 basically leaves them alone and thus finishes very fast.
15840*/
15842{
15843 int bufferIndex = 0;
15844 if (mPaintBuffers.isEmpty())
15846
15847 for (int layerIndex = 0; layerIndex < mLayers.size(); ++layerIndex)
15848 {
15849 QCPLayer *layer = mLayers.at(layerIndex);
15850 if (layer->mode() == QCPLayer::lmLogical)
15851 {
15852 layer->mPaintBuffer = mPaintBuffers.at(bufferIndex).toWeakRef();
15853 } else if (layer->mode() == QCPLayer::lmBuffered)
15854 {
15855 ++bufferIndex;
15856 if (bufferIndex >= mPaintBuffers.size())
15858 layer->mPaintBuffer = mPaintBuffers.at(bufferIndex).toWeakRef();
15859 if (layerIndex < mLayers.size()-1 && mLayers.at(layerIndex+1)->mode() == QCPLayer::lmLogical) // not last layer, and next one is logical, so prepare another buffer for next layerables
15860 {
15861 ++bufferIndex;
15862 if (bufferIndex >= mPaintBuffers.size())
15864 }
15865 }
15866 }
15867 // remove unneeded buffers:
15868 while (mPaintBuffers.size()-1 > bufferIndex)
15869 mPaintBuffers.removeLast();
15870 // resize buffers to viewport size and clear contents:
15871 foreach (QSharedPointer<QCPAbstractPaintBuffer> buffer, mPaintBuffers)
15872 {
15873 buffer->setSize(viewport().size()); // won't do anything if already correct size
15874 buffer->clear(Qt::transparent);
15875 buffer->setInvalidated();
15876 }
15877}
15878
15879/*! \internal
15880
15881 This method is used by \ref setupPaintBuffers when it needs to create new paint buffers.
15882
15883 Depending on the current setting of \ref setOpenGl, and the current Qt version, different
15884 backends (subclasses of \ref QCPAbstractPaintBuffer) are created, initialized with the proper
15885 size and device pixel ratio, and returned.
15886*/
15888{
15889 if (mOpenGl)
15890 {
15891#if defined(QCP_OPENGL_FBO)
15892 return new QCPPaintBufferGlFbo(viewport().size(), mBufferDevicePixelRatio, mGlContext, mGlPaintDevice);
15893#elif defined(QCP_OPENGL_PBUFFER)
15894 return new QCPPaintBufferGlPbuffer(viewport().size(), mBufferDevicePixelRatio, mOpenGlMultisamples);
15895#else
15896 qDebug() << Q_FUNC_INFO << "OpenGL enabled even though no support for it compiled in, this shouldn't have happened. Falling back to pixmap paint buffer.";
15897 return new QCPPaintBufferPixmap(viewport().size(), mBufferDevicePixelRatio);
15898#endif
15899 } else
15900 return new QCPPaintBufferPixmap(viewport().size(), mBufferDevicePixelRatio);
15901}
15902
15903/*!
15904 This method returns whether any of the paint buffers held by this QCustomPlot instance are
15905 invalidated.
15906
15907 If any buffer is invalidated, a partial replot (\ref QCPLayer::replot) is not allowed and always
15908 causes a full replot (\ref QCustomPlot::replot) of all layers. This is the case when for example
15909 the layer order has changed, new layers were added or removed, layer modes were changed (\ref
15910 QCPLayer::setMode), or layerables were added or removed.
15911
15912 \see QCPAbstractPaintBuffer::setInvalidated
15913*/
15915{
15916 foreach (QSharedPointer<QCPAbstractPaintBuffer> buffer, mPaintBuffers)
15917 {
15918 if (buffer->invalidated())
15919 return true;
15920 }
15921 return false;
15922}
15923
15924/*! \internal
15925
15926 When \ref setOpenGl is set to true, this method is used to initialize OpenGL (create a context,
15927 surface, paint device).
15928
15929 Returns true on success.
15930
15931 If this method is successful, all paint buffers should be deleted and then reallocated by calling
15932 \ref setupPaintBuffers, so the OpenGL-based paint buffer subclasses (\ref
15933 QCPPaintBufferGlPbuffer, \ref QCPPaintBufferGlFbo) are used for subsequent replots.
15934
15935 \see freeOpenGl
15936*/
15938{
15939#ifdef QCP_OPENGL_FBO
15940 freeOpenGl();
15941 QSurfaceFormat proposedSurfaceFormat;
15942 proposedSurfaceFormat.setSamples(mOpenGlMultisamples);
15943#ifdef QCP_OPENGL_OFFSCREENSURFACE
15944 QOffscreenSurface *surface = new QOffscreenSurface;
15945#else
15946 QWindow *surface = new QWindow;
15947 surface->setSurfaceType(QSurface::OpenGLSurface);
15948#endif
15949 surface->setFormat(proposedSurfaceFormat);
15950 surface->create();
15951 mGlSurface = QSharedPointer<QSurface>(surface);
15953 mGlContext->setFormat(mGlSurface->format());
15954 if (!mGlContext->create())
15955 {
15956 qDebug() << Q_FUNC_INFO << "Failed to create OpenGL context";
15957 mGlContext.clear();
15958 mGlSurface.clear();
15959 return false;
15960 }
15961 if (!mGlContext->makeCurrent(mGlSurface.data())) // context needs to be current to create paint device
15962 {
15963 qDebug() << Q_FUNC_INFO << "Failed to make opengl context current";
15964 mGlContext.clear();
15965 mGlSurface.clear();
15966 return false;
15967 }
15969 {
15970 qDebug() << Q_FUNC_INFO << "OpenGL of this system doesn't support frame buffer objects";
15971 mGlContext.clear();
15972 mGlSurface.clear();
15973 return false;
15974 }
15976 return true;
15977#elif defined(QCP_OPENGL_PBUFFER)
15978 return QGLFormat::hasOpenGL();
15979#else
15980 return false;
15981#endif
15982}
15983
15984/*! \internal
15985
15986 When \ref setOpenGl is set to false, this method is used to deinitialize OpenGL (releases the
15987 context and frees resources).
15988
15989 After OpenGL is disabled, all paint buffers should be deleted and then reallocated by calling
15990 \ref setupPaintBuffers, so the standard software rendering paint buffer subclass (\ref
15991 QCPPaintBufferPixmap) is used for subsequent replots.
15992
15993 \see setupOpenGl
15994*/
15996{
15997#ifdef QCP_OPENGL_FBO
15998 mGlPaintDevice.clear();
15999 mGlContext.clear();
16000 mGlSurface.clear();
16001#endif
16002}
16003
16004/*! \internal
16005
16006 This method is used by \ref QCPAxisRect::removeAxis to report removed axes to the QCustomPlot
16007 so it may clear its QCustomPlot::xAxis, yAxis, xAxis2 and yAxis2 members accordingly.
16008*/
16010{
16011 if (xAxis == axis)
16012 xAxis = nullptr;
16013 if (xAxis2 == axis)
16014 xAxis2 = nullptr;
16015 if (yAxis == axis)
16016 yAxis = nullptr;
16017 if (yAxis2 == axis)
16018 yAxis2 = nullptr;
16019
16020 // Note: No need to take care of range drag axes and range zoom axes, because they are stored in smart pointers
16021}
16022
16023/*! \internal
16024
16025 This method is used by the QCPLegend destructor to report legend removal to the QCustomPlot so
16026 it may clear its QCustomPlot::legend member accordingly.
16027*/
16029{
16030 if (this->legend == legend)
16031 this->legend = nullptr;
16032}
16033
16034/*! \internal
16035
16036 This slot is connected to the selection rect's \ref QCPSelectionRect::accepted signal when \ref
16037 setSelectionRectMode is set to \ref QCP::srmSelect.
16038
16039 First, it determines which axis rect was the origin of the selection rect judging by the starting
16040 point of the selection. Then it goes through the plottables (\ref QCPAbstractPlottable1D to be
16041 precise) associated with that axis rect and finds the data points that are in \a rect. It does
16042 this by querying their \ref QCPAbstractPlottable1D::selectTestRect method.
16043
16044 Then, the actual selection is done by calling the plottables' \ref
16045 QCPAbstractPlottable::selectEvent, placing the found selected data points in the \a details
16046 parameter as <tt>QVariant(\ref QCPDataSelection)</tt>. All plottables that weren't touched by \a
16047 rect receive a \ref QCPAbstractPlottable::deselectEvent.
16048
16049 \see processRectZoom
16050*/
16052{
16053 typedef QPair<QCPAbstractPlottable*, QCPDataSelection> SelectionCandidate;
16054 typedef QMultiMap<int, SelectionCandidate> SelectionCandidates; // map key is number of selected data points, so we have selections sorted by size
16055
16056 bool selectionStateChanged = false;
16057
16058 if (mInteractions.testFlag(QCP::iSelectPlottables))
16059 {
16060 SelectionCandidates potentialSelections;
16061 QRectF rectF(rect.normalized());
16062 if (QCPAxisRect *affectedAxisRect = axisRectAt(rectF.topLeft()))
16063 {
16064 // determine plottables that were hit by the rect and thus are candidates for selection:
16065 foreach (QCPAbstractPlottable *plottable, affectedAxisRect->plottables())
16066 {
16067 if (QCPPlottableInterface1D *plottableInterface = plottable->interface1D())
16068 {
16069 QCPDataSelection dataSel = plottableInterface->selectTestRect(rectF, true);
16070 if (!dataSel.isEmpty())
16071 potentialSelections.insert(dataSel.dataPointCount(), SelectionCandidate(plottable, dataSel));
16072 }
16073 }
16074
16075 if (!mInteractions.testFlag(QCP::iMultiSelect))
16076 {
16077 // only leave plottable with most selected points in map, since we will only select a single plottable:
16078 if (!potentialSelections.isEmpty())
16079 {
16080 SelectionCandidates::iterator it = potentialSelections.begin();
16081 while (it != std::prev(potentialSelections.end())) // erase all except last element
16082 it = potentialSelections.erase(it);
16083 }
16084 }
16085
16086 bool additive = event->modifiers().testFlag(mMultiSelectModifier);
16087 // deselect all other layerables if not additive selection:
16088 if (!additive)
16089 {
16090 // emit deselection except to those plottables who will be selected afterwards:
16091 foreach (QCPLayer *layer, mLayers)
16092 {
16093 foreach (QCPLayerable *layerable, layer->children())
16094 {
16095 if ((potentialSelections.isEmpty() || potentialSelections.constBegin()->first != layerable) && mInteractions.testFlag(layerable->selectionCategory()))
16096 {
16097 bool selChanged = false;
16098 layerable->deselectEvent(&selChanged);
16099 selectionStateChanged |= selChanged;
16100 }
16101 }
16102 }
16103 }
16104
16105 // go through selections in reverse (largest selection first) and emit select events:
16106 SelectionCandidates::const_iterator it = potentialSelections.constEnd();
16107 while (it != potentialSelections.constBegin())
16108 {
16109 --it;
16110 if (mInteractions.testFlag(it.value().first->selectionCategory()))
16111 {
16112 bool selChanged = false;
16113 it.value().first->selectEvent(event, additive, QVariant::fromValue(it.value().second), &selChanged);
16114 selectionStateChanged |= selChanged;
16115 }
16116 }
16117 }
16118 }
16119
16120 if (selectionStateChanged)
16121 {
16124 } else if (mSelectionRect)
16125 mSelectionRect->layer()->replot();
16126}
16127
16128/*! \internal
16129
16130 This slot is connected to the selection rect's \ref QCPSelectionRect::accepted signal when \ref
16131 setSelectionRectMode is set to \ref QCP::srmZoom.
16132
16133 It determines which axis rect was the origin of the selection rect judging by the starting point
16134 of the selection, and then zooms the axes defined via \ref QCPAxisRect::setRangeZoomAxes to the
16135 provided \a rect (see \ref QCPAxisRect::zoom).
16136
16137 \see processRectSelection
16138*/
16140{
16141 Q_UNUSED(event)
16142 if (QCPAxisRect *axisRect = axisRectAt(rect.topLeft()))
16143 {
16145 affectedAxes.removeAll(static_cast<QCPAxis*>(nullptr));
16146 axisRect->zoom(QRectF(rect), affectedAxes);
16147 }
16148 replot(rpQueuedReplot); // always replot to make selection rect disappear
16149}
16150
16151/*! \internal
16152
16153 This method is called when a simple left mouse click was detected on the QCustomPlot surface.
16154
16155 It first determines the layerable that was hit by the click, and then calls its \ref
16156 QCPLayerable::selectEvent. All other layerables receive a QCPLayerable::deselectEvent (unless the
16157 multi-select modifier was pressed, see \ref setMultiSelectModifier).
16158
16159 In this method the hit layerable is determined a second time using \ref layerableAt (after the
16160 one in \ref mousePressEvent), because we want \a onlySelectable set to true this time. This
16161 implies that the mouse event grabber (mMouseEventLayerable) may be a different one from the
16162 clicked layerable determined here. For example, if a non-selectable layerable is in front of a
16163 selectable layerable at the click position, the front layerable will receive mouse events but the
16164 selectable one in the back will receive the \ref QCPLayerable::selectEvent.
16165
16166 \see processRectSelection, QCPLayerable::selectTest
16167*/
16169{
16170 QVariant details;
16171 QCPLayerable *clickedLayerable = layerableAt(event->pos(), true, &details);
16172 bool selectionStateChanged = false;
16173 bool additive = mInteractions.testFlag(QCP::iMultiSelect) && event->modifiers().testFlag(mMultiSelectModifier);
16174 // deselect all other layerables if not additive selection:
16175 if (!additive)
16176 {
16177 foreach (QCPLayer *layer, mLayers)
16178 {
16179 foreach (QCPLayerable *layerable, layer->children())
16180 {
16181 if (layerable != clickedLayerable && mInteractions.testFlag(layerable->selectionCategory()))
16182 {
16183 bool selChanged = false;
16184 layerable->deselectEvent(&selChanged);
16185 selectionStateChanged |= selChanged;
16186 }
16187 }
16188 }
16189 }
16190 if (clickedLayerable && mInteractions.testFlag(clickedLayerable->selectionCategory()))
16191 {
16192 // a layerable was actually clicked, call its selectEvent:
16193 bool selChanged = false;
16194 clickedLayerable->selectEvent(event, additive, details, &selChanged);
16195 selectionStateChanged |= selChanged;
16196 }
16197 if (selectionStateChanged)
16198 {
16201 }
16202}
16203
16204/*! \internal
16205
16206 Registers the specified plottable with this QCustomPlot and, if \ref setAutoAddPlottableToLegend
16207 is enabled, adds it to the legend (QCustomPlot::legend). QCustomPlot takes ownership of the
16208 plottable.
16209
16210 Returns true on success, i.e. when \a plottable isn't already in this plot and the parent plot of
16211 \a plottable is this QCustomPlot.
16212
16213 This method is called automatically in the QCPAbstractPlottable base class constructor.
16214*/
16216{
16217 if (mPlottables.contains(plottable))
16218 {
16219 qDebug() << Q_FUNC_INFO << "plottable already added to this QCustomPlot:" << reinterpret_cast<quintptr>(plottable);
16220 return false;
16221 }
16222 if (plottable->parentPlot() != this)
16223 {
16224 qDebug() << Q_FUNC_INFO << "plottable not created with this QCustomPlot as parent:" << reinterpret_cast<quintptr>(plottable);
16225 return false;
16226 }
16227
16228 mPlottables.append(plottable);
16229 // possibly add plottable to legend:
16230 if (mAutoAddPlottableToLegend)
16232 if (!plottable->layer()) // usually the layer is already set in the constructor of the plottable (via QCPLayerable constructor)
16234 return true;
16235}
16236
16237/*! \internal
16238
16239 In order to maintain the simplified graph interface of QCustomPlot, this method is called by the
16240 QCPGraph constructor to register itself with this QCustomPlot's internal graph list. Returns true
16241 on success, i.e. if \a graph is valid and wasn't already registered with this QCustomPlot.
16242
16243 This graph specific registration happens in addition to the call to \ref registerPlottable by the
16244 QCPAbstractPlottable base class.
16245*/
16247{
16248 if (!graph)
16249 {
16250 qDebug() << Q_FUNC_INFO << "passed graph is zero";
16251 return false;
16252 }
16253 if (mGraphs.contains(graph))
16254 {
16255 qDebug() << Q_FUNC_INFO << "graph already registered with this QCustomPlot";
16256 return false;
16257 }
16258
16259 mGraphs.append(graph);
16260 return true;
16261}
16262
16263
16264/*! \internal
16265
16266 Registers the specified item with this QCustomPlot. QCustomPlot takes ownership of the item.
16267
16268 Returns true on success, i.e. when \a item wasn't already in the plot and the parent plot of \a
16269 item is this QCustomPlot.
16270
16271 This method is called automatically in the QCPAbstractItem base class constructor.
16272*/
16274{
16275 if (mItems.contains(item))
16276 {
16277 qDebug() << Q_FUNC_INFO << "item already added to this QCustomPlot:" << reinterpret_cast<quintptr>(item);
16278 return false;
16279 }
16280 if (item->parentPlot() != this)
16281 {
16282 qDebug() << Q_FUNC_INFO << "item not created with this QCustomPlot as parent:" << reinterpret_cast<quintptr>(item);
16283 return false;
16284 }
16285
16286 mItems.append(item);
16287 if (!item->layer()) // usually the layer is already set in the constructor of the item (via QCPLayerable constructor)
16289 return true;
16290}
16291
16292/*! \internal
16293
16294 Assigns all layers their index (QCPLayer::mIndex) in the mLayers list. This method is thus called
16295 after every operation that changes the layer indices, like layer removal, layer creation, layer
16296 moving.
16297*/
16299{
16300 for (int i=0; i<mLayers.size(); ++i)
16301 mLayers.at(i)->mIndex = i;
16302}
16303
16304/*! \internal
16305
16306 Returns the top-most layerable at pixel position \a pos. If \a onlySelectable is set to true,
16307 only those layerables that are selectable will be considered. (Layerable subclasses communicate
16308 their selectability via the QCPLayerable::selectTest method, by returning -1.)
16309
16310 \a selectionDetails is an output parameter that contains selection specifics of the affected
16311 layerable. This is useful if the respective layerable shall be given a subsequent
16312 QCPLayerable::selectEvent (like in \ref mouseReleaseEvent). \a selectionDetails usually contains
16313 information about which part of the layerable was hit, in multi-part layerables (e.g.
16314 QCPAxis::SelectablePart). If the layerable is a plottable, \a selectionDetails contains a \ref
16315 QCPDataSelection instance with the single data point which is closest to \a pos.
16316
16317 \see layerableListAt, layoutElementAt, axisRectAt
16318*/
16319QCPLayerable *QCustomPlot::layerableAt(const QPointF &pos, bool onlySelectable, QVariant *selectionDetails) const
16320{
16321 QList<QVariant> details;
16322 QList<QCPLayerable*> candidates = layerableListAt(pos, onlySelectable, selectionDetails ? &details : nullptr);
16323 if (selectionDetails && !details.isEmpty())
16324 *selectionDetails = details.first();
16325 if (!candidates.isEmpty())
16326 return candidates.first();
16327 else
16328 return nullptr;
16329}
16330
16331/*! \internal
16332
16333 Returns the layerables at pixel position \a pos. If \a onlySelectable is set to true, only those
16334 layerables that are selectable will be considered. (Layerable subclasses communicate their
16335 selectability via the QCPLayerable::selectTest method, by returning -1.)
16336
16337 The returned list is sorted by the layerable/drawing order such that the layerable that appears
16338 on top in the plot is at index 0 of the returned list. If you only need to know the top
16339 layerable, rather use \ref layerableAt.
16340
16341 \a selectionDetails is an output parameter that contains selection specifics of the affected
16342 layerable. This is useful if the respective layerable shall be given a subsequent
16343 QCPLayerable::selectEvent (like in \ref mouseReleaseEvent). \a selectionDetails usually contains
16344 information about which part of the layerable was hit, in multi-part layerables (e.g.
16345 QCPAxis::SelectablePart). If the layerable is a plottable, \a selectionDetails contains a \ref
16346 QCPDataSelection instance with the single data point which is closest to \a pos.
16347
16348 \see layerableAt, layoutElementAt, axisRectAt
16349*/
16350QList<QCPLayerable*> QCustomPlot::layerableListAt(const QPointF &pos, bool onlySelectable, QList<QVariant> *selectionDetails) const
16351{
16352 QList<QCPLayerable*> result;
16353 for (int layerIndex=mLayers.size()-1; layerIndex>=0; --layerIndex)
16354 {
16355 const QList<QCPLayerable*> layerables = mLayers.at(layerIndex)->children();
16356 for (int i=layerables.size()-1; i>=0; --i)
16357 {
16358 if (!layerables.at(i)->realVisibility())
16359 continue;
16360 QVariant details;
16361 double dist = layerables.at(i)->selectTest(pos, onlySelectable, selectionDetails ? &details : nullptr);
16362 if (dist >= 0 && dist < selectionTolerance())
16363 {
16364 result.append(layerables.at(i));
16365 if (selectionDetails)
16366 selectionDetails->append(details);
16367 }
16368 }
16369 }
16370 return result;
16371}
16372
16373/*!
16374 Saves the plot to a rastered image file \a fileName in the image format \a format. The plot is
16375 sized to \a width and \a height in pixels and scaled with \a scale. (width 100 and scale 2.0 lead
16376 to a full resolution file with width 200.) If the \a format supports compression, \a quality may
16377 be between 0 and 100 to control it.
16378
16379 Returns true on success. If this function fails, most likely the given \a format isn't supported
16380 by the system, see Qt docs about QImageWriter::supportedImageFormats().
16381
16382 The \a resolution will be written to the image file header (if the file format supports this) and
16383 has no direct consequence for the quality or the pixel size. However, if opening the image with a
16384 tool which respects the metadata, it will be able to scale the image to match either a given size
16385 in real units of length (inch, centimeters, etc.), or the target display DPI. You can specify in
16386 which units \a resolution is given, by setting \a resolutionUnit. The \a resolution is converted
16387 to the format's expected resolution unit internally.
16388
16389 \see saveBmp, saveJpg, savePng, savePdf
16390*/
16391bool QCustomPlot::saveRastered(const QString &fileName, int width, int height, double scale, const char *format, int quality, int resolution, QCP::ResolutionUnit resolutionUnit)
16392{
16393 QImage buffer = toPixmap(width, height, scale).toImage();
16394
16395 int dotsPerMeter = 0;
16396 switch (resolutionUnit)
16397 {
16398 case QCP::ruDotsPerMeter: dotsPerMeter = resolution; break;
16399 case QCP::ruDotsPerCentimeter: dotsPerMeter = resolution*100; break;
16400 case QCP::ruDotsPerInch: dotsPerMeter = int(resolution/0.0254); break;
16401 }
16402 buffer.setDotsPerMeterX(dotsPerMeter); // this is saved together with some image formats, e.g. PNG, and is relevant when opening image in other tools
16403 buffer.setDotsPerMeterY(dotsPerMeter); // this is saved together with some image formats, e.g. PNG, and is relevant when opening image in other tools
16404 if (!buffer.isNull())
16405 return buffer.save(fileName, format, quality);
16406 else
16407 return false;
16408}
16409
16410/*!
16411 Renders the plot to a pixmap and returns it.
16412
16413 The plot is sized to \a width and \a height in pixels and scaled with \a scale. (width 100 and
16414 scale 2.0 lead to a full resolution pixmap with width 200.)
16415
16416 \see toPainter, saveRastered, saveBmp, savePng, saveJpg, savePdf
16417*/
16418QPixmap QCustomPlot::toPixmap(int width, int height, double scale)
16419{
16420 // this method is somewhat similar to toPainter. Change something here, and a change in toPainter might be necessary, too.
16421 int newWidth, newHeight;
16422 if (width == 0 || height == 0)
16423 {
16424 newWidth = this->width();
16425 newHeight = this->height();
16426 } else
16427 {
16428 newWidth = width;
16429 newHeight = height;
16430 }
16431 int scaledWidth = qRound(scale*newWidth);
16432 int scaledHeight = qRound(scale*newHeight);
16433
16434 QPixmap result(scaledWidth, scaledHeight);
16435 result.fill(mBackgroundBrush.style() == Qt::SolidPattern ? mBackgroundBrush.color() : Qt::transparent); // if using non-solid pattern, make transparent now and draw brush pattern later
16436 QCPPainter painter;
16437 painter.begin(&result);
16438 if (painter.isActive())
16439 {
16440 QRect oldViewport = viewport();
16441 setViewport(QRect(0, 0, newWidth, newHeight));
16443 if (!qFuzzyCompare(scale, 1.0))
16444 {
16445 if (scale > 1.0) // for scale < 1 we always want cosmetic pens where possible, because else lines might disappear for very small scales
16447 painter.scale(scale, scale);
16448 }
16449 if (mBackgroundBrush.style() != Qt::SolidPattern && mBackgroundBrush.style() != Qt::NoBrush) // solid fills were done a few lines above with QPixmap::fill
16450 painter.fillRect(mViewport, mBackgroundBrush);
16451 draw(&painter);
16452 setViewport(oldViewport);
16453 painter.end();
16454 } else // might happen if pixmap has width or height zero
16455 {
16456 qDebug() << Q_FUNC_INFO << "Couldn't activate painter on pixmap";
16457 return QPixmap();
16458 }
16459 return result;
16460}
16461
16462/*!
16463 Renders the plot using the passed \a painter.
16464
16465 The plot is sized to \a width and \a height in pixels. If the \a painter's scale is not 1.0, the resulting plot will
16466 appear scaled accordingly.
16467
16468 \note If you are restricted to using a QPainter (instead of QCPPainter), create a temporary QPicture and open a QCPPainter
16469 on it. Then call \ref toPainter with this QCPPainter. After ending the paint operation on the picture, draw it with
16470 the QPainter. This will reproduce the painter actions the QCPPainter took, with a QPainter.
16471
16472 \see toPixmap
16473*/
16474void QCustomPlot::toPainter(QCPPainter *painter, int width, int height)
16475{
16476 // this method is somewhat similar to toPixmap. Change something here, and a change in toPixmap might be necessary, too.
16477 int newWidth, newHeight;
16478 if (width == 0 || height == 0)
16479 {
16480 newWidth = this->width();
16481 newHeight = this->height();
16482 } else
16483 {
16484 newWidth = width;
16485 newHeight = height;
16486 }
16487
16488 if (painter->isActive())
16489 {
16490 QRect oldViewport = viewport();
16491 setViewport(QRect(0, 0, newWidth, newHeight));
16493 if (mBackgroundBrush.style() != Qt::NoBrush) // unlike in toPixmap, we can't do QPixmap::fill for Qt::SolidPattern brush style, so we also draw solid fills with fillRect here
16494 painter->fillRect(mViewport, mBackgroundBrush);
16495 draw(painter);
16496 setViewport(oldViewport);
16497 } else
16498 qDebug() << Q_FUNC_INFO << "Passed painter is not active";
16499}
16500/* end of 'src/core.cpp' */
16501
16502
16503/* including file 'src/colorgradient.cpp' */
16504/* modified 2022-11-06T12:45:56, size 25408 */
16505
16506
16507////////////////////////////////////////////////////////////////////////////////////////////////////
16508//////////////////// QCPColorGradient
16509////////////////////////////////////////////////////////////////////////////////////////////////////
16510
16511/*! \class QCPColorGradient
16512 \brief Defines a color gradient for use with e.g. \ref QCPColorMap
16513
16514 This class describes a color gradient which can be used to encode data with color. For example,
16515 QCPColorMap and QCPColorScale have \ref QCPColorMap::setGradient "setGradient" methods which
16516 take an instance of this class. Colors are set with \ref setColorStopAt(double position, const QColor &color)
16517 with a \a position from 0 to 1. In between these defined color positions, the
16518 color will be interpolated linearly either in RGB or HSV space, see \ref setColorInterpolation.
16519
16520 Alternatively, load one of the preset color gradients shown in the image below, with \ref
16521 loadPreset, or by directly specifying the preset in the constructor.
16522
16523 Apart from red, green and blue components, the gradient also interpolates the alpha values of the
16524 configured color stops. This allows to display some portions of the data range as transparent in
16525 the plot.
16526
16527 How NaN values are interpreted can be configured with \ref setNanHandling.
16528
16529 \image html QCPColorGradient.png
16530
16531 The constructor \ref QCPColorGradient(GradientPreset preset) allows directly converting a \ref
16532 GradientPreset to a QCPColorGradient. This means that you can directly pass \ref GradientPreset
16533 to all the \a setGradient methods, e.g.:
16534 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcolorgradient-setgradient
16535
16536 The total number of levels used in the gradient can be set with \ref setLevelCount. Whether the
16537 color gradient shall be applied periodically (wrapping around) to data values that lie outside
16538 the data range specified on the plottable instance can be controlled with \ref setPeriodic.
16539*/
16540
16541/*!
16542 Constructs a new, empty QCPColorGradient with no predefined color stops. You can add own color
16543 stops with \ref setColorStopAt.
16544
16545 The color level count is initialized to 350.
16546*/
16548 mLevelCount(350),
16549 mColorInterpolation(ciRGB),
16550 mNanHandling(nhNone),
16551 mNanColor(Qt::black),
16552 mPeriodic(false),
16553 mColorBufferInvalidated(true)
16554{
16555 mColorBuffer.fill(qRgb(0, 0, 0), mLevelCount);
16556}
16557
16558/*!
16559 Constructs a new QCPColorGradient initialized with the colors and color interpolation according
16560 to \a preset.
16561
16562 The color level count is initialized to 350.
16563*/
16565 mLevelCount(350),
16566 mColorInterpolation(ciRGB),
16567 mNanHandling(nhNone),
16568 mNanColor(Qt::black),
16569 mPeriodic(false),
16570 mColorBufferInvalidated(true)
16571{
16572 mColorBuffer.fill(qRgb(0, 0, 0), mLevelCount);
16573 loadPreset(preset);
16574}
16575
16576/* undocumented operator */
16577bool QCPColorGradient::operator==(const QCPColorGradient &other) const
16578{
16579 return ((other.mLevelCount == this->mLevelCount) &&
16580 (other.mColorInterpolation == this->mColorInterpolation) &&
16581 (other.mNanHandling == this ->mNanHandling) &&
16582 (other.mNanColor == this->mNanColor) &&
16583 (other.mPeriodic == this->mPeriodic) &&
16584 (other.mColorStops == this->mColorStops));
16585}
16586
16587/*!
16588 Sets the number of discretization levels of the color gradient to \a n. The default is 350 which
16589 is typically enough to create a smooth appearance. The minimum number of levels is 2.
16590
16591 \image html QCPColorGradient-levelcount.png
16592*/
16594{
16595 if (n < 2)
16596 {
16597 qDebug() << Q_FUNC_INFO << "n must be greater or equal 2 but was" << n;
16598 n = 2;
16599 }
16600 if (n != mLevelCount)
16601 {
16602 mLevelCount = n;
16603 mColorBufferInvalidated = true;
16604 }
16605}
16606
16607/*!
16608 Sets at which positions from 0 to 1 which color shall occur. The positions are the keys, the
16609 colors are the values of the passed QMap \a colorStops. In between these color stops, the color
16610 is interpolated according to \ref setColorInterpolation.
16611
16612 A more convenient way to create a custom gradient may be to clear all color stops with \ref
16613 clearColorStops (or creating a new, empty QCPColorGradient) and then adding them one by one with
16614 \ref setColorStopAt.
16615
16616 \see clearColorStops
16617*/
16619{
16620 mColorStops = colorStops;
16621 mColorBufferInvalidated = true;
16622}
16623
16624/*!
16625 Sets the \a color the gradient will have at the specified \a position (from 0 to 1). In between
16626 these color stops, the color is interpolated according to \ref setColorInterpolation.
16627
16628 \see setColorStops, clearColorStops
16629*/
16630void QCPColorGradient::setColorStopAt(double position, const QColor &color)
16631{
16632 mColorStops.insert(position, color);
16633 mColorBufferInvalidated = true;
16634}
16635
16636/*!
16637 Sets whether the colors in between the configured color stops (see \ref setColorStopAt) shall be
16638 interpolated linearly in RGB or in HSV color space.
16639
16640 For example, a sweep in RGB space from red to green will have a muddy brown intermediate color,
16641 whereas in HSV space the intermediate color is yellow.
16642*/
16644{
16645 if (interpolation != mColorInterpolation)
16646 {
16647 mColorInterpolation = interpolation;
16648 mColorBufferInvalidated = true;
16649 }
16650}
16651
16652/*!
16653 Sets how NaNs in the data are displayed in the plot.
16654
16655 \see setNanColor
16656*/
16658{
16659 mNanHandling = handling;
16660}
16661
16662/*!
16663 Sets the color that NaN data is represented by, if \ref setNanHandling is set
16664 to ref nhNanColor.
16665
16666 \see setNanHandling
16667*/
16669{
16670 mNanColor = color;
16671}
16672
16673/*!
16674 Sets whether data points that are outside the configured data range (e.g. \ref
16675 QCPColorMap::setDataRange) are colored by periodically repeating the color gradient or whether
16676 they all have the same color, corresponding to the respective gradient boundary color.
16677
16678 \image html QCPColorGradient-periodic.png
16679
16680 As shown in the image above, gradients that have the same start and end color are especially
16681 suitable for a periodic gradient mapping, since they produce smooth color transitions throughout
16682 the color map. A preset that has this property is \ref gpHues.
16683
16684 In practice, using periodic color gradients makes sense when the data corresponds to a periodic
16685 dimension, such as an angle or a phase. If this is not the case, the color encoding might become
16686 ambiguous, because multiple different data values are shown as the same color.
16687*/
16689{
16690 mPeriodic = enabled;
16691}
16692
16693/*! \overload
16694
16695 This method is used to quickly convert a \a data array to colors. The colors will be output in
16696 the array \a scanLine. Both \a data and \a scanLine must have the length \a n when passed to this
16697 function. The data range that shall be used for mapping the data value to the gradient is passed
16698 in \a range. \a logarithmic indicates whether the data values shall be mapped to colors
16699 logarithmically.
16700
16701 if \a data actually contains 2D-data linearized via <tt>[row*columnCount + column]</tt>, you can
16702 set \a dataIndexFactor to <tt>columnCount</tt> to convert a column instead of a row of the data
16703 array, in \a scanLine. \a scanLine will remain a regular (1D) array. This works because \a data
16704 is addressed <tt>data[i*dataIndexFactor]</tt>.
16705
16706 Use the overloaded method to additionally provide alpha map data.
16707
16708 The QRgb values that are placed in \a scanLine have their r, g, and b components premultiplied
16709 with alpha (see QImage::Format_ARGB32_Premultiplied).
16710*/
16711void QCPColorGradient::colorize(const double *data, const QCPRange &range, QRgb *scanLine, int n, int dataIndexFactor, bool logarithmic)
16712{
16713 // If you change something here, make sure to also adapt color() and the other colorize() overload
16714 if (!data)
16715 {
16716 qDebug() << Q_FUNC_INFO << "null pointer given as data";
16717 return;
16718 }
16719 if (!scanLine)
16720 {
16721 qDebug() << Q_FUNC_INFO << "null pointer given as scanLine";
16722 return;
16723 }
16724 if (mColorBufferInvalidated)
16726
16727 const bool skipNanCheck = mNanHandling == nhNone;
16728 const double posToIndexFactor = !logarithmic ? (mLevelCount-1)/range.size() : (mLevelCount-1)/qLn(range.upper/range.lower);
16729 for (int i=0; i<n; ++i)
16730 {
16731 const double value = data[dataIndexFactor*i];
16732 if (skipNanCheck || !std::isnan(value))
16733 {
16734 qint64 index = qint64((!logarithmic ? value-range.lower : qLn(value/range.lower)) * posToIndexFactor);
16735 if (!mPeriodic)
16736 {
16737 index = qBound(qint64(0), index, qint64(mLevelCount-1));
16738 } else
16739 {
16740 index %= mLevelCount;
16741 if (index < 0)
16742 index += mLevelCount;
16743 }
16744 scanLine[i] = mColorBuffer.at(index);
16745 } else
16746 {
16747 switch(mNanHandling)
16748 {
16749 case nhLowestColor: scanLine[i] = mColorBuffer.first(); break;
16750 case nhHighestColor: scanLine[i] = mColorBuffer.last(); break;
16751 case nhTransparent: scanLine[i] = qRgba(0, 0, 0, 0); break;
16752 case nhNanColor: scanLine[i] = mNanColor.rgba(); break;
16753 case nhNone: break; // shouldn't happen
16754 }
16755 }
16756 }
16757}
16758
16759/*! \overload
16760
16761 Additionally to the other overload of \ref colorize, this method takes the array \a alpha, which
16762 has the same size and structure as \a data and encodes the alpha information per data point.
16763
16764 The QRgb values that are placed in \a scanLine have their r, g and b components premultiplied
16765 with alpha (see QImage::Format_ARGB32_Premultiplied).
16766*/
16767void QCPColorGradient::colorize(const double *data, const unsigned char *alpha, const QCPRange &range, QRgb *scanLine, int n, int dataIndexFactor, bool logarithmic)
16768{
16769 // If you change something here, make sure to also adapt color() and the other colorize() overload
16770 if (!data)
16771 {
16772 qDebug() << Q_FUNC_INFO << "null pointer given as data";
16773 return;
16774 }
16775 if (!alpha)
16776 {
16777 qDebug() << Q_FUNC_INFO << "null pointer given as alpha";
16778 return;
16779 }
16780 if (!scanLine)
16781 {
16782 qDebug() << Q_FUNC_INFO << "null pointer given as scanLine";
16783 return;
16784 }
16785 if (mColorBufferInvalidated)
16787
16788 const bool skipNanCheck = mNanHandling == nhNone;
16789 const double posToIndexFactor = !logarithmic ? (mLevelCount-1)/range.size() : (mLevelCount-1)/qLn(range.upper/range.lower);
16790 for (int i=0; i<n; ++i)
16791 {
16792 const double value = data[dataIndexFactor*i];
16793 if (skipNanCheck || !std::isnan(value))
16794 {
16795 qint64 index = qint64((!logarithmic ? value-range.lower : qLn(value/range.lower)) * posToIndexFactor);
16796 if (!mPeriodic)
16797 {
16798 index = qBound(qint64(0), index, qint64(mLevelCount-1));
16799 } else
16800 {
16801 index %= mLevelCount;
16802 if (index < 0)
16803 index += mLevelCount;
16804 }
16805 if (alpha[dataIndexFactor*i] == 255)
16806 {
16807 scanLine[i] = mColorBuffer.at(index);
16808 } else
16809 {
16810 const QRgb rgb = mColorBuffer.at(index);
16811 const float alphaF = alpha[dataIndexFactor*i]/255.0f;
16812 scanLine[i] = qRgba(int(qRed(rgb)*alphaF), int(qGreen(rgb)*alphaF), int(qBlue(rgb)*alphaF), int(qAlpha(rgb)*alphaF)); // also multiply r,g,b with alpha, to conform to Format_ARGB32_Premultiplied
16813 }
16814 } else
16815 {
16816 switch(mNanHandling)
16817 {
16818 case nhLowestColor: scanLine[i] = mColorBuffer.first(); break;
16819 case nhHighestColor: scanLine[i] = mColorBuffer.last(); break;
16820 case nhTransparent: scanLine[i] = qRgba(0, 0, 0, 0); break;
16821 case nhNanColor: scanLine[i] = mNanColor.rgba(); break;
16822 case nhNone: break; // shouldn't happen
16823 }
16824 }
16825 }
16826}
16827
16828/*! \internal
16829
16830 This method is used to colorize a single data value given in \a position, to colors. The data
16831 range that shall be used for mapping the data value to the gradient is passed in \a range. \a
16832 logarithmic indicates whether the data value shall be mapped to a color logarithmically.
16833
16834 If an entire array of data values shall be converted, rather use \ref colorize, for better
16835 performance.
16836
16837 The returned QRgb has its r, g and b components premultiplied with alpha (see
16838 QImage::Format_ARGB32_Premultiplied).
16839*/
16840QRgb QCPColorGradient::color(double position, const QCPRange &range, bool logarithmic)
16841{
16842 // If you change something here, make sure to also adapt ::colorize()
16843 if (mColorBufferInvalidated)
16845
16846 const bool skipNanCheck = mNanHandling == nhNone;
16847 if (!skipNanCheck && std::isnan(position))
16848 {
16849 switch(mNanHandling)
16850 {
16851 case nhLowestColor: return mColorBuffer.first();
16852 case nhHighestColor: return mColorBuffer.last();
16853 case nhTransparent: return qRgba(0, 0, 0, 0);
16854 case nhNanColor: return mNanColor.rgba();
16855 case nhNone: return qRgba(0, 0, 0, 0); // shouldn't happen
16856 }
16857 }
16858
16859 const double posToIndexFactor = !logarithmic ? (mLevelCount-1)/range.size() : (mLevelCount-1)/qLn(range.upper/range.lower);
16860 int index = int((!logarithmic ? position-range.lower : qLn(position/range.lower)) * posToIndexFactor);
16861 if (!mPeriodic)
16862 {
16863 index = qBound(0, index, mLevelCount-1);
16864 } else
16865 {
16866 index %= mLevelCount;
16867 if (index < 0)
16868 index += mLevelCount;
16869 }
16870 return mColorBuffer.at(index);
16871}
16872
16873/*!
16874 Clears the current color stops and loads the specified \a preset. A preset consists of predefined
16875 color stops and the corresponding color interpolation method.
16876
16877 The available presets are:
16878 \image html QCPColorGradient.png
16879*/
16881{
16883 switch (preset)
16884 {
16885 case gpGrayscale:
16889 break;
16890 case gpHot:
16892 setColorStopAt(0, QColor(50, 0, 0));
16893 setColorStopAt(0.2, QColor(180, 10, 0));
16894 setColorStopAt(0.4, QColor(245, 50, 0));
16895 setColorStopAt(0.6, QColor(255, 150, 10));
16896 setColorStopAt(0.8, QColor(255, 255, 50));
16897 setColorStopAt(1, QColor(255, 255, 255));
16898 break;
16899 case gpCold:
16901 setColorStopAt(0, QColor(0, 0, 50));
16902 setColorStopAt(0.2, QColor(0, 10, 180));
16903 setColorStopAt(0.4, QColor(0, 50, 245));
16904 setColorStopAt(0.6, QColor(10, 150, 255));
16905 setColorStopAt(0.8, QColor(50, 255, 255));
16906 setColorStopAt(1, QColor(255, 255, 255));
16907 break;
16908 case gpNight:
16910 setColorStopAt(0, QColor(10, 20, 30));
16911 setColorStopAt(1, QColor(250, 255, 250));
16912 break;
16913 case gpCandy:
16915 setColorStopAt(0, QColor(0, 0, 255));
16916 setColorStopAt(1, QColor(255, 250, 250));
16917 break;
16918 case gpGeography:
16920 setColorStopAt(0, QColor(70, 170, 210));
16921 setColorStopAt(0.20, QColor(90, 160, 180));
16922 setColorStopAt(0.25, QColor(45, 130, 175));
16923 setColorStopAt(0.30, QColor(100, 140, 125));
16924 setColorStopAt(0.5, QColor(100, 140, 100));
16925 setColorStopAt(0.6, QColor(130, 145, 120));
16926 setColorStopAt(0.7, QColor(140, 130, 120));
16927 setColorStopAt(0.9, QColor(180, 190, 190));
16928 setColorStopAt(1, QColor(210, 210, 230));
16929 break;
16930 case gpIon:
16932 setColorStopAt(0, QColor(50, 10, 10));
16933 setColorStopAt(0.45, QColor(0, 0, 255));
16934 setColorStopAt(0.8, QColor(0, 255, 255));
16935 setColorStopAt(1, QColor(0, 255, 0));
16936 break;
16937 case gpThermal:
16939 setColorStopAt(0, QColor(0, 0, 50));
16940 setColorStopAt(0.15, QColor(20, 0, 120));
16941 setColorStopAt(0.33, QColor(200, 30, 140));
16942 setColorStopAt(0.6, QColor(255, 100, 0));
16943 setColorStopAt(0.85, QColor(255, 255, 40));
16944 setColorStopAt(1, QColor(255, 255, 255));
16945 break;
16946 case gpPolar:
16948 setColorStopAt(0, QColor(50, 255, 255));
16949 setColorStopAt(0.18, QColor(10, 70, 255));
16950 setColorStopAt(0.28, QColor(10, 10, 190));
16951 setColorStopAt(0.5, QColor(0, 0, 0));
16952 setColorStopAt(0.72, QColor(190, 10, 10));
16953 setColorStopAt(0.82, QColor(255, 70, 10));
16954 setColorStopAt(1, QColor(255, 255, 50));
16955 break;
16956 case gpSpectrum:
16958 setColorStopAt(0, QColor(50, 0, 50));
16959 setColorStopAt(0.15, QColor(0, 0, 255));
16960 setColorStopAt(0.35, QColor(0, 255, 255));
16961 setColorStopAt(0.6, QColor(255, 255, 0));
16962 setColorStopAt(0.75, QColor(255, 30, 0));
16963 setColorStopAt(1, QColor(50, 0, 0));
16964 break;
16965 case gpJet:
16967 setColorStopAt(0, QColor(0, 0, 100));
16968 setColorStopAt(0.15, QColor(0, 50, 255));
16969 setColorStopAt(0.35, QColor(0, 255, 255));
16970 setColorStopAt(0.65, QColor(255, 255, 0));
16971 setColorStopAt(0.85, QColor(255, 30, 0));
16972 setColorStopAt(1, QColor(100, 0, 0));
16973 break;
16974 case gpHues:
16976 setColorStopAt(0, QColor(255, 0, 0));
16977 setColorStopAt(1.0/3.0, QColor(0, 0, 255));
16978 setColorStopAt(2.0/3.0, QColor(0, 255, 0));
16979 setColorStopAt(1, QColor(255, 0, 0));
16980 break;
16981 }
16982}
16983
16984/*!
16985 Clears all color stops.
16986
16987 \see setColorStops, setColorStopAt
16988*/
16990{
16991 mColorStops.clear();
16992 mColorBufferInvalidated = true;
16993}
16994
16995/*!
16996 Returns an inverted gradient. The inverted gradient has all properties as this \ref
16997 QCPColorGradient, but the order of the color stops is inverted.
16998
16999 \see setColorStops, setColorStopAt
17000*/
17002{
17003 QCPColorGradient result(*this);
17004 result.clearColorStops();
17005 for (QMap<double, QColor>::const_iterator it=mColorStops.constBegin(); it!=mColorStops.constEnd(); ++it)
17006 result.setColorStopAt(1.0-it.key(), it.value());
17007 return result;
17008}
17009
17010/*! \internal
17011
17012 Returns true if the color gradient uses transparency, i.e. if any of the configured color stops
17013 has an alpha value below 255.
17014*/
17016{
17017 for (QMap<double, QColor>::const_iterator it=mColorStops.constBegin(); it!=mColorStops.constEnd(); ++it)
17018 {
17019 if (it.value().alpha() < 255)
17020 return true;
17021 }
17022 return false;
17023}
17024
17025/*! \internal
17026
17027 Updates the internal color buffer which will be used by \ref colorize and \ref color, to quickly
17028 convert positions to colors. This is where the interpolation between color stops is calculated.
17029*/
17031{
17032 if (mColorBuffer.size() != mLevelCount)
17033 mColorBuffer.resize(mLevelCount);
17034 if (mColorStops.size() > 1)
17035 {
17036 double indexToPosFactor = 1.0/double(mLevelCount-1);
17037 const bool useAlpha = stopsUseAlpha();
17038 for (int i=0; i<mLevelCount; ++i)
17039 {
17040 double position = i*indexToPosFactor;
17041 QMap<double, QColor>::const_iterator it = const_cast<const QMap<double, QColor>*>(&mColorStops)->lowerBound(position); // force using the const lowerBound method
17042 if (it == mColorStops.constEnd()) // position is on or after last stop, use color of last stop
17043 {
17044 if (useAlpha)
17045 {
17046 const QColor col = std::prev(it).value();
17047 const double alphaPremultiplier = col.alpha()/255.0; // since we use QImage::Format_ARGB32_Premultiplied
17048 mColorBuffer[i] = qRgba(int(col.red()*alphaPremultiplier),
17049 int(col.green()*alphaPremultiplier),
17050 int(col.blue()*alphaPremultiplier),
17051 col.alpha());
17052 } else
17053 mColorBuffer[i] = std::prev(it).value().rgba();
17054 } else if (it == mColorStops.constBegin()) // position is on or before first stop, use color of first stop
17055 {
17056 if (useAlpha)
17057 {
17058 const QColor &col = it.value();
17059 const double alphaPremultiplier = col.alpha()/255.0; // since we use QImage::Format_ARGB32_Premultiplied
17060 mColorBuffer[i] = qRgba(int(col.red()*alphaPremultiplier),
17061 int(col.green()*alphaPremultiplier),
17062 int(col.blue()*alphaPremultiplier),
17063 col.alpha());
17064 } else
17065 mColorBuffer[i] = it.value().rgba();
17066 } else // position is in between stops (or on an intermediate stop), interpolate color
17067 {
17069 QMap<double, QColor>::const_iterator low = std::prev(it);
17070 double t = (position-low.key())/(high.key()-low.key()); // interpolation factor 0..1
17071 switch (mColorInterpolation)
17072 {
17073 case ciRGB:
17074 {
17075 if (useAlpha)
17076 {
17077 const int alpha = int((1-t)*low.value().alpha() + t*high.value().alpha());
17078 const double alphaPremultiplier = alpha/255.0; // since we use QImage::Format_ARGB32_Premultiplied
17079 mColorBuffer[i] = qRgba(int( ((1-t)*low.value().red() + t*high.value().red())*alphaPremultiplier ),
17080 int( ((1-t)*low.value().green() + t*high.value().green())*alphaPremultiplier ),
17081 int( ((1-t)*low.value().blue() + t*high.value().blue())*alphaPremultiplier ),
17082 alpha);
17083 } else
17084 {
17085 mColorBuffer[i] = qRgb(int( ((1-t)*low.value().red() + t*high.value().red()) ),
17086 int( ((1-t)*low.value().green() + t*high.value().green()) ),
17087 int( ((1-t)*low.value().blue() + t*high.value().blue())) );
17088 }
17089 break;
17090 }
17091 case ciHSV:
17092 {
17093 QColor lowHsv = low.value().toHsv();
17094 QColor highHsv = high.value().toHsv();
17095 double hue = 0;
17096 double hueDiff = highHsv.hueF()-lowHsv.hueF();
17097 if (hueDiff > 0.5)
17098 hue = lowHsv.hueF() - t*(1.0-hueDiff);
17099 else if (hueDiff < -0.5)
17100 hue = lowHsv.hueF() + t*(1.0+hueDiff);
17101 else
17102 hue = lowHsv.hueF() + t*hueDiff;
17103 if (hue < 0) hue += 1.0;
17104 else if (hue >= 1.0) hue -= 1.0;
17105 if (useAlpha)
17106 {
17107 const QRgb rgb = QColor::fromHsvF(hue,
17108 (1-t)*lowHsv.saturationF() + t*highHsv.saturationF(),
17109 (1-t)*lowHsv.valueF() + t*highHsv.valueF()).rgb();
17110 const double alpha = (1-t)*lowHsv.alphaF() + t*highHsv.alphaF();
17111 mColorBuffer[i] = qRgba(int(qRed(rgb)*alpha), int(qGreen(rgb)*alpha), int(qBlue(rgb)*alpha), int(255*alpha));
17112 }
17113 else
17114 {
17115 mColorBuffer[i] = QColor::fromHsvF(hue,
17116 (1-t)*lowHsv.saturationF() + t*highHsv.saturationF(),
17117 (1-t)*lowHsv.valueF() + t*highHsv.valueF()).rgb();
17118 }
17119 break;
17120 }
17121 }
17122 }
17123 }
17124 } else if (mColorStops.size() == 1)
17125 {
17126 const QRgb rgb = mColorStops.constBegin().value().rgb();
17127 const double alpha = mColorStops.constBegin().value().alphaF();
17128 mColorBuffer.fill(qRgba(int(qRed(rgb)*alpha), int(qGreen(rgb)*alpha), int(qBlue(rgb)*alpha), int(255*alpha)));
17129 } else // mColorStops is empty, fill color buffer with black
17130 {
17131 mColorBuffer.fill(qRgb(0, 0, 0));
17132 }
17133 mColorBufferInvalidated = false;
17134}
17135/* end of 'src/colorgradient.cpp' */
17136
17137
17138/* including file 'src/selectiondecorator-bracket.cpp' */
17139/* modified 2022-11-06T12:45:56, size 12308 */
17140
17141////////////////////////////////////////////////////////////////////////////////////////////////////
17142//////////////////// QCPSelectionDecoratorBracket
17143////////////////////////////////////////////////////////////////////////////////////////////////////
17144
17145/*! \class QCPSelectionDecoratorBracket
17146 \brief A selection decorator which draws brackets around each selected data segment
17147
17148 Additionally to the regular highlighting of selected segments via color, fill and scatter style,
17149 this \ref QCPSelectionDecorator subclass draws markers at the begin and end of each selected data
17150 segment of the plottable.
17151
17152 The shape of the markers can be controlled with \ref setBracketStyle, \ref setBracketWidth and
17153 \ref setBracketHeight. The color/fill can be controlled with \ref setBracketPen and \ref
17154 setBracketBrush.
17155
17156 To introduce custom bracket styles, it is only necessary to sublcass \ref
17157 QCPSelectionDecoratorBracket and reimplement \ref drawBracket. The rest will be managed by the
17158 base class.
17159*/
17160
17161/*!
17162 Creates a new QCPSelectionDecoratorBracket instance with default values.
17163*/
17165 mBracketPen(QPen(Qt::black)),
17166 mBracketBrush(Qt::NoBrush),
17167 mBracketWidth(5),
17168 mBracketHeight(50),
17169 mBracketStyle(bsSquareBracket),
17170 mTangentToData(false),
17171 mTangentAverage(2)
17172{
17173
17174}
17175
17176QCPSelectionDecoratorBracket::~QCPSelectionDecoratorBracket()
17177{
17178}
17179
17180/*!
17181 Sets the pen that will be used to draw the brackets at the beginning and end of each selected
17182 data segment.
17183*/
17185{
17186 mBracketPen = pen;
17187}
17188
17189/*!
17190 Sets the brush that will be used to draw the brackets at the beginning and end of each selected
17191 data segment.
17192*/
17194{
17195 mBracketBrush = brush;
17196}
17197
17198/*!
17199 Sets the width of the drawn bracket. The width dimension is always parallel to the key axis of
17200 the data, or the tangent direction of the current data slope, if \ref setTangentToData is
17201 enabled.
17202*/
17204{
17205 mBracketWidth = width;
17206}
17207
17208/*!
17209 Sets the height of the drawn bracket. The height dimension is always perpendicular to the key axis
17210 of the data, or the tangent direction of the current data slope, if \ref setTangentToData is
17211 enabled.
17212*/
17214{
17215 mBracketHeight = height;
17216}
17217
17218/*!
17219 Sets the shape that the bracket/marker will have.
17220
17221 \see setBracketWidth, setBracketHeight
17222*/
17227
17228/*!
17229 Sets whether the brackets will be rotated such that they align with the slope of the data at the
17230 position that they appear in.
17231
17232 For noisy data, it might be more visually appealing to average the slope over multiple data
17233 points. This can be configured via \ref setTangentAverage.
17234*/
17236{
17237 mTangentToData = enabled;
17238}
17239
17240/*!
17241 Controls over how many data points the slope shall be averaged, when brackets shall be aligned
17242 with the data (if \ref setTangentToData is true).
17243
17244 From the position of the bracket, \a pointCount points towards the selected data range will be
17245 taken into account. The smallest value of \a pointCount is 1, which is effectively equivalent to
17246 disabling \ref setTangentToData.
17247*/
17249{
17250 mTangentAverage = pointCount;
17251 if (mTangentAverage < 1)
17252 mTangentAverage = 1;
17253}
17254
17255/*!
17256 Draws the bracket shape with \a painter. The parameter \a direction is either -1 or 1 and
17257 indicates whether the bracket shall point to the left or the right (i.e. is a closing or opening
17258 bracket, respectively).
17259
17260 The passed \a painter already contains all transformations that are necessary to position and
17261 rotate the bracket appropriately. Painting operations can be performed as if drawing upright
17262 brackets on flat data with horizontal key axis, with (0, 0) being the center of the bracket.
17263
17264 If you wish to sublcass \ref QCPSelectionDecoratorBracket in order to provide custom bracket
17265 shapes (see \ref QCPSelectionDecoratorBracket::bsUserStyle), this is the method you should
17266 reimplement.
17267*/
17269{
17270 switch (mBracketStyle)
17271 {
17272 case bsSquareBracket:
17273 {
17274 painter->drawLine(QLineF(mBracketWidth*direction, -mBracketHeight*0.5, 0, -mBracketHeight*0.5));
17275 painter->drawLine(QLineF(mBracketWidth*direction, mBracketHeight*0.5, 0, mBracketHeight*0.5));
17276 painter->drawLine(QLineF(0, -mBracketHeight*0.5, 0, mBracketHeight*0.5));
17277 break;
17278 }
17279 case bsHalfEllipse:
17280 {
17281 painter->drawArc(QRectF(-mBracketWidth*0.5, -mBracketHeight*0.5, mBracketWidth, mBracketHeight), -90*16, -180*16*direction);
17282 break;
17283 }
17284 case bsEllipse:
17285 {
17286 painter->drawEllipse(QRectF(-mBracketWidth*0.5, -mBracketHeight*0.5, mBracketWidth, mBracketHeight));
17287 break;
17288 }
17289 case bsPlus:
17290 {
17291 painter->drawLine(QLineF(0, -mBracketHeight*0.5, 0, mBracketHeight*0.5));
17292 painter->drawLine(QLineF(-mBracketWidth*0.5, 0, mBracketWidth*0.5, 0));
17293 break;
17294 }
17295 default:
17296 {
17297 qDebug() << Q_FUNC_INFO << "unknown/custom bracket style can't be handeld by default implementation:" << static_cast<int>(mBracketStyle);
17298 break;
17299 }
17300 }
17301}
17302
17303/*!
17304 Draws the bracket decoration on the data points at the begin and end of each selected data
17305 segment given in \a seletion.
17306
17307 It uses the method \ref drawBracket to actually draw the shapes.
17308
17309 \seebaseclassmethod
17310*/
17312{
17313 if (!mPlottable || selection.isEmpty()) return;
17314
17315 if (QCPPlottableInterface1D *interface1d = mPlottable->interface1D())
17316 {
17317 foreach (const QCPDataRange &dataRange, selection.dataRanges())
17318 {
17319 // determine position and (if tangent mode is enabled) angle of brackets:
17320 int openBracketDir = (mPlottable->keyAxis() && !mPlottable->keyAxis()->rangeReversed()) ? 1 : -1;
17321 int closeBracketDir = -openBracketDir;
17322 QPointF openBracketPos = getPixelCoordinates(interface1d, dataRange.begin());
17323 QPointF closeBracketPos = getPixelCoordinates(interface1d, dataRange.end()-1);
17324 double openBracketAngle = 0;
17325 double closeBracketAngle = 0;
17326 if (mTangentToData)
17327 {
17328 openBracketAngle = getTangentAngle(interface1d, dataRange.begin(), openBracketDir);
17329 closeBracketAngle = getTangentAngle(interface1d, dataRange.end()-1, closeBracketDir);
17330 }
17331 // draw opening bracket:
17332 QTransform oldTransform = painter->transform();
17333 painter->setPen(mBracketPen);
17334 painter->setBrush(mBracketBrush);
17335 painter->translate(openBracketPos);
17336 painter->rotate(openBracketAngle/M_PI*180.0);
17337 drawBracket(painter, openBracketDir);
17338 painter->setTransform(oldTransform);
17339 // draw closing bracket:
17340 painter->setPen(mBracketPen);
17341 painter->setBrush(mBracketBrush);
17342 painter->translate(closeBracketPos);
17343 painter->rotate(closeBracketAngle/M_PI*180.0);
17344 drawBracket(painter, closeBracketDir);
17345 painter->setTransform(oldTransform);
17346 }
17347 }
17348}
17349
17350/*! \internal
17351
17352 If \ref setTangentToData is enabled, brackets need to be rotated according to the data slope.
17353 This method returns the angle in radians by which a bracket at the given \a dataIndex must be
17354 rotated.
17355
17356 The parameter \a direction must be set to either -1 or 1, representing whether it is an opening
17357 or closing bracket. Since for slope calculation multiple data points are required, this defines
17358 the direction in which the algorithm walks, starting at \a dataIndex, to average those data
17359 points. (see \ref setTangentToData and \ref setTangentAverage)
17360
17361 \a interface1d is the interface to the plottable's data which is used to query data coordinates.
17362*/
17363double QCPSelectionDecoratorBracket::getTangentAngle(const QCPPlottableInterface1D *interface1d, int dataIndex, int direction) const
17364{
17365 if (!interface1d || dataIndex < 0 || dataIndex >= interface1d->dataCount())
17366 return 0;
17367 direction = direction < 0 ? -1 : 1; // enforce direction is either -1 or 1
17368
17369 // how many steps we can actually go from index in the given direction without exceeding data bounds:
17370 int averageCount;
17371 if (direction < 0)
17372 averageCount = qMin(mTangentAverage, dataIndex);
17373 else
17374 averageCount = qMin(mTangentAverage, interface1d->dataCount()-1-dataIndex);
17375 qDebug() << averageCount;
17376 // calculate point average of averageCount points:
17377 QVector<QPointF> points(averageCount);
17378 QPointF pointsAverage;
17379 int currentIndex = dataIndex;
17380 for (int i=0; i<averageCount; ++i)
17381 {
17382 points[i] = getPixelCoordinates(interface1d, currentIndex);
17383 pointsAverage += points[i];
17384 currentIndex += direction;
17385 }
17386 pointsAverage /= double(averageCount);
17387
17388 // calculate slope of linear regression through points:
17389 double numSum = 0;
17390 double denomSum = 0;
17391 for (int i=0; i<averageCount; ++i)
17392 {
17393 const double dx = points.at(i).x()-pointsAverage.x();
17394 const double dy = points.at(i).y()-pointsAverage.y();
17395 numSum += dx*dy;
17396 denomSum += dx*dx;
17397 }
17398 if (!qFuzzyIsNull(denomSum) && !qFuzzyIsNull(numSum))
17399 {
17400 return qAtan2(numSum, denomSum);
17401 } else // undetermined angle, probably mTangentAverage == 1, so using only one data point
17402 return 0;
17403}
17404
17405/*! \internal
17406
17407 Returns the pixel coordinates of the data point at \a dataIndex, using \a interface1d to access
17408 the data points.
17409*/
17411{
17412 QCPAxis *keyAxis = mPlottable->keyAxis();
17413 QCPAxis *valueAxis = mPlottable->valueAxis();
17414 if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return {0, 0}; }
17415
17416 if (keyAxis->orientation() == Qt::Horizontal)
17417 return {keyAxis->coordToPixel(interface1d->dataMainKey(dataIndex)), valueAxis->coordToPixel(interface1d->dataMainValue(dataIndex))};
17418 else
17419 return {valueAxis->coordToPixel(interface1d->dataMainValue(dataIndex)), keyAxis->coordToPixel(interface1d->dataMainKey(dataIndex))};
17420}
17421/* end of 'src/selectiondecorator-bracket.cpp' */
17422
17423
17424/* including file 'src/layoutelements/layoutelement-axisrect.cpp' */
17425/* modified 2022-11-06T12:45:56, size 47193 */
17426
17427
17428////////////////////////////////////////////////////////////////////////////////////////////////////
17429//////////////////// QCPAxisRect
17430////////////////////////////////////////////////////////////////////////////////////////////////////
17431
17432/*! \class QCPAxisRect
17433 \brief Holds multiple axes and arranges them in a rectangular shape.
17434
17435 This class represents an axis rect, a rectangular area that is bounded on all sides with an
17436 arbitrary number of axes.
17437
17438 Initially QCustomPlot has one axis rect, accessible via QCustomPlot::axisRect(). However, the
17439 layout system allows to have multiple axis rects, e.g. arranged in a grid layout
17440 (QCustomPlot::plotLayout).
17441
17442 By default, QCPAxisRect comes with four axes, at bottom, top, left and right. They can be
17443 accessed via \ref axis by providing the respective axis type (\ref QCPAxis::AxisType) and index.
17444 If you need all axes in the axis rect, use \ref axes. The top and right axes are set to be
17445 invisible initially (QCPAxis::setVisible). To add more axes to a side, use \ref addAxis or \ref
17446 addAxes. To remove an axis, use \ref removeAxis.
17447
17448 The axis rect layerable itself only draws a background pixmap or color, if specified (\ref
17449 setBackground). It is placed on the "background" layer initially (see \ref QCPLayer for an
17450 explanation of the QCustomPlot layer system). The axes that are held by the axis rect can be
17451 placed on other layers, independently of the axis rect.
17452
17453 Every axis rect has a child layout of type \ref QCPLayoutInset. It is accessible via \ref
17454 insetLayout and can be used to have other layout elements (or even other layouts with multiple
17455 elements) hovering inside the axis rect.
17456
17457 If an axis rect is clicked and dragged, it processes this by moving certain axis ranges. The
17458 behaviour can be controlled with \ref setRangeDrag and \ref setRangeDragAxes. If the mouse wheel
17459 is scrolled while the cursor is on the axis rect, certain axes are scaled. This is controllable
17460 via \ref setRangeZoom, \ref setRangeZoomAxes and \ref setRangeZoomFactor. These interactions are
17461 only enabled if \ref QCustomPlot::setInteractions contains \ref QCP::iRangeDrag and \ref
17462 QCP::iRangeZoom.
17463
17464 \image html AxisRectSpacingOverview.png
17465 <center>Overview of the spacings and paddings that define the geometry of an axis. The dashed
17466 line on the far left indicates the viewport/widget border.</center>
17467*/
17468
17469/* start documentation of inline functions */
17470
17471/*! \fn QCPLayoutInset *QCPAxisRect::insetLayout() const
17472
17473 Returns the inset layout of this axis rect. It can be used to place other layout elements (or
17474 even layouts with multiple other elements) inside/on top of an axis rect.
17475
17476 \see QCPLayoutInset
17477*/
17478
17479/*! \fn int QCPAxisRect::left() const
17480
17481 Returns the pixel position of the left border of this axis rect. Margins are not taken into
17482 account here, so the returned value is with respect to the inner \ref rect.
17483*/
17484
17485/*! \fn int QCPAxisRect::right() const
17486
17487 Returns the pixel position of the right border of this axis rect. Margins are not taken into
17488 account here, so the returned value is with respect to the inner \ref rect.
17489*/
17490
17491/*! \fn int QCPAxisRect::top() const
17492
17493 Returns the pixel position of the top border of this axis rect. Margins are not taken into
17494 account here, so the returned value is with respect to the inner \ref rect.
17495*/
17496
17497/*! \fn int QCPAxisRect::bottom() const
17498
17499 Returns the pixel position of the bottom border of this axis rect. Margins are not taken into
17500 account here, so the returned value is with respect to the inner \ref rect.
17501*/
17502
17503/*! \fn int QCPAxisRect::width() const
17504
17505 Returns the pixel width of this axis rect. Margins are not taken into account here, so the
17506 returned value is with respect to the inner \ref rect.
17507*/
17508
17509/*! \fn int QCPAxisRect::height() const
17510
17511 Returns the pixel height of this axis rect. Margins are not taken into account here, so the
17512 returned value is with respect to the inner \ref rect.
17513*/
17514
17515/*! \fn QSize QCPAxisRect::size() const
17516
17517 Returns the pixel size of this axis rect. Margins are not taken into account here, so the
17518 returned value is with respect to the inner \ref rect.
17519*/
17520
17521/*! \fn QPoint QCPAxisRect::topLeft() const
17522
17523 Returns the top left corner of this axis rect in pixels. Margins are not taken into account here,
17524 so the returned value is with respect to the inner \ref rect.
17525*/
17526
17527/*! \fn QPoint QCPAxisRect::topRight() const
17528
17529 Returns the top right corner of this axis rect in pixels. Margins are not taken into account
17530 here, so the returned value is with respect to the inner \ref rect.
17531*/
17532
17533/*! \fn QPoint QCPAxisRect::bottomLeft() const
17534
17535 Returns the bottom left corner of this axis rect in pixels. Margins are not taken into account
17536 here, so the returned value is with respect to the inner \ref rect.
17537*/
17538
17539/*! \fn QPoint QCPAxisRect::bottomRight() const
17540
17541 Returns the bottom right corner of this axis rect in pixels. Margins are not taken into account
17542 here, so the returned value is with respect to the inner \ref rect.
17543*/
17544
17545/*! \fn QPoint QCPAxisRect::center() const
17546
17547 Returns the center of this axis rect in pixels. Margins are not taken into account here, so the
17548 returned value is with respect to the inner \ref rect.
17549*/
17550
17551/* end documentation of inline functions */
17552
17553/*!
17554 Creates a QCPAxisRect instance and sets default values. An axis is added for each of the four
17555 sides, the top and right axes are set invisible initially.
17556*/
17557QCPAxisRect::QCPAxisRect(QCustomPlot *parentPlot, bool setupDefaultAxes) :
17558 QCPLayoutElement(parentPlot),
17559 mBackgroundBrush(Qt::NoBrush),
17560 mBackgroundScaled(true),
17561 mBackgroundScaledMode(Qt::KeepAspectRatioByExpanding),
17562 mInsetLayout(new QCPLayoutInset),
17563 mRangeDrag(Qt::Horizontal|Qt::Vertical),
17564 mRangeZoom(Qt::Horizontal|Qt::Vertical),
17565 mRangeZoomFactorHorz(0.85),
17566 mRangeZoomFactorVert(0.85),
17567 mDragging(false)
17568{
17569 mInsetLayout->initializeParentPlot(mParentPlot);
17570 mInsetLayout->setParentLayerable(this);
17571 mInsetLayout->setParent(this);
17572
17573 setMinimumSize(50, 50);
17574 setMinimumMargins(QMargins(15, 15, 15, 15));
17579
17580 if (setupDefaultAxes)
17581 {
17584 QCPAxis *xAxis2 = addAxis(QCPAxis::atTop);
17585 QCPAxis *yAxis2 = addAxis(QCPAxis::atRight);
17586 setRangeDragAxes(xAxis, yAxis);
17587 setRangeZoomAxes(xAxis, yAxis);
17588 xAxis2->setVisible(false);
17589 yAxis2->setVisible(false);
17590 xAxis->grid()->setVisible(true);
17591 yAxis->grid()->setVisible(true);
17592 xAxis2->grid()->setVisible(false);
17593 yAxis2->grid()->setVisible(false);
17594 xAxis2->grid()->setZeroLinePen(Qt::NoPen);
17595 yAxis2->grid()->setZeroLinePen(Qt::NoPen);
17596 xAxis2->grid()->setVisible(false);
17597 yAxis2->grid()->setVisible(false);
17598 }
17599}
17600
17601QCPAxisRect::~QCPAxisRect()
17602{
17603 delete mInsetLayout;
17604 mInsetLayout = nullptr;
17605
17606 foreach (QCPAxis *axis, axes())
17608}
17609
17610/*!
17611 Returns the number of axes on the axis rect side specified with \a type.
17612
17613 \see axis
17614*/
17616{
17617 return mAxes.value(type).size();
17618}
17619
17620/*!
17621 Returns the axis with the given \a index on the axis rect side specified with \a type.
17622
17623 \see axisCount, axes
17624*/
17626{
17627 QList<QCPAxis*> ax(mAxes.value(type));
17628 if (index >= 0 && index < ax.size())
17629 {
17630 return ax.at(index);
17631 } else
17632 {
17633 qDebug() << Q_FUNC_INFO << "Axis index out of bounds:" << index;
17634 return nullptr;
17635 }
17636}
17637
17638/*!
17639 Returns all axes on the axis rect sides specified with \a types.
17640
17641 \a types may be a single \ref QCPAxis::AxisType or an <tt>or</tt>-combination, to get the axes of
17642 multiple sides.
17643
17644 \see axis
17645*/
17647{
17648 QList<QCPAxis*> result;
17649 if (types.testFlag(QCPAxis::atLeft))
17650 result << mAxes.value(QCPAxis::atLeft);
17651 if (types.testFlag(QCPAxis::atRight))
17652 result << mAxes.value(QCPAxis::atRight);
17653 if (types.testFlag(QCPAxis::atTop))
17654 result << mAxes.value(QCPAxis::atTop);
17655 if (types.testFlag(QCPAxis::atBottom))
17656 result << mAxes.value(QCPAxis::atBottom);
17657 return result;
17658}
17659
17660/*! \overload
17661
17662 Returns all axes of this axis rect.
17663*/
17665{
17666 QList<QCPAxis*> result;
17668 while (it.hasNext())
17669 {
17670 it.next();
17671 result << it.value();
17672 }
17673 return result;
17674}
17675
17676/*!
17677 Adds a new axis to the axis rect side specified with \a type, and returns it. If \a axis is 0, a
17678 new QCPAxis instance is created internally. QCustomPlot owns the returned axis, so if you want to
17679 remove an axis, use \ref removeAxis instead of deleting it manually.
17680
17681 You may inject QCPAxis instances (or subclasses of QCPAxis) by setting \a axis to an axis that was
17682 previously created outside QCustomPlot. It is important to note that QCustomPlot takes ownership
17683 of the axis, so you may not delete it afterwards. Further, the \a axis must have been created
17684 with this axis rect as parent and with the same axis type as specified in \a type. If this is not
17685 the case, a debug output is generated, the axis is not added, and the method returns \c nullptr.
17686
17687 This method can not be used to move \a axis between axis rects. The same \a axis instance must
17688 not be added multiple times to the same or different axis rects.
17689
17690 If an axis rect side already contains one or more axes, the lower and upper endings of the new
17691 axis (\ref QCPAxis::setLowerEnding, \ref QCPAxis::setUpperEnding) are set to \ref
17692 QCPLineEnding::esHalfBar.
17693
17694 \see addAxes, setupFullAxesBox
17695*/
17697{
17698 QCPAxis *newAxis = axis;
17699 if (!newAxis)
17700 {
17701 newAxis = new QCPAxis(this, type);
17702 } else // user provided existing axis instance, do some sanity checks
17703 {
17704 if (newAxis->axisType() != type)
17705 {
17706 qDebug() << Q_FUNC_INFO << "passed axis has different axis type than specified in type parameter";
17707 return nullptr;
17708 }
17709 if (newAxis->axisRect() != this)
17710 {
17711 qDebug() << Q_FUNC_INFO << "passed axis doesn't have this axis rect as parent axis rect";
17712 return nullptr;
17713 }
17714 if (axes().contains(newAxis))
17715 {
17716 qDebug() << Q_FUNC_INFO << "passed axis is already owned by this axis rect";
17717 return nullptr;
17718 }
17719 }
17720 if (!mAxes[type].isEmpty()) // multiple axes on one side, add half-bar axis ending to additional axes with offset
17721 {
17722 bool invert = (type == QCPAxis::atRight) || (type == QCPAxis::atBottom);
17723 newAxis->setLowerEnding(QCPLineEnding(QCPLineEnding::esHalfBar, 6, 10, !invert));
17724 newAxis->setUpperEnding(QCPLineEnding(QCPLineEnding::esHalfBar, 6, 10, invert));
17725 }
17726 mAxes[type].append(newAxis);
17727
17728 // reset convenience axis pointers on parent QCustomPlot if they are unset:
17729 if (mParentPlot && mParentPlot->axisRectCount() > 0 && mParentPlot->axisRect(0) == this)
17730 {
17731 switch (type)
17732 {
17733 case QCPAxis::atBottom: { if (!mParentPlot->xAxis) mParentPlot->xAxis = newAxis; break; }
17734 case QCPAxis::atLeft: { if (!mParentPlot->yAxis) mParentPlot->yAxis = newAxis; break; }
17735 case QCPAxis::atTop: { if (!mParentPlot->xAxis2) mParentPlot->xAxis2 = newAxis; break; }
17736 case QCPAxis::atRight: { if (!mParentPlot->yAxis2) mParentPlot->yAxis2 = newAxis; break; }
17737 }
17738 }
17739
17740 return newAxis;
17741}
17742
17743/*!
17744 Adds a new axis with \ref addAxis to each axis rect side specified in \a types. This may be an
17745 <tt>or</tt>-combination of QCPAxis::AxisType, so axes can be added to multiple sides at once.
17746
17747 Returns a list of the added axes.
17748
17749 \see addAxis, setupFullAxesBox
17750*/
17752{
17753 QList<QCPAxis*> result;
17754 if (types.testFlag(QCPAxis::atLeft))
17755 result << addAxis(QCPAxis::atLeft);
17756 if (types.testFlag(QCPAxis::atRight))
17757 result << addAxis(QCPAxis::atRight);
17758 if (types.testFlag(QCPAxis::atTop))
17759 result << addAxis(QCPAxis::atTop);
17760 if (types.testFlag(QCPAxis::atBottom))
17761 result << addAxis(QCPAxis::atBottom);
17762 return result;
17763}
17764
17765/*!
17766 Removes the specified \a axis from the axis rect and deletes it.
17767
17768 Returns true on success, i.e. if \a axis was a valid axis in this axis rect.
17769
17770 \see addAxis
17771*/
17773{
17774 // don't access axis->axisType() to provide safety when axis is an invalid pointer, rather go through all axis containers:
17776 while (it.hasNext())
17777 {
17778 it.next();
17779 if (it.value().contains(axis))
17780 {
17781 if (it.value().first() == axis && it.value().size() > 1) // if removing first axis, transfer axis offset to the new first axis (which at this point is the second axis, if it exists)
17782 it.value()[1]->setOffset(axis->offset());
17783 mAxes[it.key()].removeOne(axis);
17784 if (qobject_cast<QCustomPlot*>(parentPlot())) // make sure this isn't called from QObject dtor when QCustomPlot is already destructed (happens when the axis rect is not in any layout and thus QObject-child of QCustomPlot)
17785 parentPlot()->axisRemoved(axis);
17786 delete axis;
17787 return true;
17788 }
17789 }
17790 qDebug() << Q_FUNC_INFO << "Axis isn't in axis rect:" << reinterpret_cast<quintptr>(axis);
17791 return false;
17792}
17793
17794/*!
17795 Zooms in (or out) to the passed rectangular region \a pixelRect, given in pixel coordinates.
17796
17797 All axes of this axis rect will have their range zoomed accordingly. If you only wish to zoom
17798 specific axes, use the overloaded version of this method.
17799
17800 \see QCustomPlot::setSelectionRectMode
17801*/
17802void QCPAxisRect::zoom(const QRectF &pixelRect)
17803{
17804 zoom(pixelRect, axes());
17805}
17806
17807/*! \overload
17808
17809 Zooms in (or out) to the passed rectangular region \a pixelRect, given in pixel coordinates.
17810
17811 Only the axes passed in \a affectedAxes will have their ranges zoomed accordingly.
17812
17813 \see QCustomPlot::setSelectionRectMode
17814*/
17815void QCPAxisRect::zoom(const QRectF &pixelRect, const QList<QCPAxis*> &affectedAxes)
17816{
17817 foreach (QCPAxis *axis, affectedAxes)
17818 {
17819 if (!axis)
17820 {
17821 qDebug() << Q_FUNC_INFO << "a passed axis was zero";
17822 continue;
17823 }
17824 QCPRange pixelRange;
17826 pixelRange = QCPRange(pixelRect.left(), pixelRect.right());
17827 else
17828 pixelRange = QCPRange(pixelRect.top(), pixelRect.bottom());
17829 axis->setRange(axis->pixelToCoord(pixelRange.lower), axis->pixelToCoord(pixelRange.upper));
17830 }
17831}
17832
17833/*!
17834 Convenience function to create an axis on each side that doesn't have any axes yet and set their
17835 visibility to true. Further, the top/right axes are assigned the following properties of the
17836 bottom/left axes:
17837
17838 \li range (\ref QCPAxis::setRange)
17839 \li range reversed (\ref QCPAxis::setRangeReversed)
17840 \li scale type (\ref QCPAxis::setScaleType)
17841 \li tick visibility (\ref QCPAxis::setTicks)
17842 \li number format (\ref QCPAxis::setNumberFormat)
17843 \li number precision (\ref QCPAxis::setNumberPrecision)
17844 \li tick count of ticker (\ref QCPAxisTicker::setTickCount)
17845 \li tick origin of ticker (\ref QCPAxisTicker::setTickOrigin)
17846
17847 Tick label visibility (\ref QCPAxis::setTickLabels) of the right and top axes are set to false.
17848
17849 If \a connectRanges is true, the \ref QCPAxis::rangeChanged "rangeChanged" signals of the bottom
17850 and left axes are connected to the \ref QCPAxis::setRange slots of the top and right axes.
17851*/
17852void QCPAxisRect::setupFullAxesBox(bool connectRanges)
17853{
17854 QCPAxis *xAxis, *yAxis, *xAxis2, *yAxis2;
17855 if (axisCount(QCPAxis::atBottom) == 0)
17856 xAxis = addAxis(QCPAxis::atBottom);
17857 else
17858 xAxis = axis(QCPAxis::atBottom);
17859
17860 if (axisCount(QCPAxis::atLeft) == 0)
17861 yAxis = addAxis(QCPAxis::atLeft);
17862 else
17863 yAxis = axis(QCPAxis::atLeft);
17864
17865 if (axisCount(QCPAxis::atTop) == 0)
17866 xAxis2 = addAxis(QCPAxis::atTop);
17867 else
17868 xAxis2 = axis(QCPAxis::atTop);
17869
17870 if (axisCount(QCPAxis::atRight) == 0)
17871 yAxis2 = addAxis(QCPAxis::atRight);
17872 else
17873 yAxis2 = axis(QCPAxis::atRight);
17874
17875 xAxis->setVisible(true);
17876 yAxis->setVisible(true);
17877 xAxis2->setVisible(true);
17878 yAxis2->setVisible(true);
17879 xAxis2->setTickLabels(false);
17880 yAxis2->setTickLabels(false);
17881
17882 xAxis2->setRange(xAxis->range());
17883 xAxis2->setRangeReversed(xAxis->rangeReversed());
17884 xAxis2->setScaleType(xAxis->scaleType());
17885 xAxis2->setTicks(xAxis->ticks());
17886 xAxis2->setNumberFormat(xAxis->numberFormat());
17887 xAxis2->setNumberPrecision(xAxis->numberPrecision());
17888 xAxis2->ticker()->setTickCount(xAxis->ticker()->tickCount());
17889 xAxis2->ticker()->setTickOrigin(xAxis->ticker()->tickOrigin());
17890
17891 yAxis2->setRange(yAxis->range());
17892 yAxis2->setRangeReversed(yAxis->rangeReversed());
17893 yAxis2->setScaleType(yAxis->scaleType());
17894 yAxis2->setTicks(yAxis->ticks());
17895 yAxis2->setNumberFormat(yAxis->numberFormat());
17896 yAxis2->setNumberPrecision(yAxis->numberPrecision());
17897 yAxis2->ticker()->setTickCount(yAxis->ticker()->tickCount());
17898 yAxis2->ticker()->setTickOrigin(yAxis->ticker()->tickOrigin());
17899
17900 if (connectRanges)
17901 {
17902 connect(xAxis, SIGNAL(rangeChanged(QCPRange)), xAxis2, SLOT(setRange(QCPRange)));
17903 connect(yAxis, SIGNAL(rangeChanged(QCPRange)), yAxis2, SLOT(setRange(QCPRange)));
17904 }
17905}
17906
17907/*!
17908 Returns a list of all the plottables that are associated with this axis rect.
17909
17910 A plottable is considered associated with an axis rect if its key or value axis (or both) is in
17911 this axis rect.
17912
17913 \see graphs, items
17914*/
17916{
17917 // Note: don't append all QCPAxis::plottables() into a list, because we might get duplicate entries
17919 foreach (QCPAbstractPlottable *plottable, mParentPlot->mPlottables)
17920 {
17921 if (plottable->keyAxis()->axisRect() == this || plottable->valueAxis()->axisRect() == this)
17922 result.append(plottable);
17923 }
17924 return result;
17925}
17926
17927/*!
17928 Returns a list of all the graphs that are associated with this axis rect.
17929
17930 A graph is considered associated with an axis rect if its key or value axis (or both) is in
17931 this axis rect.
17932
17933 \see plottables, items
17934*/
17936{
17937 // Note: don't append all QCPAxis::graphs() into a list, because we might get duplicate entries
17938 QList<QCPGraph*> result;
17939 foreach (QCPGraph *graph, mParentPlot->mGraphs)
17940 {
17941 if (graph->keyAxis()->axisRect() == this || graph->valueAxis()->axisRect() == this)
17942 result.append(graph);
17943 }
17944 return result;
17945}
17946
17947/*!
17948 Returns a list of all the items that are associated with this axis rect.
17949
17950 An item is considered associated with an axis rect if any of its positions has key or value axis
17951 set to an axis that is in this axis rect, or if any of its positions has \ref
17952 QCPItemPosition::setAxisRect set to the axis rect, or if the clip axis rect (\ref
17953 QCPAbstractItem::setClipAxisRect) is set to this axis rect.
17954
17955 \see plottables, graphs
17956*/
17958{
17959 // Note: don't just append all QCPAxis::items() into a list, because we might get duplicate entries
17960 // and miss those items that have this axis rect as clipAxisRect.
17962 foreach (QCPAbstractItem *item, mParentPlot->mItems)
17963 {
17964 if (item->clipAxisRect() == this)
17965 {
17966 result.append(item);
17967 continue;
17968 }
17969 foreach (QCPItemPosition *position, item->positions())
17970 {
17971 if (position->axisRect() == this ||
17972 position->keyAxis()->axisRect() == this ||
17973 position->valueAxis()->axisRect() == this)
17974 {
17975 result.append(item);
17976 break;
17977 }
17978 }
17979 }
17980 return result;
17981}
17982
17983/*!
17984 This method is called automatically upon replot and doesn't need to be called by users of
17985 QCPAxisRect.
17986
17987 Calls the base class implementation to update the margins (see \ref QCPLayoutElement::update),
17988 and finally passes the \ref rect to the inset layout (\ref insetLayout) and calls its
17989 QCPInsetLayout::update function.
17990
17991 \seebaseclassmethod
17992*/
17994{
17996
17997 switch (phase)
17998 {
17999 case upPreparation:
18000 {
18001 foreach (QCPAxis *axis, axes())
18003 break;
18004 }
18005 case upLayout:
18006 {
18007 mInsetLayout->setOuterRect(rect());
18008 break;
18009 }
18010 default: break;
18011 }
18012
18013 // pass update call on to inset layout (doesn't happen automatically, because QCPAxisRect doesn't derive from QCPLayout):
18014 mInsetLayout->update(phase);
18015}
18016
18017/* inherits documentation from base class */
18019{
18021 if (mInsetLayout)
18022 {
18023 result << mInsetLayout;
18024 if (recursive)
18025 result << mInsetLayout->elements(recursive);
18026 }
18027 return result;
18028}
18029
18030/* inherits documentation from base class */
18032{
18033 painter->setAntialiasing(false);
18034}
18035
18036/* inherits documentation from base class */
18038{
18039 drawBackground(painter);
18040}
18041
18042/*!
18043 Sets \a pm as the axis background pixmap. The axis background pixmap will be drawn inside the
18044 axis rect. Since axis rects place themselves on the "background" layer by default, the axis rect
18045 backgrounds are usually drawn below everything else.
18046
18047 For cases where the provided pixmap doesn't have the same size as the axis rect, scaling can be
18048 enabled with \ref setBackgroundScaled and the scaling mode (i.e. whether and how the aspect ratio
18049 is preserved) can be set with \ref setBackgroundScaledMode. To set all these options in one call,
18050 consider using the overloaded version of this function.
18051
18052 Below the pixmap, the axis rect may be optionally filled with a brush, if specified with \ref
18053 setBackground(const QBrush &brush).
18054
18055 \see setBackgroundScaled, setBackgroundScaledMode, setBackground(const QBrush &brush)
18056*/
18058{
18059 mBackgroundPixmap = pm;
18060 mScaledBackgroundPixmap = QPixmap();
18061}
18062
18063/*! \overload
18064
18065 Sets \a brush as the background brush. The axis rect background will be filled with this brush.
18066 Since axis rects place themselves on the "background" layer by default, the axis rect backgrounds
18067 are usually drawn below everything else.
18068
18069 The brush will be drawn before (under) any background pixmap, which may be specified with \ref
18070 setBackground(const QPixmap &pm).
18071
18072 To disable drawing of a background brush, set \a brush to Qt::NoBrush.
18073
18074 \see setBackground(const QPixmap &pm)
18075*/
18077{
18078 mBackgroundBrush = brush;
18079}
18080
18081/*! \overload
18082
18083 Allows setting the background pixmap of the axis rect, whether it shall be scaled and how it
18084 shall be scaled in one call.
18085
18086 \see setBackground(const QPixmap &pm), setBackgroundScaled, setBackgroundScaledMode
18087*/
18089{
18090 mBackgroundPixmap = pm;
18091 mScaledBackgroundPixmap = QPixmap();
18092 mBackgroundScaled = scaled;
18093 mBackgroundScaledMode = mode;
18094}
18095
18096/*!
18097 Sets whether the axis background pixmap shall be scaled to fit the axis rect or not. If \a scaled
18098 is set to true, you may control whether and how the aspect ratio of the original pixmap is
18099 preserved with \ref setBackgroundScaledMode.
18100
18101 Note that the scaled version of the original pixmap is buffered, so there is no performance
18102 penalty on replots. (Except when the axis rect dimensions are changed continuously.)
18103
18104 \see setBackground, setBackgroundScaledMode
18105*/
18107{
18108 mBackgroundScaled = scaled;
18109}
18110
18111/*!
18112 If scaling of the axis background pixmap is enabled (\ref setBackgroundScaled), use this function to
18113 define whether and how the aspect ratio of the original pixmap passed to \ref setBackground is preserved.
18114 \see setBackground, setBackgroundScaled
18115*/
18117{
18118 mBackgroundScaledMode = mode;
18119}
18120
18121/*!
18122 Returns the range drag axis of the \a orientation provided. If multiple axes were set, returns
18123 the first one (use \ref rangeDragAxes to retrieve a list with all set axes).
18124
18125 \see setRangeDragAxes
18126*/
18128{
18129 if (orientation == Qt::Horizontal)
18130 return mRangeDragHorzAxis.isEmpty() ? nullptr : mRangeDragHorzAxis.first().data();
18131 else
18132 return mRangeDragVertAxis.isEmpty() ? nullptr : mRangeDragVertAxis.first().data();
18133}
18134
18135/*!
18136 Returns the range zoom axis of the \a orientation provided. If multiple axes were set, returns
18137 the first one (use \ref rangeZoomAxes to retrieve a list with all set axes).
18138
18139 \see setRangeZoomAxes
18140*/
18142{
18143 if (orientation == Qt::Horizontal)
18144 return mRangeZoomHorzAxis.isEmpty() ? nullptr : mRangeZoomHorzAxis.first().data();
18145 else
18146 return mRangeZoomVertAxis.isEmpty() ? nullptr : mRangeZoomVertAxis.first().data();
18147}
18148
18149/*!
18150 Returns all range drag axes of the \a orientation provided.
18151
18152 \see rangeZoomAxis, setRangeZoomAxes
18153*/
18155{
18156 QList<QCPAxis*> result;
18157 if (orientation == Qt::Horizontal)
18158 {
18159 foreach (QPointer<QCPAxis> axis, mRangeDragHorzAxis)
18160 {
18161 if (!axis.isNull())
18162 result.append(axis.data());
18163 }
18164 } else
18165 {
18166 foreach (QPointer<QCPAxis> axis, mRangeDragVertAxis)
18167 {
18168 if (!axis.isNull())
18169 result.append(axis.data());
18170 }
18171 }
18172 return result;
18173}
18174
18175/*!
18176 Returns all range zoom axes of the \a orientation provided.
18177
18178 \see rangeDragAxis, setRangeDragAxes
18179*/
18181{
18182 QList<QCPAxis*> result;
18183 if (orientation == Qt::Horizontal)
18184 {
18185 foreach (QPointer<QCPAxis> axis, mRangeZoomHorzAxis)
18186 {
18187 if (!axis.isNull())
18188 result.append(axis.data());
18189 }
18190 } else
18191 {
18192 foreach (QPointer<QCPAxis> axis, mRangeZoomVertAxis)
18193 {
18194 if (!axis.isNull())
18195 result.append(axis.data());
18196 }
18197 }
18198 return result;
18199}
18200
18201/*!
18202 Returns the range zoom factor of the \a orientation provided.
18203
18204 \see setRangeZoomFactor
18205*/
18207{
18208 return (orientation == Qt::Horizontal ? mRangeZoomFactorHorz : mRangeZoomFactorVert);
18209}
18210
18211/*!
18212 Sets which axis orientation may be range dragged by the user with mouse interaction.
18213 What orientation corresponds to which specific axis can be set with
18214 \ref setRangeDragAxes(QCPAxis *horizontal, QCPAxis *vertical). By
18215 default, the horizontal axis is the bottom axis (xAxis) and the vertical axis
18216 is the left axis (yAxis).
18217
18218 To disable range dragging entirely, pass \c nullptr as \a orientations or remove \ref
18219 QCP::iRangeDrag from \ref QCustomPlot::setInteractions. To enable range dragging for both
18220 directions, pass <tt>Qt::Horizontal | Qt::Vertical</tt> as \a orientations.
18221
18222 In addition to setting \a orientations to a non-zero value, make sure \ref QCustomPlot::setInteractions
18223 contains \ref QCP::iRangeDrag to enable the range dragging interaction.
18224
18225 \see setRangeZoom, setRangeDragAxes, QCustomPlot::setNoAntialiasingOnDrag
18226*/
18228{
18229 mRangeDrag = orientations;
18230}
18231
18232/*!
18233 Sets which axis orientation may be zoomed by the user with the mouse wheel. What orientation
18234 corresponds to which specific axis can be set with \ref setRangeZoomAxes(QCPAxis *horizontal,
18235 QCPAxis *vertical). By default, the horizontal axis is the bottom axis (xAxis) and the vertical
18236 axis is the left axis (yAxis).
18237
18238 To disable range zooming entirely, pass \c nullptr as \a orientations or remove \ref
18239 QCP::iRangeZoom from \ref QCustomPlot::setInteractions. To enable range zooming for both
18240 directions, pass <tt>Qt::Horizontal | Qt::Vertical</tt> as \a orientations.
18241
18242 In addition to setting \a orientations to a non-zero value, make sure \ref QCustomPlot::setInteractions
18243 contains \ref QCP::iRangeZoom to enable the range zooming interaction.
18244
18245 \see setRangeZoomFactor, setRangeZoomAxes, setRangeDrag
18246*/
18248{
18249 mRangeZoom = orientations;
18250}
18251
18252/*! \overload
18253
18254 Sets the axes whose range will be dragged when \ref setRangeDrag enables mouse range dragging on
18255 the QCustomPlot widget. Pass \c nullptr if no axis shall be dragged in the respective
18256 orientation.
18257
18258 Use the overload taking a list of axes, if multiple axes (more than one per orientation) shall
18259 react to dragging interactions.
18260
18261 \see setRangeZoomAxes
18262*/
18264{
18265 QList<QCPAxis*> horz, vert;
18266 if (horizontal)
18267 horz.append(horizontal);
18268 if (vertical)
18269 vert.append(vertical);
18270 setRangeDragAxes(horz, vert);
18271}
18272
18273/*! \overload
18274
18275 This method allows to set up multiple axes to react to horizontal and vertical dragging. The drag
18276 orientation that the respective axis will react to is deduced from its orientation (\ref
18277 QCPAxis::orientation).
18278
18279 In the unusual case that you wish to e.g. drag a vertically oriented axis with a horizontal drag
18280 motion, use the overload taking two separate lists for horizontal and vertical dragging.
18281*/
18283{
18284 QList<QCPAxis*> horz, vert;
18285 foreach (QCPAxis *ax, axes)
18286 {
18287 if (ax->orientation() == Qt::Horizontal)
18288 horz.append(ax);
18289 else
18290 vert.append(ax);
18291 }
18292 setRangeDragAxes(horz, vert);
18293}
18294
18295/*! \overload
18296
18297 This method allows to set multiple axes up to react to horizontal and vertical dragging, and
18298 define specifically which axis reacts to which drag orientation (irrespective of the axis
18299 orientation).
18300*/
18302{
18303 mRangeDragHorzAxis.clear();
18304 foreach (QCPAxis *ax, horizontal)
18305 {
18306 QPointer<QCPAxis> axPointer(ax);
18307 if (!axPointer.isNull())
18308 mRangeDragHorzAxis.append(axPointer);
18309 else
18310 qDebug() << Q_FUNC_INFO << "invalid axis passed in horizontal list:" << reinterpret_cast<quintptr>(ax);
18311 }
18312 mRangeDragVertAxis.clear();
18313 foreach (QCPAxis *ax, vertical)
18314 {
18315 QPointer<QCPAxis> axPointer(ax);
18316 if (!axPointer.isNull())
18317 mRangeDragVertAxis.append(axPointer);
18318 else
18319 qDebug() << Q_FUNC_INFO << "invalid axis passed in vertical list:" << reinterpret_cast<quintptr>(ax);
18320 }
18321}
18322
18323/*!
18324 Sets the axes whose range will be zoomed when \ref setRangeZoom enables mouse wheel zooming on
18325 the QCustomPlot widget. Pass \c nullptr if no axis shall be zoomed in the respective orientation.
18326
18327 The two axes can be zoomed with different strengths, when different factors are passed to \ref
18328 setRangeZoomFactor(double horizontalFactor, double verticalFactor).
18329
18330 Use the overload taking a list of axes, if multiple axes (more than one per orientation) shall
18331 react to zooming interactions.
18332
18333 \see setRangeDragAxes
18334*/
18336{
18337 QList<QCPAxis*> horz, vert;
18338 if (horizontal)
18339 horz.append(horizontal);
18340 if (vertical)
18341 vert.append(vertical);
18342 setRangeZoomAxes(horz, vert);
18343}
18344
18345/*! \overload
18346
18347 This method allows to set up multiple axes to react to horizontal and vertical range zooming. The
18348 zoom orientation that the respective axis will react to is deduced from its orientation (\ref
18349 QCPAxis::orientation).
18350
18351 In the unusual case that you wish to e.g. zoom a vertically oriented axis with a horizontal zoom
18352 interaction, use the overload taking two separate lists for horizontal and vertical zooming.
18353*/
18355{
18356 QList<QCPAxis*> horz, vert;
18357 foreach (QCPAxis *ax, axes)
18358 {
18359 if (ax->orientation() == Qt::Horizontal)
18360 horz.append(ax);
18361 else
18362 vert.append(ax);
18363 }
18364 setRangeZoomAxes(horz, vert);
18365}
18366
18367/*! \overload
18368
18369 This method allows to set multiple axes up to react to horizontal and vertical zooming, and
18370 define specifically which axis reacts to which zoom orientation (irrespective of the axis
18371 orientation).
18372*/
18374{
18375 mRangeZoomHorzAxis.clear();
18376 foreach (QCPAxis *ax, horizontal)
18377 {
18378 QPointer<QCPAxis> axPointer(ax);
18379 if (!axPointer.isNull())
18380 mRangeZoomHorzAxis.append(axPointer);
18381 else
18382 qDebug() << Q_FUNC_INFO << "invalid axis passed in horizontal list:" << reinterpret_cast<quintptr>(ax);
18383 }
18384 mRangeZoomVertAxis.clear();
18385 foreach (QCPAxis *ax, vertical)
18386 {
18387 QPointer<QCPAxis> axPointer(ax);
18388 if (!axPointer.isNull())
18389 mRangeZoomVertAxis.append(axPointer);
18390 else
18391 qDebug() << Q_FUNC_INFO << "invalid axis passed in vertical list:" << reinterpret_cast<quintptr>(ax);
18392 }
18393}
18394
18395/*!
18396 Sets how strong one rotation step of the mouse wheel zooms, when range zoom was activated with
18397 \ref setRangeZoom. The two parameters \a horizontalFactor and \a verticalFactor provide a way to
18398 let the horizontal axis zoom at different rates than the vertical axis. Which axis is horizontal
18399 and which is vertical, can be set with \ref setRangeZoomAxes.
18400
18401 When the zoom factor is greater than one, scrolling the mouse wheel backwards (towards the user)
18402 will zoom in (make the currently visible range smaller). For zoom factors smaller than one, the
18403 same scrolling direction will zoom out.
18404*/
18405void QCPAxisRect::setRangeZoomFactor(double horizontalFactor, double verticalFactor)
18406{
18407 mRangeZoomFactorHorz = horizontalFactor;
18408 mRangeZoomFactorVert = verticalFactor;
18409}
18410
18411/*! \overload
18412
18413 Sets both the horizontal and vertical zoom \a factor.
18414*/
18416{
18417 mRangeZoomFactorHorz = factor;
18418 mRangeZoomFactorVert = factor;
18419}
18420
18421/*! \internal
18422
18423 Draws the background of this axis rect. It may consist of a background fill (a QBrush) and a
18424 pixmap.
18425
18426 If a brush was given via \ref setBackground(const QBrush &brush), this function first draws an
18427 according filling inside the axis rect with the provided \a painter.
18428
18429 Then, if a pixmap was provided via \ref setBackground, this function buffers the scaled version
18430 depending on \ref setBackgroundScaled and \ref setBackgroundScaledMode and then draws it inside
18431 the axis rect with the provided \a painter. The scaled version is buffered in
18432 mScaledBackgroundPixmap to prevent expensive rescaling at every redraw. It is only updated, when
18433 the axis rect has changed in a way that requires a rescale of the background pixmap (this is
18434 dependent on the \ref setBackgroundScaledMode), or when a differend axis background pixmap was
18435 set.
18436
18437 \see setBackground, setBackgroundScaled, setBackgroundScaledMode
18438*/
18440{
18441 // draw background fill:
18442 if (mBackgroundBrush != Qt::NoBrush)
18443 painter->fillRect(mRect, mBackgroundBrush);
18444
18445 // draw background pixmap (on top of fill, if brush specified):
18446 if (!mBackgroundPixmap.isNull())
18447 {
18448 if (mBackgroundScaled)
18449 {
18450 // check whether mScaledBackground needs to be updated:
18451 QSize scaledSize(mBackgroundPixmap.size());
18452 scaledSize.scale(mRect.size(), mBackgroundScaledMode);
18453 if (mScaledBackgroundPixmap.size() != scaledSize)
18454 mScaledBackgroundPixmap = mBackgroundPixmap.scaled(mRect.size(), mBackgroundScaledMode, Qt::SmoothTransformation);
18455 painter->drawPixmap(mRect.topLeft()+QPoint(0, -1), mScaledBackgroundPixmap, QRect(0, 0, mRect.width(), mRect.height()) & mScaledBackgroundPixmap.rect());
18456 } else
18457 {
18458 painter->drawPixmap(mRect.topLeft()+QPoint(0, -1), mBackgroundPixmap, QRect(0, 0, mRect.width(), mRect.height()));
18459 }
18460 }
18461}
18462
18463/*! \internal
18464
18465 This function makes sure multiple axes on the side specified with \a type don't collide, but are
18466 distributed according to their respective space requirement (QCPAxis::calculateMargin).
18467
18468 It does this by setting an appropriate offset (\ref QCPAxis::setOffset) on all axes except the
18469 one with index zero.
18470
18471 This function is called by \ref calculateAutoMargin.
18472*/
18474{
18475 const QList<QCPAxis*> axesList = mAxes.value(type);
18476 if (axesList.isEmpty())
18477 return;
18478
18479 bool isFirstVisible = !axesList.first()->visible(); // if the first axis is visible, the second axis (which is where the loop starts) isn't the first visible axis, so initialize with false
18480 for (int i=1; i<axesList.size(); ++i)
18481 {
18482 int offset = axesList.at(i-1)->offset() + axesList.at(i-1)->calculateMargin();
18483 if (axesList.at(i)->visible()) // only add inner tick length to offset if this axis is visible and it's not the first visible one (might happen if true first axis is invisible)
18484 {
18485 if (!isFirstVisible)
18486 offset += axesList.at(i)->tickLengthIn();
18487 isFirstVisible = false;
18488 }
18489 axesList.at(i)->setOffset(offset);
18490 }
18491}
18492
18493/* inherits documentation from base class */
18495{
18496 if (!mAutoMargins.testFlag(side))
18497 qDebug() << Q_FUNC_INFO << "Called with side that isn't specified as auto margin";
18498
18500
18501 // note: only need to look at the last (outer most) axis to determine the total margin, due to updateAxisOffset call
18502 const QList<QCPAxis*> axesList = mAxes.value(QCPAxis::marginSideToAxisType(side));
18503 if (!axesList.isEmpty())
18504 return axesList.last()->offset() + axesList.last()->calculateMargin();
18505 else
18506 return 0;
18507}
18508
18509/*! \internal
18510
18511 Reacts to a change in layout to potentially set the convenience axis pointers \ref
18512 QCustomPlot::xAxis, \ref QCustomPlot::yAxis, etc. of the parent QCustomPlot to the respective
18513 axes of this axis rect. This is only done if the respective convenience pointer is currently zero
18514 and if there is no QCPAxisRect at position (0, 0) of the plot layout.
18515
18516 This automation makes it simpler to replace the main axis rect with a newly created one, without
18517 the need to manually reset the convenience pointers.
18518*/
18520{
18521 if (mParentPlot && mParentPlot->axisRectCount() > 0 && mParentPlot->axisRect(0) == this)
18522 {
18523 if (axisCount(QCPAxis::atBottom) > 0 && !mParentPlot->xAxis)
18524 mParentPlot->xAxis = axis(QCPAxis::atBottom);
18525 if (axisCount(QCPAxis::atLeft) > 0 && !mParentPlot->yAxis)
18526 mParentPlot->yAxis = axis(QCPAxis::atLeft);
18527 if (axisCount(QCPAxis::atTop) > 0 && !mParentPlot->xAxis2)
18528 mParentPlot->xAxis2 = axis(QCPAxis::atTop);
18529 if (axisCount(QCPAxis::atRight) > 0 && !mParentPlot->yAxis2)
18530 mParentPlot->yAxis2 = axis(QCPAxis::atRight);
18531 }
18532}
18533
18534/*! \internal
18535
18536 Event handler for when a mouse button is pressed on the axis rect. If the left mouse button is
18537 pressed, the range dragging interaction is initialized (the actual range manipulation happens in
18538 the \ref mouseMoveEvent).
18539
18540 The mDragging flag is set to true and some anchor points are set that are needed to determine the
18541 distance the mouse was dragged in the mouse move/release events later.
18542
18543 \see mouseMoveEvent, mouseReleaseEvent
18544*/
18546{
18547 Q_UNUSED(details)
18548 if (event->buttons() & Qt::LeftButton)
18549 {
18550 mDragging = true;
18551 // initialize antialiasing backup in case we start dragging:
18552 if (mParentPlot->noAntialiasingOnDrag())
18553 {
18554 mAADragBackup = mParentPlot->antialiasedElements();
18555 mNotAADragBackup = mParentPlot->notAntialiasedElements();
18556 }
18557 // Mouse range dragging interaction:
18558 if (mParentPlot->interactions().testFlag(QCP::iRangeDrag))
18559 {
18560 mDragStartHorzRange.clear();
18561 foreach (QPointer<QCPAxis> axis, mRangeDragHorzAxis)
18562 mDragStartHorzRange.append(axis.isNull() ? QCPRange() : axis->range());
18563 mDragStartVertRange.clear();
18564 foreach (QPointer<QCPAxis> axis, mRangeDragVertAxis)
18565 mDragStartVertRange.append(axis.isNull() ? QCPRange() : axis->range());
18566 }
18567 }
18568}
18569
18570/*! \internal
18571
18572 Event handler for when the mouse is moved on the axis rect. If range dragging was activated in a
18573 preceding \ref mousePressEvent, the range is moved accordingly.
18574
18575 \see mousePressEvent, mouseReleaseEvent
18576*/
18578{
18579 Q_UNUSED(startPos)
18580 // Mouse range dragging interaction:
18581 if (mDragging && mParentPlot->interactions().testFlag(QCP::iRangeDrag))
18582 {
18583
18584 if (mRangeDrag.testFlag(Qt::Horizontal))
18585 {
18586 for (int i=0; i<mRangeDragHorzAxis.size(); ++i)
18587 {
18588 QCPAxis *ax = mRangeDragHorzAxis.at(i).data();
18589 if (!ax)
18590 continue;
18591 if (i >= mDragStartHorzRange.size())
18592 break;
18593 if (ax->mScaleType == QCPAxis::stLinear)
18594 {
18595 double diff = ax->pixelToCoord(startPos.x()) - ax->pixelToCoord(event->pos().x());
18596 ax->setRange(mDragStartHorzRange.at(i).lower+diff, mDragStartHorzRange.at(i).upper+diff);
18597 } else if (ax->mScaleType == QCPAxis::stLogarithmic)
18598 {
18599 double diff = ax->pixelToCoord(startPos.x()) / ax->pixelToCoord(event->pos().x());
18600 ax->setRange(mDragStartHorzRange.at(i).lower*diff, mDragStartHorzRange.at(i).upper*diff);
18601 }
18602 }
18603 }
18604
18605 if (mRangeDrag.testFlag(Qt::Vertical))
18606 {
18607 for (int i=0; i<mRangeDragVertAxis.size(); ++i)
18608 {
18609 QCPAxis *ax = mRangeDragVertAxis.at(i).data();
18610 if (!ax)
18611 continue;
18612 if (i >= mDragStartVertRange.size())
18613 break;
18614 if (ax->mScaleType == QCPAxis::stLinear)
18615 {
18616 double diff = ax->pixelToCoord(startPos.y()) - ax->pixelToCoord(event->pos().y());
18617 ax->setRange(mDragStartVertRange.at(i).lower+diff, mDragStartVertRange.at(i).upper+diff);
18618 } else if (ax->mScaleType == QCPAxis::stLogarithmic)
18619 {
18620 double diff = ax->pixelToCoord(startPos.y()) / ax->pixelToCoord(event->pos().y());
18621 ax->setRange(mDragStartVertRange.at(i).lower*diff, mDragStartVertRange.at(i).upper*diff);
18622 }
18623 }
18624 }
18625
18626 if (mRangeDrag != 0) // if either vertical or horizontal drag was enabled, do a replot
18627 {
18628 if (mParentPlot->noAntialiasingOnDrag())
18630 mParentPlot->replot(QCustomPlot::rpQueuedReplot);
18631 }
18632
18633 }
18634}
18635
18636/* inherits documentation from base class */
18638{
18639 Q_UNUSED(event)
18640 Q_UNUSED(startPos)
18641 mDragging = false;
18642 if (mParentPlot->noAntialiasingOnDrag())
18643 {
18644 mParentPlot->setAntialiasedElements(mAADragBackup);
18645 mParentPlot->setNotAntialiasedElements(mNotAADragBackup);
18646 }
18647}
18648
18649/*! \internal
18650
18651 Event handler for mouse wheel events. If rangeZoom is Qt::Horizontal, Qt::Vertical or both, the
18652 ranges of the axes defined as rangeZoomHorzAxis and rangeZoomVertAxis are scaled. The center of
18653 the scaling operation is the current cursor position inside the axis rect. The scaling factor is
18654 dependent on the mouse wheel delta (which direction the wheel was rotated) to provide a natural
18655 zooming feel. The Strength of the zoom can be controlled via \ref setRangeZoomFactor.
18656
18657 Note, that event->angleDelta() is usually +/-120 for single rotation steps. However, if the mouse
18658 wheel is turned rapidly, many steps may bunch up to one event, so the delta may then be multiples
18659 of 120. This is taken into account here, by calculating \a wheelSteps and using it as exponent of
18660 the range zoom factor. This takes care of the wheel direction automatically, by inverting the
18661 factor, when the wheel step is negative (f^-1 = 1/f).
18662*/
18664{
18665#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
18666 const double delta = event->delta();
18667#else
18668 const double delta = event->angleDelta().y();
18669#endif
18670
18671#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
18672 const QPointF pos = event->pos();
18673#else
18674 const QPointF pos = event->position();
18675#endif
18676
18677 // Mouse range zooming interaction:
18678 if (mParentPlot->interactions().testFlag(QCP::iRangeZoom))
18679 {
18680 if (mRangeZoom != 0)
18681 {
18682 double factor;
18683 double wheelSteps = delta/120.0; // a single step delta is +/-120 usually
18684 if (mRangeZoom.testFlag(Qt::Horizontal))
18685 {
18686 factor = qPow(mRangeZoomFactorHorz, wheelSteps);
18687 foreach (QPointer<QCPAxis> axis, mRangeZoomHorzAxis)
18688 {
18689 if (!axis.isNull())
18690 axis->scaleRange(factor, axis->pixelToCoord(pos.x()));
18691 }
18692 }
18693 if (mRangeZoom.testFlag(Qt::Vertical))
18694 {
18695 factor = qPow(mRangeZoomFactorVert, wheelSteps);
18696 foreach (QPointer<QCPAxis> axis, mRangeZoomVertAxis)
18697 {
18698 if (!axis.isNull())
18699 axis->scaleRange(factor, axis->pixelToCoord(pos.y()));
18700 }
18701 }
18702 mParentPlot->replot();
18703 }
18704 }
18705}
18706/* end of 'src/layoutelements/layoutelement-axisrect.cpp' */
18707
18708
18709/* including file 'src/layoutelements/layoutelement-legend.cpp' */
18710/* modified 2022-11-06T12:45:56, size 31762 */
18711
18712////////////////////////////////////////////////////////////////////////////////////////////////////
18713//////////////////// QCPAbstractLegendItem
18714////////////////////////////////////////////////////////////////////////////////////////////////////
18715
18716/*! \class QCPAbstractLegendItem
18717 \brief The abstract base class for all entries in a QCPLegend.
18718
18719 It defines a very basic interface for entries in a QCPLegend. For representing plottables in the
18720 legend, the subclass \ref QCPPlottableLegendItem is more suitable.
18721
18722 Only derive directly from this class when you need absolute freedom (e.g. a custom legend entry
18723 that's not even associated with a plottable).
18724
18725 You must implement the following pure virtual functions:
18726 \li \ref draw (from QCPLayerable)
18727
18728 You inherit the following members you may use:
18729 <table>
18730 <tr>
18731 <td>QCPLegend *\b mParentLegend</td>
18732 <td>A pointer to the parent QCPLegend.</td>
18733 </tr><tr>
18734 <td>QFont \b mFont</td>
18735 <td>The generic font of the item. You should use this font for all or at least the most prominent text of the item.</td>
18736 </tr>
18737 </table>
18738*/
18739
18740/* start of documentation of signals */
18741
18742/*! \fn void QCPAbstractLegendItem::selectionChanged(bool selected)
18743
18744 This signal is emitted when the selection state of this legend item has changed, either by user
18745 interaction or by a direct call to \ref setSelected.
18746*/
18747
18748/* end of documentation of signals */
18749
18750/*!
18751 Constructs a QCPAbstractLegendItem and associates it with the QCPLegend \a parent. This does not
18752 cause the item to be added to \a parent, so \ref QCPLegend::addItem must be called separately.
18753*/
18755 QCPLayoutElement(parent->parentPlot()),
18756 mParentLegend(parent),
18757 mFont(parent->font()),
18758 mTextColor(parent->textColor()),
18759 mSelectedFont(parent->selectedFont()),
18760 mSelectedTextColor(parent->selectedTextColor()),
18761 mSelectable(true),
18762 mSelected(false)
18763{
18764 setLayer(QLatin1String("legend"));
18765 setMargins(QMargins(0, 0, 0, 0));
18766}
18767
18768/*!
18769 Sets the default font of this specific legend item to \a font.
18770
18771 \see setTextColor, QCPLegend::setFont
18772*/
18774{
18775 mFont = font;
18776}
18777
18778/*!
18779 Sets the default text color of this specific legend item to \a color.
18780
18781 \see setFont, QCPLegend::setTextColor
18782*/
18784{
18785 mTextColor = color;
18786}
18787
18788/*!
18789 When this legend item is selected, \a font is used to draw generic text, instead of the normal
18790 font set with \ref setFont.
18791
18792 \see setFont, QCPLegend::setSelectedFont
18793*/
18795{
18796 mSelectedFont = font;
18797}
18798
18799/*!
18800 When this legend item is selected, \a color is used to draw generic text, instead of the normal
18801 color set with \ref setTextColor.
18802
18803 \see setTextColor, QCPLegend::setSelectedTextColor
18804*/
18806{
18807 mSelectedTextColor = color;
18808}
18809
18810/*!
18811 Sets whether this specific legend item is selectable.
18812
18813 \see setSelectedParts, QCustomPlot::setInteractions
18814*/
18816{
18817 if (mSelectable != selectable)
18818 {
18819 mSelectable = selectable;
18820 emit selectableChanged(mSelectable);
18821 }
18822}
18823
18824/*!
18825 Sets whether this specific legend item is selected.
18826
18827 It is possible to set the selection state of this item by calling this function directly, even if
18828 setSelectable is set to false.
18829
18830 \see setSelectableParts, QCustomPlot::setInteractions
18831*/
18833{
18834 if (mSelected != selected)
18835 {
18836 mSelected = selected;
18837 emit selectionChanged(mSelected);
18838 }
18839}
18840
18841/* inherits documentation from base class */
18842double QCPAbstractLegendItem::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
18843{
18844 Q_UNUSED(details)
18845 if (!mParentPlot) return -1;
18846 if (onlySelectable && (!mSelectable || !mParentLegend->selectableParts().testFlag(QCPLegend::spItems)))
18847 return -1;
18848
18849 if (mRect.contains(pos.toPoint()))
18850 return mParentPlot->selectionTolerance()*0.99;
18851 else
18852 return -1;
18853}
18854
18855/* inherits documentation from base class */
18857{
18858 applyAntialiasingHint(painter, mAntialiased, QCP::aeLegendItems);
18859}
18860
18861/* inherits documentation from base class */
18863{
18864 return mOuterRect;
18865}
18866
18867/* inherits documentation from base class */
18868void QCPAbstractLegendItem::selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged)
18869{
18870 Q_UNUSED(event)
18871 Q_UNUSED(details)
18872 if (mSelectable && mParentLegend->selectableParts().testFlag(QCPLegend::spItems))
18873 {
18874 bool selBefore = mSelected;
18875 setSelected(additive ? !mSelected : true);
18876 if (selectionStateChanged)
18877 *selectionStateChanged = mSelected != selBefore;
18878 }
18879}
18880
18881/* inherits documentation from base class */
18882void QCPAbstractLegendItem::deselectEvent(bool *selectionStateChanged)
18883{
18884 if (mSelectable && mParentLegend->selectableParts().testFlag(QCPLegend::spItems))
18885 {
18886 bool selBefore = mSelected;
18887 setSelected(false);
18888 if (selectionStateChanged)
18889 *selectionStateChanged = mSelected != selBefore;
18890 }
18891}
18892
18893////////////////////////////////////////////////////////////////////////////////////////////////////
18894//////////////////// QCPPlottableLegendItem
18895////////////////////////////////////////////////////////////////////////////////////////////////////
18896
18897/*! \class QCPPlottableLegendItem
18898 \brief A legend item representing a plottable with an icon and the plottable name.
18899
18900 This is the standard legend item for plottables. It displays an icon of the plottable next to the
18901 plottable name. The icon is drawn by the respective plottable itself (\ref
18902 QCPAbstractPlottable::drawLegendIcon), and tries to give an intuitive symbol for the plottable.
18903 For example, the QCPGraph draws a centered horizontal line and/or a single scatter point in the
18904 middle.
18905
18906 Legend items of this type are always associated with one plottable (retrievable via the
18907 plottable() function and settable with the constructor). You may change the font of the plottable
18908 name with \ref setFont. Icon padding and border pen is taken from the parent QCPLegend, see \ref
18909 QCPLegend::setIconBorderPen and \ref QCPLegend::setIconTextPadding.
18910
18911 The function \ref QCPAbstractPlottable::addToLegend/\ref QCPAbstractPlottable::removeFromLegend
18912 creates/removes legend items of this type.
18913
18914 Since QCPLegend is based on QCPLayoutGrid, a legend item itself is just a subclass of
18915 QCPLayoutElement. While it could be added to a legend (or any other layout) via the normal layout
18916 interface, QCPLegend has specialized functions for handling legend items conveniently, see the
18917 documentation of \ref QCPLegend.
18918*/
18919
18920/*!
18921 Creates a new legend item associated with \a plottable.
18922
18923 Once it's created, it can be added to the legend via \ref QCPLegend::addItem.
18924
18925 A more convenient way of adding/removing a plottable to/from the legend is via the functions \ref
18926 QCPAbstractPlottable::addToLegend and \ref QCPAbstractPlottable::removeFromLegend.
18927*/
18929 QCPAbstractLegendItem(parent),
18930 mPlottable(plottable)
18931{
18932 setAntialiased(false);
18933}
18934
18935/*! \internal
18936
18937 Returns the pen that shall be used to draw the icon border, taking into account the selection
18938 state of this item.
18939*/
18941{
18942 return mSelected ? mParentLegend->selectedIconBorderPen() : mParentLegend->iconBorderPen();
18943}
18944
18945/*! \internal
18946
18947 Returns the text color that shall be used to draw text, taking into account the selection state
18948 of this item.
18949*/
18951{
18952 return mSelected ? mSelectedTextColor : mTextColor;
18953}
18954
18955/*! \internal
18956
18957 Returns the font that shall be used to draw text, taking into account the selection state of this
18958 item.
18959*/
18961{
18962 return mSelected ? mSelectedFont : mFont;
18963}
18964
18965/*! \internal
18966
18967 Draws the item with \a painter. The size and position of the drawn legend item is defined by the
18968 parent layout (typically a \ref QCPLegend) and the \ref minimumOuterSizeHint and \ref
18969 maximumOuterSizeHint of this legend item.
18970*/
18972{
18973 if (!mPlottable) return;
18974 painter->setFont(getFont());
18975 painter->setPen(QPen(getTextColor()));
18976 QSize iconSize = mParentLegend->iconSize();
18977 QRect textRect = painter->fontMetrics().boundingRect(0, 0, 0, iconSize.height(), Qt::TextDontClip, mPlottable->name());
18978 QRect iconRect(mRect.topLeft(), iconSize);
18979 int textHeight = qMax(textRect.height(), iconSize.height()); // if text has smaller height than icon, center text vertically in icon height, else align tops
18980 painter->drawText(mRect.x()+iconSize.width()+mParentLegend->iconTextPadding(), mRect.y(), textRect.width(), textHeight, Qt::TextDontClip, mPlottable->name());
18981 // draw icon:
18982 painter->save();
18983 painter->setClipRect(iconRect, Qt::IntersectClip);
18984 mPlottable->drawLegendIcon(painter, iconRect);
18985 painter->restore();
18986 // draw icon border:
18987 if (getIconBorderPen().style() != Qt::NoPen)
18988 {
18989 painter->setPen(getIconBorderPen());
18990 painter->setBrush(Qt::NoBrush);
18991 int halfPen = qCeil(painter->pen().widthF()*0.5)+1;
18992 painter->setClipRect(mOuterRect.adjusted(-halfPen, -halfPen, halfPen, halfPen)); // extend default clip rect so thicker pens (especially during selection) are not clipped
18993 painter->drawRect(iconRect);
18994 }
18995}
18996
18997/*! \internal
18998
18999 Calculates and returns the size of this item. This includes the icon, the text and the padding in
19000 between.
19001
19002 \seebaseclassmethod
19003*/
19005{
19006 if (!mPlottable) return {};
19007 QSize result(0, 0);
19008 QRect textRect;
19009 QFontMetrics fontMetrics(getFont());
19010 QSize iconSize = mParentLegend->iconSize();
19011 textRect = fontMetrics.boundingRect(0, 0, 0, iconSize.height(), Qt::TextDontClip, mPlottable->name());
19012 result.setWidth(iconSize.width() + mParentLegend->iconTextPadding() + textRect.width());
19013 result.setHeight(qMax(textRect.height(), iconSize.height()));
19014 result.rwidth() += mMargins.left()+mMargins.right();
19015 result.rheight() += mMargins.top()+mMargins.bottom();
19016 return result;
19017}
19018
19019
19020////////////////////////////////////////////////////////////////////////////////////////////////////
19021//////////////////// QCPLegend
19022////////////////////////////////////////////////////////////////////////////////////////////////////
19023
19024/*! \class QCPLegend
19025 \brief Manages a legend inside a QCustomPlot.
19026
19027 A legend is a small box somewhere in the plot which lists plottables with their name and icon.
19028
19029 A legend is populated with legend items by calling \ref QCPAbstractPlottable::addToLegend on the
19030 plottable, for which a legend item shall be created. In the case of the main legend (\ref
19031 QCustomPlot::legend), simply adding plottables to the plot while \ref
19032 QCustomPlot::setAutoAddPlottableToLegend is set to true (the default) creates corresponding
19033 legend items. The legend item associated with a certain plottable can be removed with \ref
19034 QCPAbstractPlottable::removeFromLegend. However, QCPLegend also offers an interface to add and
19035 manipulate legend items directly: \ref item, \ref itemWithPlottable, \ref itemCount, \ref
19036 addItem, \ref removeItem, etc.
19037
19038 Since \ref QCPLegend derives from \ref QCPLayoutGrid, it can be placed in any position a \ref
19039 QCPLayoutElement may be positioned. The legend items are themselves \ref QCPLayoutElement
19040 "QCPLayoutElements" which are placed in the grid layout of the legend. \ref QCPLegend only adds
19041 an interface specialized for handling child elements of type \ref QCPAbstractLegendItem, as
19042 mentioned above. In principle, any other layout elements may also be added to a legend via the
19043 normal \ref QCPLayoutGrid interface. See the special page about \link thelayoutsystem The Layout
19044 System\endlink for examples on how to add other elements to the legend and move it outside the axis
19045 rect.
19046
19047 Use the methods \ref setFillOrder and \ref setWrap inherited from \ref QCPLayoutGrid to control
19048 in which order (column first or row first) the legend is filled up when calling \ref addItem, and
19049 at which column or row wrapping occurs. The default fill order for legends is \ref foRowsFirst.
19050
19051 By default, every QCustomPlot has one legend (\ref QCustomPlot::legend) which is placed in the
19052 inset layout of the main axis rect (\ref QCPAxisRect::insetLayout). To move the legend to another
19053 position inside the axis rect, use the methods of the \ref QCPLayoutInset. To move the legend
19054 outside of the axis rect, place it anywhere else with the \ref QCPLayout/\ref QCPLayoutElement
19055 interface.
19056*/
19057
19058/* start of documentation of signals */
19059
19060/*! \fn void QCPLegend::selectionChanged(QCPLegend::SelectableParts selection);
19061
19062 This signal is emitted when the selection state of this legend has changed.
19063
19064 \see setSelectedParts, setSelectableParts
19065*/
19066
19067/* end of documentation of signals */
19068
19069/*!
19070 Constructs a new QCPLegend instance with default values.
19071
19072 Note that by default, QCustomPlot already contains a legend ready to be used as \ref
19073 QCustomPlot::legend
19074*/
19076 mIconTextPadding{}
19077{
19079 setWrap(0);
19080
19081 setRowSpacing(3);
19083 setMargins(QMargins(7, 5, 7, 4));
19084 setAntialiased(false);
19085 setIconSize(32, 18);
19086
19088
19091
19100}
19101
19102QCPLegend::~QCPLegend()
19103{
19104 clearItems();
19105 if (qobject_cast<QCustomPlot*>(mParentPlot)) // make sure this isn't called from QObject dtor when QCustomPlot is already destructed (happens when the legend is not in any layout and thus QObject-child of QCustomPlot)
19106 mParentPlot->legendRemoved(this);
19107}
19108
19109/* no doc for getter, see setSelectedParts */
19110QCPLegend::SelectableParts QCPLegend::selectedParts() const
19111{
19112 // check whether any legend elements selected, if yes, add spItems to return value
19113 bool hasSelectedItems = false;
19114 for (int i=0; i<itemCount(); ++i)
19115 {
19116 if (item(i) && item(i)->selected())
19117 {
19118 hasSelectedItems = true;
19119 break;
19120 }
19121 }
19122 if (hasSelectedItems)
19123 return mSelectedParts | spItems;
19124 else
19125 return mSelectedParts & ~spItems;
19126}
19127
19128/*!
19129 Sets the pen, the border of the entire legend is drawn with.
19130*/
19132{
19133 mBorderPen = pen;
19134}
19135
19136/*!
19137 Sets the brush of the legend background.
19138*/
19139void QCPLegend::setBrush(const QBrush &brush)
19140{
19141 mBrush = brush;
19142}
19143
19144/*!
19145 Sets the default font of legend text. Legend items that draw text (e.g. the name of a graph) will
19146 use this font by default. However, a different font can be specified on a per-item-basis by
19147 accessing the specific legend item.
19148
19149 This function will also set \a font on all already existing legend items.
19150
19151 \see QCPAbstractLegendItem::setFont
19152*/
19153void QCPLegend::setFont(const QFont &font)
19154{
19155 mFont = font;
19156 for (int i=0; i<itemCount(); ++i)
19157 {
19158 if (item(i))
19159 item(i)->setFont(mFont);
19160 }
19161}
19162
19163/*!
19164 Sets the default color of legend text. Legend items that draw text (e.g. the name of a graph)
19165 will use this color by default. However, a different colors can be specified on a per-item-basis
19166 by accessing the specific legend item.
19167
19168 This function will also set \a color on all already existing legend items.
19169
19170 \see QCPAbstractLegendItem::setTextColor
19171*/
19173{
19174 mTextColor = color;
19175 for (int i=0; i<itemCount(); ++i)
19176 {
19177 if (item(i))
19178 item(i)->setTextColor(color);
19179 }
19180}
19181
19182/*!
19183 Sets the size of legend icons. Legend items that draw an icon (e.g. a visual
19184 representation of the graph) will use this size by default.
19185*/
19187{
19188 mIconSize = size;
19189}
19190
19191/*! \overload
19192*/
19193void QCPLegend::setIconSize(int width, int height)
19194{
19195 mIconSize.setWidth(width);
19196 mIconSize.setHeight(height);
19197}
19198
19199/*!
19200 Sets the horizontal space in pixels between the legend icon and the text next to it.
19201 Legend items that draw an icon (e.g. a visual representation of the graph) and text (e.g. the
19202 name of the graph) will use this space by default.
19203*/
19205{
19206 mIconTextPadding = padding;
19207}
19208
19209/*!
19210 Sets the pen used to draw a border around each legend icon. Legend items that draw an
19211 icon (e.g. a visual representation of the graph) will use this pen by default.
19212
19213 If no border is wanted, set this to \a Qt::NoPen.
19214*/
19216{
19217 mIconBorderPen = pen;
19218}
19219
19220/*!
19221 Sets whether the user can (de-)select the parts in \a selectable by clicking on the QCustomPlot surface.
19222 (When \ref QCustomPlot::setInteractions contains \ref QCP::iSelectLegend.)
19223
19224 However, even when \a selectable is set to a value not allowing the selection of a specific part,
19225 it is still possible to set the selection of this part manually, by calling \ref setSelectedParts
19226 directly.
19227
19228 \see SelectablePart, setSelectedParts
19229*/
19231{
19232 if (mSelectableParts != selectable)
19233 {
19234 mSelectableParts = selectable;
19235 emit selectableChanged(mSelectableParts);
19236 }
19237}
19238
19239/*!
19240 Sets the selected state of the respective legend parts described by \ref SelectablePart. When a part
19241 is selected, it uses a different pen/font and brush. If some legend items are selected and \a selected
19242 doesn't contain \ref spItems, those items become deselected.
19243
19244 The entire selection mechanism is handled automatically when \ref QCustomPlot::setInteractions
19245 contains iSelectLegend. You only need to call this function when you wish to change the selection
19246 state manually.
19247
19248 This function can change the selection state of a part even when \ref setSelectableParts was set to a
19249 value that actually excludes the part.
19250
19251 emits the \ref selectionChanged signal when \a selected is different from the previous selection state.
19252
19253 Note that it doesn't make sense to set the selected state \ref spItems here when it wasn't set
19254 before, because there's no way to specify which exact items to newly select. Do this by calling
19255 \ref QCPAbstractLegendItem::setSelected directly on the legend item you wish to select.
19256
19257 \see SelectablePart, setSelectableParts, selectTest, setSelectedBorderPen, setSelectedIconBorderPen, setSelectedBrush,
19258 setSelectedFont
19259*/
19261{
19262 SelectableParts newSelected = selected;
19263 mSelectedParts = this->selectedParts(); // update mSelectedParts in case item selection changed
19264
19265 if (mSelectedParts != newSelected)
19266 {
19267 if (!mSelectedParts.testFlag(spItems) && newSelected.testFlag(spItems)) // attempt to set spItems flag (can't do that)
19268 {
19269 qDebug() << Q_FUNC_INFO << "spItems flag can not be set, it can only be unset with this function";
19270 newSelected &= ~spItems;
19271 }
19272 if (mSelectedParts.testFlag(spItems) && !newSelected.testFlag(spItems)) // spItems flag was unset, so clear item selection
19273 {
19274 for (int i=0; i<itemCount(); ++i)
19275 {
19276 if (item(i))
19277 item(i)->setSelected(false);
19278 }
19279 }
19280 mSelectedParts = newSelected;
19281 emit selectionChanged(mSelectedParts);
19282 }
19283}
19284
19285/*!
19286 When the legend box is selected, this pen is used to draw the border instead of the normal pen
19287 set via \ref setBorderPen.
19288
19289 \see setSelectedParts, setSelectableParts, setSelectedBrush
19290*/
19292{
19293 mSelectedBorderPen = pen;
19294}
19295
19296/*!
19297 Sets the pen legend items will use to draw their icon borders, when they are selected.
19298
19299 \see setSelectedParts, setSelectableParts, setSelectedFont
19300*/
19302{
19303 mSelectedIconBorderPen = pen;
19304}
19305
19306/*!
19307 When the legend box is selected, this brush is used to draw the legend background instead of the normal brush
19308 set via \ref setBrush.
19309
19310 \see setSelectedParts, setSelectableParts, setSelectedBorderPen
19311*/
19313{
19314 mSelectedBrush = brush;
19315}
19316
19317/*!
19318 Sets the default font that is used by legend items when they are selected.
19319
19320 This function will also set \a font on all already existing legend items.
19321
19322 \see setFont, QCPAbstractLegendItem::setSelectedFont
19323*/
19325{
19326 mSelectedFont = font;
19327 for (int i=0; i<itemCount(); ++i)
19328 {
19329 if (item(i))
19330 item(i)->setSelectedFont(font);
19331 }
19332}
19333
19334/*!
19335 Sets the default text color that is used by legend items when they are selected.
19336
19337 This function will also set \a color on all already existing legend items.
19338
19339 \see setTextColor, QCPAbstractLegendItem::setSelectedTextColor
19340*/
19342{
19343 mSelectedTextColor = color;
19344 for (int i=0; i<itemCount(); ++i)
19345 {
19346 if (item(i))
19347 item(i)->setSelectedTextColor(color);
19348 }
19349}
19350
19351/*!
19352 Returns the item with index \a i. If non-legend items were added to the legend, and the element
19353 at the specified cell index is not a QCPAbstractLegendItem, returns \c nullptr.
19354
19355 Note that the linear index depends on the current fill order (\ref setFillOrder).
19356
19357 \see itemCount, addItem, itemWithPlottable
19358*/
19360{
19362}
19363
19364/*!
19365 Returns the QCPPlottableLegendItem which is associated with \a plottable (e.g. a \ref QCPGraph*).
19366 If such an item isn't in the legend, returns \c nullptr.
19367
19368 \see hasItemWithPlottable
19369*/
19371{
19372 for (int i=0; i<itemCount(); ++i)
19373 {
19375 {
19376 if (pli->plottable() == plottable)
19377 return pli;
19378 }
19379 }
19380 return nullptr;
19381}
19382
19383/*!
19384 Returns the number of items currently in the legend. It is identical to the base class
19385 QCPLayoutGrid::elementCount(), and unlike the other "item" interface methods of QCPLegend,
19386 doesn't only address elements which can be cast to QCPAbstractLegendItem.
19387
19388 Note that if empty cells are in the legend (e.g. by calling methods of the \ref QCPLayoutGrid
19389 base class which allows creating empty cells), they are included in the returned count.
19390
19391 \see item
19392*/
19394{
19395 return elementCount();
19396}
19397
19398/*!
19399 Returns whether the legend contains \a item.
19400
19401 \see hasItemWithPlottable
19402*/
19404{
19405 for (int i=0; i<itemCount(); ++i)
19406 {
19407 if (item == this->item(i))
19408 return true;
19409 }
19410 return false;
19411}
19412
19413/*!
19414 Returns whether the legend contains a QCPPlottableLegendItem which is associated with \a plottable (e.g. a \ref QCPGraph*).
19415 If such an item isn't in the legend, returns false.
19416
19417 \see itemWithPlottable
19418*/
19420{
19421 return itemWithPlottable(plottable);
19422}
19423
19424/*!
19425 Adds \a item to the legend, if it's not present already. The element is arranged according to the
19426 current fill order (\ref setFillOrder) and wrapping (\ref setWrap).
19427
19428 Returns true on sucess, i.e. if the item wasn't in the list already and has been successfuly added.
19429
19430 The legend takes ownership of the item.
19431
19432 \see removeItem, item, hasItem
19433*/
19435{
19436 return addElement(item);
19437}
19438
19439/*! \overload
19440
19441 Removes the item with the specified \a index from the legend and deletes it.
19442
19443 After successful removal, the legend is reordered according to the current fill order (\ref
19444 setFillOrder) and wrapping (\ref setWrap), so no empty cell remains where the removed \a item
19445 was. If you don't want this, rather use the raw element interface of \ref QCPLayoutGrid.
19446
19447 Returns true, if successful. Unlike \ref QCPLayoutGrid::removeAt, this method only removes
19448 elements derived from \ref QCPAbstractLegendItem.
19449
19450 \see itemCount, clearItems
19451*/
19453{
19454 if (QCPAbstractLegendItem *ali = item(index))
19455 {
19456 bool success = remove(ali);
19457 if (success)
19458 setFillOrder(fillOrder(), true); // gets rid of empty cell by reordering
19459 return success;
19460 } else
19461 return false;
19462}
19463
19464/*! \overload
19465
19466 Removes \a item from the legend and deletes it.
19467
19468 After successful removal, the legend is reordered according to the current fill order (\ref
19469 setFillOrder) and wrapping (\ref setWrap), so no empty cell remains where the removed \a item
19470 was. If you don't want this, rather use the raw element interface of \ref QCPLayoutGrid.
19471
19472 Returns true, if successful.
19473
19474 \see clearItems
19475*/
19477{
19478 bool success = remove(item);
19479 if (success)
19480 setFillOrder(fillOrder(), true); // gets rid of empty cell by reordering
19481 return success;
19482}
19483
19484/*!
19485 Removes all items from the legend.
19486*/
19488{
19489 for (int i=elementCount()-1; i>=0; --i)
19490 {
19491 if (item(i))
19492 removeAt(i); // don't use removeItem() because it would unnecessarily reorder the whole legend for each item
19493 }
19494 setFillOrder(fillOrder(), true); // get rid of empty cells by reordering once after all items are removed
19495}
19496
19497/*!
19498 Returns the legend items that are currently selected. If no items are selected,
19499 the list is empty.
19500
19501 \see QCPAbstractLegendItem::setSelected, setSelectable
19502*/
19504{
19506 for (int i=0; i<itemCount(); ++i)
19507 {
19508 if (QCPAbstractLegendItem *ali = item(i))
19509 {
19510 if (ali->selected())
19511 result.append(ali);
19512 }
19513 }
19514 return result;
19515}
19516
19517/*! \internal
19518
19519 A convenience function to easily set the QPainter::Antialiased hint on the provided \a painter
19520 before drawing main legend elements.
19521
19522 This is the antialiasing state the painter passed to the \ref draw method is in by default.
19523
19524 This function takes into account the local setting of the antialiasing flag as well as the
19525 overrides set with \ref QCustomPlot::setAntialiasedElements and \ref
19526 QCustomPlot::setNotAntialiasedElements.
19527
19528 \seebaseclassmethod
19529
19530 \see setAntialiased
19531*/
19533{
19534 applyAntialiasingHint(painter, mAntialiased, QCP::aeLegend);
19535}
19536
19537/*! \internal
19538
19539 Returns the pen used to paint the border of the legend, taking into account the selection state
19540 of the legend box.
19541*/
19543{
19544 return mSelectedParts.testFlag(spLegendBox) ? mSelectedBorderPen : mBorderPen;
19545}
19546
19547/*! \internal
19548
19549 Returns the brush used to paint the background of the legend, taking into account the selection
19550 state of the legend box.
19551*/
19553{
19554 return mSelectedParts.testFlag(spLegendBox) ? mSelectedBrush : mBrush;
19555}
19556
19557/*! \internal
19558
19559 Draws the legend box with the provided \a painter. The individual legend items are layerables
19560 themselves, thus are drawn independently.
19561*/
19563{
19564 // draw background rect:
19565 painter->setBrush(getBrush());
19566 painter->setPen(getBorderPen());
19567 painter->drawRect(mOuterRect);
19568}
19569
19570/* inherits documentation from base class */
19571double QCPLegend::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
19572{
19573 if (!mParentPlot) return -1;
19574 if (onlySelectable && !mSelectableParts.testFlag(spLegendBox))
19575 return -1;
19576
19577 if (mOuterRect.contains(pos.toPoint()))
19578 {
19579 if (details) details->setValue(spLegendBox);
19580 return mParentPlot->selectionTolerance()*0.99;
19581 }
19582 return -1;
19583}
19584
19585/* inherits documentation from base class */
19586void QCPLegend::selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged)
19587{
19588 Q_UNUSED(event)
19589 mSelectedParts = selectedParts(); // in case item selection has changed
19590 if (details.value<SelectablePart>() == spLegendBox && mSelectableParts.testFlag(spLegendBox))
19591 {
19592 SelectableParts selBefore = mSelectedParts;
19593 setSelectedParts(additive ? mSelectedParts^spLegendBox : mSelectedParts|spLegendBox); // no need to unset spItems in !additive case, because they will be deselected by QCustomPlot (they're normal QCPLayerables with own deselectEvent)
19594 if (selectionStateChanged)
19595 *selectionStateChanged = mSelectedParts != selBefore;
19596 }
19597}
19598
19599/* inherits documentation from base class */
19600void QCPLegend::deselectEvent(bool *selectionStateChanged)
19601{
19602 mSelectedParts = selectedParts(); // in case item selection has changed
19603 if (mSelectableParts.testFlag(spLegendBox))
19604 {
19605 SelectableParts selBefore = mSelectedParts;
19606 setSelectedParts(selectedParts() & ~spLegendBox);
19607 if (selectionStateChanged)
19608 *selectionStateChanged = mSelectedParts != selBefore;
19609 }
19610}
19611
19612/* inherits documentation from base class */
19617
19618/* inherits documentation from base class */
19623
19624/* inherits documentation from base class */
19626{
19627 if (parentPlot && !parentPlot->legend)
19628 parentPlot->legend = this;
19629}
19630/* end of 'src/layoutelements/layoutelement-legend.cpp' */
19631
19632
19633/* including file 'src/layoutelements/layoutelement-textelement.cpp' */
19634/* modified 2022-11-06T12:45:56, size 12925 */
19635
19636////////////////////////////////////////////////////////////////////////////////////////////////////
19637//////////////////// QCPTextElement
19638////////////////////////////////////////////////////////////////////////////////////////////////////
19639
19640/*! \class QCPTextElement
19641 \brief A layout element displaying a text
19642
19643 The text may be specified with \ref setText, the formatting can be controlled with \ref setFont,
19644 \ref setTextColor, and \ref setTextFlags.
19645
19646 A text element can be added as follows:
19647 \snippet documentation/doc-code-snippets/mainwindow.cpp qcptextelement-creation
19648*/
19649
19650/* start documentation of signals */
19651
19652/*! \fn void QCPTextElement::selectionChanged(bool selected)
19653
19654 This signal is emitted when the selection state has changed to \a selected, either by user
19655 interaction or by a direct call to \ref setSelected.
19656
19657 \see setSelected, setSelectable
19658*/
19659
19660/*! \fn void QCPTextElement::clicked(QMouseEvent *event)
19661
19662 This signal is emitted when the text element is clicked.
19663
19664 \see doubleClicked, selectTest
19665*/
19666
19667/*! \fn void QCPTextElement::doubleClicked(QMouseEvent *event)
19668
19669 This signal is emitted when the text element is double clicked.
19670
19671 \see clicked, selectTest
19672*/
19673
19674/* end documentation of signals */
19675
19676/*! \overload
19677
19678 Creates a new QCPTextElement instance and sets default values. The initial text is empty (\ref
19679 setText).
19680*/
19682 QCPLayoutElement(parentPlot),
19683 mText(),
19684 mTextFlags(Qt::AlignCenter),
19685 mFont(QFont(QLatin1String("sans serif"), 12)), // will be taken from parentPlot if available, see below
19686 mTextColor(Qt::black),
19687 mSelectedFont(QFont(QLatin1String("sans serif"), 12)), // will be taken from parentPlot if available, see below
19688 mSelectedTextColor(Qt::blue),
19689 mSelectable(false),
19690 mSelected(false)
19691{
19692 if (parentPlot)
19693 {
19694 mFont = parentPlot->font();
19695 mSelectedFont = parentPlot->font();
19696 }
19697 setMargins(QMargins(2, 2, 2, 2));
19698}
19699
19700/*! \overload
19701
19702 Creates a new QCPTextElement instance and sets default values.
19703
19704 The initial text is set to \a text.
19705*/
19707 QCPLayoutElement(parentPlot),
19708 mText(text),
19709 mTextFlags(Qt::AlignCenter),
19710 mFont(QFont(QLatin1String("sans serif"), 12)), // will be taken from parentPlot if available, see below
19711 mTextColor(Qt::black),
19712 mSelectedFont(QFont(QLatin1String("sans serif"), 12)), // will be taken from parentPlot if available, see below
19713 mSelectedTextColor(Qt::blue),
19714 mSelectable(false),
19715 mSelected(false)
19716{
19717 if (parentPlot)
19718 {
19719 mFont = parentPlot->font();
19720 mSelectedFont = parentPlot->font();
19721 }
19722 setMargins(QMargins(2, 2, 2, 2));
19723}
19724
19725/*! \overload
19726
19727 Creates a new QCPTextElement instance and sets default values.
19728
19729 The initial text is set to \a text with \a pointSize.
19730*/
19731QCPTextElement::QCPTextElement(QCustomPlot *parentPlot, const QString &text, double pointSize) :
19732 QCPLayoutElement(parentPlot),
19733 mText(text),
19734 mTextFlags(Qt::AlignCenter),
19735 mFont(QFont(QLatin1String("sans serif"), int(pointSize))), // will be taken from parentPlot if available, see below
19736 mTextColor(Qt::black),
19737 mSelectedFont(QFont(QLatin1String("sans serif"), int(pointSize))), // will be taken from parentPlot if available, see below
19738 mSelectedTextColor(Qt::blue),
19739 mSelectable(false),
19740 mSelected(false)
19741{
19742 mFont.setPointSizeF(pointSize); // set here again as floating point, because constructor above only takes integer
19743 if (parentPlot)
19744 {
19745 mFont = parentPlot->font();
19746 mFont.setPointSizeF(pointSize);
19747 mSelectedFont = parentPlot->font();
19748 mSelectedFont.setPointSizeF(pointSize);
19749 }
19750 setMargins(QMargins(2, 2, 2, 2));
19751}
19752
19753/*! \overload
19754
19755 Creates a new QCPTextElement instance and sets default values.
19756
19757 The initial text is set to \a text with \a pointSize and the specified \a fontFamily.
19758*/
19759QCPTextElement::QCPTextElement(QCustomPlot *parentPlot, const QString &text, const QString &fontFamily, double pointSize) :
19760 QCPLayoutElement(parentPlot),
19761 mText(text),
19762 mTextFlags(Qt::AlignCenter),
19763 mFont(QFont(fontFamily, int(pointSize))),
19764 mTextColor(Qt::black),
19765 mSelectedFont(QFont(fontFamily, int(pointSize))),
19766 mSelectedTextColor(Qt::blue),
19767 mSelectable(false),
19768 mSelected(false)
19769{
19770 mFont.setPointSizeF(pointSize); // set here again as floating point, because constructor above only takes integer
19771 setMargins(QMargins(2, 2, 2, 2));
19772}
19773
19774/*! \overload
19775
19776 Creates a new QCPTextElement instance and sets default values.
19777
19778 The initial text is set to \a text with the specified \a font.
19779*/
19780QCPTextElement::QCPTextElement(QCustomPlot *parentPlot, const QString &text, const QFont &font) :
19781 QCPLayoutElement(parentPlot),
19782 mText(text),
19783 mTextFlags(Qt::AlignCenter),
19784 mFont(font),
19785 mTextColor(Qt::black),
19786 mSelectedFont(font),
19787 mSelectedTextColor(Qt::blue),
19788 mSelectable(false),
19789 mSelected(false)
19790{
19791 setMargins(QMargins(2, 2, 2, 2));
19792}
19793
19794/*!
19795 Sets the text that will be displayed to \a text. Multiple lines can be created by insertion of "\n".
19796
19797 \see setFont, setTextColor, setTextFlags
19798*/
19800{
19801 mText = text;
19802}
19803
19804/*!
19805 Sets options for text alignment and wrapping behaviour. \a flags is a bitwise OR-combination of
19806 \c Qt::AlignmentFlag and \c Qt::TextFlag enums.
19807
19808 Possible enums are:
19809 - Qt::AlignLeft
19810 - Qt::AlignRight
19811 - Qt::AlignHCenter
19812 - Qt::AlignJustify
19813 - Qt::AlignTop
19814 - Qt::AlignBottom
19815 - Qt::AlignVCenter
19816 - Qt::AlignCenter
19817 - Qt::TextDontClip
19818 - Qt::TextSingleLine
19819 - Qt::TextExpandTabs
19820 - Qt::TextShowMnemonic
19821 - Qt::TextWordWrap
19822 - Qt::TextIncludeTrailingSpaces
19823*/
19825{
19826 mTextFlags = flags;
19827}
19828
19829/*!
19830 Sets the \a font of the text.
19831
19832 \see setTextColor, setSelectedFont
19833*/
19835{
19836 mFont = font;
19837}
19838
19839/*!
19840 Sets the \a color of the text.
19841
19842 \see setFont, setSelectedTextColor
19843*/
19845{
19846 mTextColor = color;
19847}
19848
19849/*!
19850 Sets the \a font of the text that will be used if the text element is selected (\ref setSelected).
19851
19852 \see setFont
19853*/
19855{
19856 mSelectedFont = font;
19857}
19858
19859/*!
19860 Sets the \a color of the text that will be used if the text element is selected (\ref setSelected).
19861
19862 \see setTextColor
19863*/
19865{
19866 mSelectedTextColor = color;
19867}
19868
19869/*!
19870 Sets whether the user may select this text element.
19871
19872 Note that even when \a selectable is set to <tt>false</tt>, the selection state may be changed
19873 programmatically via \ref setSelected.
19874*/
19876{
19877 if (mSelectable != selectable)
19878 {
19879 mSelectable = selectable;
19880 emit selectableChanged(mSelectable);
19881 }
19882}
19883
19884/*!
19885 Sets the selection state of this text element to \a selected. If the selection has changed, \ref
19886 selectionChanged is emitted.
19887
19888 Note that this function can change the selection state independently of the current \ref
19889 setSelectable state.
19890*/
19892{
19893 if (mSelected != selected)
19894 {
19895 mSelected = selected;
19896 emit selectionChanged(mSelected);
19897 }
19898}
19899
19900/* inherits documentation from base class */
19902{
19903 applyAntialiasingHint(painter, mAntialiased, QCP::aeOther);
19904}
19905
19906/* inherits documentation from base class */
19908{
19909 painter->setFont(mainFont());
19910 painter->setPen(QPen(mainTextColor()));
19911 painter->drawText(mRect, mTextFlags, mText, &mTextBoundingRect);
19912}
19913
19914/* inherits documentation from base class */
19916{
19917 QFontMetrics metrics(mFont);
19918 QSize result(metrics.boundingRect(0, 0, 0, 0, Qt::TextDontClip, mText).size());
19919 result.rwidth() += mMargins.left()+mMargins.right();
19920 result.rheight() += mMargins.top()+mMargins.bottom();
19921 return result;
19922}
19923
19924/* inherits documentation from base class */
19926{
19927 QFontMetrics metrics(mFont);
19928 QSize result(metrics.boundingRect(0, 0, 0, 0, Qt::TextDontClip, mText).size());
19929 result.setWidth(QWIDGETSIZE_MAX);
19930 result.rheight() += mMargins.top()+mMargins.bottom();
19931 return result;
19932}
19933
19934/* inherits documentation from base class */
19935void QCPTextElement::selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged)
19936{
19937 Q_UNUSED(event)
19938 Q_UNUSED(details)
19939 if (mSelectable)
19940 {
19941 bool selBefore = mSelected;
19942 setSelected(additive ? !mSelected : true);
19943 if (selectionStateChanged)
19944 *selectionStateChanged = mSelected != selBefore;
19945 }
19946}
19947
19948/* inherits documentation from base class */
19949void QCPTextElement::deselectEvent(bool *selectionStateChanged)
19950{
19951 if (mSelectable)
19952 {
19953 bool selBefore = mSelected;
19954 setSelected(false);
19955 if (selectionStateChanged)
19956 *selectionStateChanged = mSelected != selBefore;
19957 }
19958}
19959
19960/*!
19961 Returns 0.99*selectionTolerance (see \ref QCustomPlot::setSelectionTolerance) when \a pos is
19962 within the bounding box of the text element's text. Note that this bounding box is updated in the
19963 draw call.
19964
19965 If \a pos is outside the text's bounding box or if \a onlySelectable is true and this text
19966 element is not selectable (\ref setSelectable), returns -1.
19967
19968 \seebaseclassmethod
19969*/
19970double QCPTextElement::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
19971{
19972 Q_UNUSED(details)
19973 if (onlySelectable && !mSelectable)
19974 return -1;
19975
19976 if (mTextBoundingRect.contains(pos.toPoint()))
19977 return mParentPlot->selectionTolerance()*0.99;
19978 else
19979 return -1;
19980}
19981
19982/*!
19983 Accepts the mouse event in order to emit the according click signal in the \ref
19984 mouseReleaseEvent.
19985
19986 \seebaseclassmethod
19987*/
19989{
19990 Q_UNUSED(details)
19991 event->accept();
19992}
19993
19994/*!
19995 Emits the \ref clicked signal if the cursor hasn't moved by more than a few pixels since the \ref
19996 mousePressEvent.
19997
19998 \seebaseclassmethod
19999*/
20001{
20002 if ((QPointF(event->pos())-startPos).manhattanLength() <= 3)
20003 emit clicked(event);
20004}
20005
20006/*!
20007 Emits the \ref doubleClicked signal.
20008
20009 \seebaseclassmethod
20010*/
20012{
20013 Q_UNUSED(details)
20014 emit doubleClicked(event);
20015}
20016
20017/*! \internal
20018
20019 Returns the main font to be used. This is mSelectedFont if \ref setSelected is set to
20020 <tt>true</tt>, else mFont is returned.
20021*/
20023{
20024 return mSelected ? mSelectedFont : mFont;
20025}
20026
20027/*! \internal
20028
20029 Returns the main color to be used. This is mSelectedTextColor if \ref setSelected is set to
20030 <tt>true</tt>, else mTextColor is returned.
20031*/
20033{
20034 return mSelected ? mSelectedTextColor : mTextColor;
20035}
20036/* end of 'src/layoutelements/layoutelement-textelement.cpp' */
20037
20038
20039/* including file 'src/layoutelements/layoutelement-colorscale.cpp' */
20040/* modified 2022-11-06T12:45:56, size 26531 */
20041
20042
20043////////////////////////////////////////////////////////////////////////////////////////////////////
20044//////////////////// QCPColorScale
20045////////////////////////////////////////////////////////////////////////////////////////////////////
20046
20047/*! \class QCPColorScale
20048 \brief A color scale for use with color coding data such as QCPColorMap
20049
20050 This layout element can be placed on the plot to correlate a color gradient with data values. It
20051 is usually used in combination with one or multiple \ref QCPColorMap "QCPColorMaps".
20052
20053 \image html QCPColorScale.png
20054
20055 The color scale can be either horizontal or vertical, as shown in the image above. The
20056 orientation and the side where the numbers appear is controlled with \ref setType.
20057
20058 Use \ref QCPColorMap::setColorScale to connect a color map with a color scale. Once they are
20059 connected, they share their gradient, data range and data scale type (\ref setGradient, \ref
20060 setDataRange, \ref setDataScaleType). Multiple color maps may be associated with a single color
20061 scale, to make them all synchronize these properties.
20062
20063 To have finer control over the number display and axis behaviour, you can directly access the
20064 \ref axis. See the documentation of QCPAxis for details about configuring axes. For example, if
20065 you want to change the number of automatically generated ticks, call
20066 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcolorscale-tickcount
20067
20068 Placing a color scale next to the main axis rect works like with any other layout element:
20069 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcolorscale-creation
20070 In this case we have placed it to the right of the default axis rect, so it wasn't necessary to
20071 call \ref setType, since \ref QCPAxis::atRight is already the default. The text next to the color
20072 scale can be set with \ref setLabel.
20073
20074 For optimum appearance (like in the image above), it may be desirable to line up the axis rect and
20075 the borders of the color scale. Use a \ref QCPMarginGroup to achieve this:
20076 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcolorscale-margingroup
20077
20078 Color scales are initialized with a non-zero minimum top and bottom margin (\ref
20079 setMinimumMargins), because vertical color scales are most common and the minimum top/bottom
20080 margin makes sure it keeps some distance to the top/bottom widget border. So if you change to a
20081 horizontal color scale by setting \ref setType to \ref QCPAxis::atBottom or \ref QCPAxis::atTop, you
20082 might want to also change the minimum margins accordingly, e.g. <tt>setMinimumMargins(QMargins(6, 0, 6, 0))</tt>.
20083*/
20084
20085/* start documentation of inline functions */
20086
20087/*! \fn QCPAxis *QCPColorScale::axis() const
20088
20089 Returns the internal \ref QCPAxis instance of this color scale. You can access it to alter the
20090 appearance and behaviour of the axis. \ref QCPColorScale duplicates some properties in its
20091 interface for convenience. Those are \ref setDataRange (\ref QCPAxis::setRange), \ref
20092 setDataScaleType (\ref QCPAxis::setScaleType), and the method \ref setLabel (\ref
20093 QCPAxis::setLabel). As they each are connected, it does not matter whether you use the method on
20094 the QCPColorScale or on its QCPAxis.
20095
20096 If the type of the color scale is changed with \ref setType, the axis returned by this method
20097 will change, too, to either the left, right, bottom or top axis, depending on which type was set.
20098*/
20099
20100/* end documentation of signals */
20101/* start documentation of signals */
20102
20103/*! \fn void QCPColorScale::dataRangeChanged(const QCPRange &newRange);
20104
20105 This signal is emitted when the data range changes.
20106
20107 \see setDataRange
20108*/
20109
20110/*! \fn void QCPColorScale::dataScaleTypeChanged(QCPAxis::ScaleType scaleType);
20111
20112 This signal is emitted when the data scale type changes.
20113
20114 \see setDataScaleType
20115*/
20116
20117/*! \fn void QCPColorScale::gradientChanged(const QCPColorGradient &newGradient);
20118
20119 This signal is emitted when the gradient changes.
20120
20121 \see setGradient
20122*/
20123
20124/* end documentation of signals */
20125
20126/*!
20127 Constructs a new QCPColorScale.
20128*/
20130 QCPLayoutElement(parentPlot),
20131 mType(QCPAxis::atTop), // set to atTop such that setType(QCPAxis::atRight) below doesn't skip work because it thinks it's already atRight
20132 mDataScaleType(QCPAxis::stLinear),
20133 mGradient(QCPColorGradient::gpCold),
20134 mBarWidth(20),
20135 mAxisRect(new QCPColorScaleAxisRectPrivate(this))
20136{
20137 setMinimumMargins(QMargins(0, 6, 0, 6)); // for default right color scale types, keep some room at bottom and top (important if no margin group is used)
20139 setDataRange(QCPRange(0, 6));
20140}
20141
20142QCPColorScale::~QCPColorScale()
20143{
20144 delete mAxisRect;
20145}
20146
20147/* undocumented getter */
20148QString QCPColorScale::label() const
20149{
20150 if (!mColorAxis)
20151 {
20152 qDebug() << Q_FUNC_INFO << "internal color axis undefined";
20153 return QString();
20154 }
20155
20156 return mColorAxis.data()->label();
20157}
20158
20159/* undocumented getter */
20160bool QCPColorScale::rangeDrag() const
20161{
20162 if (!mAxisRect)
20163 {
20164 qDebug() << Q_FUNC_INFO << "internal axis rect was deleted";
20165 return false;
20166 }
20167
20168 return mAxisRect.data()->rangeDrag().testFlag(QCPAxis::orientation(mType)) &&
20169 mAxisRect.data()->rangeDragAxis(QCPAxis::orientation(mType)) &&
20170 mAxisRect.data()->rangeDragAxis(QCPAxis::orientation(mType))->orientation() == QCPAxis::orientation(mType);
20171}
20172
20173/* undocumented getter */
20174bool QCPColorScale::rangeZoom() const
20175{
20176 if (!mAxisRect)
20177 {
20178 qDebug() << Q_FUNC_INFO << "internal axis rect was deleted";
20179 return false;
20180 }
20181
20182 return mAxisRect.data()->rangeZoom().testFlag(QCPAxis::orientation(mType)) &&
20183 mAxisRect.data()->rangeZoomAxis(QCPAxis::orientation(mType)) &&
20184 mAxisRect.data()->rangeZoomAxis(QCPAxis::orientation(mType))->orientation() == QCPAxis::orientation(mType);
20185}
20186
20187/*!
20188 Sets at which side of the color scale the axis is placed, and thus also its orientation.
20189
20190 Note that after setting \a type to a different value, the axis returned by \ref axis() will
20191 be a different one. The new axis will adopt the following properties from the previous axis: The
20192 range, scale type, label and ticker (the latter will be shared and not copied).
20193*/
20195{
20196 if (!mAxisRect)
20197 {
20198 qDebug() << Q_FUNC_INFO << "internal axis rect was deleted";
20199 return;
20200 }
20201 if (mType != type)
20202 {
20203 mType = type;
20204 QCPRange rangeTransfer(0, 6);
20205 QString labelTransfer;
20206 QSharedPointer<QCPAxisTicker> tickerTransfer;
20207 // transfer/revert some settings on old axis if it exists:
20208 bool doTransfer = !mColorAxis.isNull();
20209 if (doTransfer)
20210 {
20211 rangeTransfer = mColorAxis.data()->range();
20212 labelTransfer = mColorAxis.data()->label();
20213 tickerTransfer = mColorAxis.data()->ticker();
20214 mColorAxis.data()->setLabel(QString());
20215 disconnect(mColorAxis.data(), SIGNAL(rangeChanged(QCPRange)), this, SLOT(setDataRange(QCPRange)));
20216 disconnect(mColorAxis.data(), SIGNAL(scaleTypeChanged(QCPAxis::ScaleType)), this, SLOT(setDataScaleType(QCPAxis::ScaleType)));
20217 }
20219 foreach (QCPAxis::AxisType atype, allAxisTypes)
20220 {
20221 mAxisRect.data()->axis(atype)->setTicks(atype == mType);
20222 mAxisRect.data()->axis(atype)->setTickLabels(atype== mType);
20223 }
20224 // set new mColorAxis pointer:
20225 mColorAxis = mAxisRect.data()->axis(mType);
20226 // transfer settings to new axis:
20227 if (doTransfer)
20228 {
20229 mColorAxis.data()->setRange(rangeTransfer); // range transfer necessary if axis changes from vertical to horizontal or vice versa (axes with same orientation are synchronized via signals)
20230 mColorAxis.data()->setLabel(labelTransfer);
20231 mColorAxis.data()->setTicker(tickerTransfer);
20232 }
20233 connect(mColorAxis.data(), SIGNAL(rangeChanged(QCPRange)), this, SLOT(setDataRange(QCPRange)));
20234 connect(mColorAxis.data(), SIGNAL(scaleTypeChanged(QCPAxis::ScaleType)), this, SLOT(setDataScaleType(QCPAxis::ScaleType)));
20235 mAxisRect.data()->setRangeDragAxes(QList<QCPAxis*>() << mColorAxis.data());
20236 }
20237}
20238
20239/*!
20240 Sets the range spanned by the color gradient and that is shown by the axis in the color scale.
20241
20242 It is equivalent to calling QCPColorMap::setDataRange on any of the connected color maps. It is
20243 also equivalent to directly accessing the \ref axis and setting its range with \ref
20244 QCPAxis::setRange.
20245
20246 \see setDataScaleType, setGradient, rescaleDataRange
20247*/
20249{
20250 if (mDataRange.lower != dataRange.lower || mDataRange.upper != dataRange.upper)
20251 {
20252 mDataRange = dataRange;
20253 if (mColorAxis)
20254 mColorAxis.data()->setRange(mDataRange);
20255 emit dataRangeChanged(mDataRange);
20256 }
20257}
20258
20259/*!
20260 Sets the scale type of the color scale, i.e. whether values are associated with colors linearly
20261 or logarithmically.
20262
20263 It is equivalent to calling QCPColorMap::setDataScaleType on any of the connected color maps. It is
20264 also equivalent to directly accessing the \ref axis and setting its scale type with \ref
20265 QCPAxis::setScaleType.
20266
20267 Note that this method controls the coordinate transformation. For logarithmic scales, you will
20268 likely also want to use a logarithmic tick spacing and labeling, which can be achieved by setting
20269 the color scale's \ref axis ticker to an instance of \ref QCPAxisTickerLog :
20270
20271 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpaxisticker-log-colorscale
20272
20273 See the documentation of \ref QCPAxisTickerLog about the details of logarithmic axis tick
20274 creation.
20275
20276 \see setDataRange, setGradient
20277*/
20279{
20280 if (mDataScaleType != scaleType)
20281 {
20282 mDataScaleType = scaleType;
20283 if (mColorAxis)
20284 mColorAxis.data()->setScaleType(mDataScaleType);
20285 if (mDataScaleType == QCPAxis::stLogarithmic)
20286 setDataRange(mDataRange.sanitizedForLogScale());
20287 emit dataScaleTypeChanged(mDataScaleType);
20288 }
20289}
20290
20291/*!
20292 Sets the color gradient that will be used to represent data values.
20293
20294 It is equivalent to calling QCPColorMap::setGradient on any of the connected color maps.
20295
20296 \see setDataRange, setDataScaleType
20297*/
20299{
20300 if (mGradient != gradient)
20301 {
20302 mGradient = gradient;
20303 if (mAxisRect)
20304 mAxisRect.data()->mGradientImageInvalidated = true;
20305 emit gradientChanged(mGradient);
20306 }
20307}
20308
20309/*!
20310 Sets the axis label of the color scale. This is equivalent to calling \ref QCPAxis::setLabel on
20311 the internal \ref axis.
20312*/
20314{
20315 if (!mColorAxis)
20316 {
20317 qDebug() << Q_FUNC_INFO << "internal color axis undefined";
20318 return;
20319 }
20320
20321 mColorAxis.data()->setLabel(str);
20322}
20323
20324/*!
20325 Sets the width (or height, for horizontal color scales) the bar where the gradient is displayed
20326 will have.
20327*/
20329{
20330 mBarWidth = width;
20331}
20332
20333/*!
20334 Sets whether the user can drag the data range (\ref setDataRange).
20335
20336 Note that \ref QCP::iRangeDrag must be in the QCustomPlot's interactions (\ref
20337 QCustomPlot::setInteractions) to allow range dragging.
20338*/
20340{
20341 if (!mAxisRect)
20342 {
20343 qDebug() << Q_FUNC_INFO << "internal axis rect was deleted";
20344 return;
20345 }
20346
20347 if (enabled)
20348 {
20349 mAxisRect.data()->setRangeDrag(QCPAxis::orientation(mType));
20350 } else
20351 {
20352#if QT_VERSION < QT_VERSION_CHECK(5, 2, 0)
20353 mAxisRect.data()->setRangeDrag(nullptr);
20354#else
20355 mAxisRect.data()->setRangeDrag({});
20356#endif
20357 }
20358}
20359
20360/*!
20361 Sets whether the user can zoom the data range (\ref setDataRange) by scrolling the mouse wheel.
20362
20363 Note that \ref QCP::iRangeZoom must be in the QCustomPlot's interactions (\ref
20364 QCustomPlot::setInteractions) to allow range dragging.
20365*/
20367{
20368 if (!mAxisRect)
20369 {
20370 qDebug() << Q_FUNC_INFO << "internal axis rect was deleted";
20371 return;
20372 }
20373
20374 if (enabled)
20375 {
20376 mAxisRect.data()->setRangeZoom(QCPAxis::orientation(mType));
20377 } else
20378 {
20379#if QT_VERSION < QT_VERSION_CHECK(5, 2, 0)
20380 mAxisRect.data()->setRangeDrag(nullptr);
20381#else
20382 mAxisRect.data()->setRangeZoom({});
20383#endif
20384 }
20385}
20386
20387/*!
20388 Returns a list of all the color maps associated with this color scale.
20389*/
20391{
20392 QList<QCPColorMap*> result;
20393 for (int i=0; i<mParentPlot->plottableCount(); ++i)
20394 {
20395 if (QCPColorMap *cm = qobject_cast<QCPColorMap*>(mParentPlot->plottable(i)))
20396 if (cm->colorScale() == this)
20397 result.append(cm);
20398 }
20399 return result;
20400}
20401
20402/*!
20403 Changes the data range such that all color maps associated with this color scale are fully mapped
20404 to the gradient in the data dimension.
20405
20406 \see setDataRange
20407*/
20408void QCPColorScale::rescaleDataRange(bool onlyVisibleMaps)
20409{
20411 QCPRange newRange;
20412 bool haveRange = false;
20414 if (mDataScaleType == QCPAxis::stLogarithmic)
20415 sign = (mDataRange.upper < 0 ? QCP::sdNegative : QCP::sdPositive);
20416 foreach (QCPColorMap *map, maps)
20417 {
20418 if (!map->realVisibility() && onlyVisibleMaps)
20419 continue;
20420 QCPRange mapRange;
20421 if (map->colorScale() == this)
20422 {
20423 bool currentFoundRange = true;
20424 mapRange = map->data()->dataBounds();
20425 if (sign == QCP::sdPositive)
20426 {
20427 if (mapRange.lower <= 0 && mapRange.upper > 0)
20428 mapRange.lower = mapRange.upper*1e-3;
20429 else if (mapRange.lower <= 0 && mapRange.upper <= 0)
20430 currentFoundRange = false;
20431 } else if (sign == QCP::sdNegative)
20432 {
20433 if (mapRange.upper >= 0 && mapRange.lower < 0)
20434 mapRange.upper = mapRange.lower*1e-3;
20435 else if (mapRange.upper >= 0 && mapRange.lower >= 0)
20436 currentFoundRange = false;
20437 }
20438 if (currentFoundRange)
20439 {
20440 if (!haveRange)
20441 newRange = mapRange;
20442 else
20443 newRange.expand(mapRange);
20444 haveRange = true;
20445 }
20446 }
20447 }
20448 if (haveRange)
20449 {
20450 if (!QCPRange::validRange(newRange)) // likely due to range being zero (plottable has only constant data in this dimension), shift current range to at least center the data
20451 {
20452 double center = (newRange.lower+newRange.upper)*0.5; // upper and lower should be equal anyway, but just to make sure, incase validRange returned false for other reason
20453 if (mDataScaleType == QCPAxis::stLinear)
20454 {
20455 newRange.lower = center-mDataRange.size()/2.0;
20456 newRange.upper = center+mDataRange.size()/2.0;
20457 } else // mScaleType == stLogarithmic
20458 {
20459 newRange.lower = center/qSqrt(mDataRange.upper/mDataRange.lower);
20460 newRange.upper = center*qSqrt(mDataRange.upper/mDataRange.lower);
20461 }
20462 }
20463 setDataRange(newRange);
20464 }
20465}
20466
20467/* inherits documentation from base class */
20469{
20471 if (!mAxisRect)
20472 {
20473 qDebug() << Q_FUNC_INFO << "internal axis rect was deleted";
20474 return;
20475 }
20476
20477 mAxisRect.data()->update(phase);
20478
20479 switch (phase)
20480 {
20481 case upMargins:
20482 {
20483 if (mType == QCPAxis::atBottom || mType == QCPAxis::atTop)
20484 {
20485 setMaximumSize(QWIDGETSIZE_MAX, mBarWidth+mAxisRect.data()->margins().top()+mAxisRect.data()->margins().bottom());
20486 setMinimumSize(0, mBarWidth+mAxisRect.data()->margins().top()+mAxisRect.data()->margins().bottom());
20487 } else
20488 {
20489 setMaximumSize(mBarWidth+mAxisRect.data()->margins().left()+mAxisRect.data()->margins().right(), QWIDGETSIZE_MAX);
20490 setMinimumSize(mBarWidth+mAxisRect.data()->margins().left()+mAxisRect.data()->margins().right(), 0);
20491 }
20492 break;
20493 }
20494 case upLayout:
20495 {
20496 mAxisRect.data()->setOuterRect(rect());
20497 break;
20498 }
20499 default: break;
20500 }
20501}
20502
20503/* inherits documentation from base class */
20505{
20506 painter->setAntialiasing(false);
20507}
20508
20509/* inherits documentation from base class */
20511{
20512 if (!mAxisRect)
20513 {
20514 qDebug() << Q_FUNC_INFO << "internal axis rect was deleted";
20515 return;
20516 }
20517 mAxisRect.data()->mousePressEvent(event, details);
20518}
20519
20520/* inherits documentation from base class */
20522{
20523 if (!mAxisRect)
20524 {
20525 qDebug() << Q_FUNC_INFO << "internal axis rect was deleted";
20526 return;
20527 }
20528 mAxisRect.data()->mouseMoveEvent(event, startPos);
20529}
20530
20531/* inherits documentation from base class */
20533{
20534 if (!mAxisRect)
20535 {
20536 qDebug() << Q_FUNC_INFO << "internal axis rect was deleted";
20537 return;
20538 }
20539 mAxisRect.data()->mouseReleaseEvent(event, startPos);
20540}
20541
20542/* inherits documentation from base class */
20544{
20545 if (!mAxisRect)
20546 {
20547 qDebug() << Q_FUNC_INFO << "internal axis rect was deleted";
20548 return;
20549 }
20550 mAxisRect.data()->wheelEvent(event);
20551}
20552
20553////////////////////////////////////////////////////////////////////////////////////////////////////
20554//////////////////// QCPColorScaleAxisRectPrivate
20555////////////////////////////////////////////////////////////////////////////////////////////////////
20556
20557/*! \class QCPColorScaleAxisRectPrivate
20558
20559 \internal
20560 \brief An axis rect subclass for use in a QCPColorScale
20561
20562 This is a private class and not part of the public QCustomPlot interface.
20563
20564 It provides the axis rect functionality for the QCPColorScale class.
20565*/
20566
20567
20568/*!
20569 Creates a new instance, as a child of \a parentColorScale.
20570*/
20572 QCPAxisRect(parentColorScale->parentPlot(), true),
20573 mParentColorScale(parentColorScale),
20574 mGradientImageInvalidated(true)
20575{
20576 setParentLayerable(parentColorScale);
20577 setMinimumMargins(QMargins(0, 0, 0, 0));
20579 foreach (QCPAxis::AxisType type, allAxisTypes)
20580 {
20581 axis(type)->setVisible(true);
20582 axis(type)->grid()->setVisible(false);
20583 axis(type)->setPadding(0);
20584 connect(axis(type), SIGNAL(selectionChanged(QCPAxis::SelectableParts)), this, SLOT(axisSelectionChanged(QCPAxis::SelectableParts)));
20585 connect(axis(type), SIGNAL(selectableChanged(QCPAxis::SelectableParts)), this, SLOT(axisSelectableChanged(QCPAxis::SelectableParts)));
20586 }
20587
20588 connect(axis(QCPAxis::atLeft), SIGNAL(rangeChanged(QCPRange)), axis(QCPAxis::atRight), SLOT(setRange(QCPRange)));
20589 connect(axis(QCPAxis::atRight), SIGNAL(rangeChanged(QCPRange)), axis(QCPAxis::atLeft), SLOT(setRange(QCPRange)));
20590 connect(axis(QCPAxis::atBottom), SIGNAL(rangeChanged(QCPRange)), axis(QCPAxis::atTop), SLOT(setRange(QCPRange)));
20591 connect(axis(QCPAxis::atTop), SIGNAL(rangeChanged(QCPRange)), axis(QCPAxis::atBottom), SLOT(setRange(QCPRange)));
20592 connect(axis(QCPAxis::atLeft), SIGNAL(scaleTypeChanged(QCPAxis::ScaleType)), axis(QCPAxis::atRight), SLOT(setScaleType(QCPAxis::ScaleType)));
20593 connect(axis(QCPAxis::atRight), SIGNAL(scaleTypeChanged(QCPAxis::ScaleType)), axis(QCPAxis::atLeft), SLOT(setScaleType(QCPAxis::ScaleType)));
20594 connect(axis(QCPAxis::atBottom), SIGNAL(scaleTypeChanged(QCPAxis::ScaleType)), axis(QCPAxis::atTop), SLOT(setScaleType(QCPAxis::ScaleType)));
20595 connect(axis(QCPAxis::atTop), SIGNAL(scaleTypeChanged(QCPAxis::ScaleType)), axis(QCPAxis::atBottom), SLOT(setScaleType(QCPAxis::ScaleType)));
20596
20597 // make layer transfers of color scale transfer to axis rect and axes
20598 // the axes must be set after axis rect, such that they appear above color gradient drawn by axis rect:
20599 connect(parentColorScale, SIGNAL(layerChanged(QCPLayer*)), this, SLOT(setLayer(QCPLayer*)));
20600 foreach (QCPAxis::AxisType type, allAxisTypes)
20601 connect(parentColorScale, SIGNAL(layerChanged(QCPLayer*)), axis(type), SLOT(setLayer(QCPLayer*)));
20602}
20603
20604/*! \internal
20605
20606 Updates the color gradient image if necessary, by calling \ref updateGradientImage, then draws
20607 it. Then the axes are drawn by calling the \ref QCPAxisRect::draw base class implementation.
20608
20609 \seebaseclassmethod
20610*/
20612{
20613 if (mGradientImageInvalidated)
20615
20616 bool mirrorHorz = false;
20617 bool mirrorVert = false;
20618 if (mParentColorScale->mColorAxis)
20619 {
20620 mirrorHorz = mParentColorScale->mColorAxis.data()->rangeReversed() && (mParentColorScale->type() == QCPAxis::atBottom || mParentColorScale->type() == QCPAxis::atTop);
20621 mirrorVert = mParentColorScale->mColorAxis.data()->rangeReversed() && (mParentColorScale->type() == QCPAxis::atLeft || mParentColorScale->type() == QCPAxis::atRight);
20622 }
20623
20624 painter->drawImage(rect().adjusted(0, -1, 0, -1), mGradientImage.mirrored(mirrorHorz, mirrorVert));
20625 QCPAxisRect::draw(painter);
20626}
20627
20628/*! \internal
20629
20630 Uses the current gradient of the parent \ref QCPColorScale (specified in the constructor) to
20631 generate a gradient image. This gradient image will be used in the \ref draw method.
20632*/
20634{
20635 if (rect().isEmpty())
20636 return;
20637
20639 int n = mParentColorScale->mGradient.levelCount();
20640 int w, h;
20641 QVector<double> data(n);
20642 for (int i=0; i<n; ++i)
20643 data[i] = i;
20644 if (mParentColorScale->mType == QCPAxis::atBottom || mParentColorScale->mType == QCPAxis::atTop)
20645 {
20646 w = n;
20647 h = rect().height();
20648 mGradientImage = QImage(w, h, format);
20649 QVector<QRgb*> pixels;
20650 for (int y=0; y<h; ++y)
20651 pixels.append(reinterpret_cast<QRgb*>(mGradientImage.scanLine(y)));
20652 mParentColorScale->mGradient.colorize(data.constData(), QCPRange(0, n-1), pixels.first(), n);
20653 for (int y=1; y<h; ++y)
20654 memcpy(pixels.at(y), pixels.first(), size_t(n)*sizeof(QRgb));
20655 } else
20656 {
20657 w = rect().width();
20658 h = n;
20659 mGradientImage = QImage(w, h, format);
20660 for (int y=0; y<h; ++y)
20661 {
20662 QRgb *pixels = reinterpret_cast<QRgb*>(mGradientImage.scanLine(y));
20663 const QRgb lineColor = mParentColorScale->mGradient.color(data[h-1-y], QCPRange(0, n-1));
20664 for (int x=0; x<w; ++x)
20665 pixels[x] = lineColor;
20666 }
20667 }
20668 mGradientImageInvalidated = false;
20669}
20670
20671/*! \internal
20672
20673 This slot is connected to the selectionChanged signals of the four axes in the constructor. It
20674 synchronizes the selection state of the axes.
20675*/
20677{
20678 // axis bases of four axes shall always (de-)selected synchronously:
20680 foreach (QCPAxis::AxisType type, allAxisTypes)
20681 {
20682 if (QCPAxis *senderAxis = qobject_cast<QCPAxis*>(sender()))
20683 if (senderAxis->axisType() == type)
20684 continue;
20685
20686 if (axis(type)->selectableParts().testFlag(QCPAxis::spAxis))
20687 {
20688 if (selectedParts.testFlag(QCPAxis::spAxis))
20689 axis(type)->setSelectedParts(axis(type)->selectedParts() | QCPAxis::spAxis);
20690 else
20691 axis(type)->setSelectedParts(axis(type)->selectedParts() & ~QCPAxis::spAxis);
20692 }
20693 }
20694}
20695
20696/*! \internal
20697
20698 This slot is connected to the selectableChanged signals of the four axes in the constructor. It
20699 synchronizes the selectability of the axes.
20700*/
20702{
20703 // synchronize axis base selectability:
20705 foreach (QCPAxis::AxisType type, allAxisTypes)
20706 {
20707 if (QCPAxis *senderAxis = qobject_cast<QCPAxis*>(sender()))
20708 if (senderAxis->axisType() == type)
20709 continue;
20710
20711 if (axis(type)->selectableParts().testFlag(QCPAxis::spAxis))
20712 {
20713 if (selectableParts.testFlag(QCPAxis::spAxis))
20714 axis(type)->setSelectableParts(axis(type)->selectableParts() | QCPAxis::spAxis);
20715 else
20716 axis(type)->setSelectableParts(axis(type)->selectableParts() & ~QCPAxis::spAxis);
20717 }
20718 }
20719}
20720/* end of 'src/layoutelements/layoutelement-colorscale.cpp' */
20721
20722
20723/* including file 'src/plottables/plottable-graph.cpp' */
20724/* modified 2022-11-06T12:45:57, size 74926 */
20725
20726////////////////////////////////////////////////////////////////////////////////////////////////////
20727//////////////////// QCPGraphData
20728////////////////////////////////////////////////////////////////////////////////////////////////////
20729
20730/*! \class QCPGraphData
20731 \brief Holds the data of one single data point for QCPGraph.
20732
20733 The stored data is:
20734 \li \a key: coordinate on the key axis of this data point (this is the \a mainKey and the \a sortKey)
20735 \li \a value: coordinate on the value axis of this data point (this is the \a mainValue)
20736
20737 The container for storing multiple data points is \ref QCPGraphDataContainer. It is a typedef for
20738 \ref QCPDataContainer with \ref QCPGraphData as the DataType template parameter. See the
20739 documentation there for an explanation regarding the data type's generic methods.
20740
20741 \see QCPGraphDataContainer
20742*/
20743
20744/* start documentation of inline functions */
20745
20746/*! \fn double QCPGraphData::sortKey() const
20747
20748 Returns the \a key member of this data point.
20749
20750 For a general explanation of what this method is good for in the context of the data container,
20751 see the documentation of \ref QCPDataContainer.
20752*/
20753
20754/*! \fn static QCPGraphData QCPGraphData::fromSortKey(double sortKey)
20755
20756 Returns a data point with the specified \a sortKey. All other members are set to zero.
20757
20758 For a general explanation of what this method is good for in the context of the data container,
20759 see the documentation of \ref QCPDataContainer.
20760*/
20761
20762/*! \fn static static bool QCPGraphData::sortKeyIsMainKey()
20763
20764 Since the member \a key is both the data point key coordinate and the data ordering parameter,
20765 this method returns true.
20766
20767 For a general explanation of what this method is good for in the context of the data container,
20768 see the documentation of \ref QCPDataContainer.
20769*/
20770
20771/*! \fn double QCPGraphData::mainKey() const
20772
20773 Returns the \a key member of this data point.
20774
20775 For a general explanation of what this method is good for in the context of the data container,
20776 see the documentation of \ref QCPDataContainer.
20777*/
20778
20779/*! \fn double QCPGraphData::mainValue() const
20780
20781 Returns the \a value member of this data point.
20782
20783 For a general explanation of what this method is good for in the context of the data container,
20784 see the documentation of \ref QCPDataContainer.
20785*/
20786
20787/*! \fn QCPRange QCPGraphData::valueRange() const
20788
20789 Returns a QCPRange with both lower and upper boundary set to \a value of this data point.
20790
20791 For a general explanation of what this method is good for in the context of the data container,
20792 see the documentation of \ref QCPDataContainer.
20793*/
20794
20795/* end documentation of inline functions */
20796
20797/*!
20798 Constructs a data point with key and value set to zero.
20799*/
20801 key(0),
20802 value(0)
20803{
20804}
20805
20806/*!
20807 Constructs a data point with the specified \a key and \a value.
20808*/
20809QCPGraphData::QCPGraphData(double key, double value) :
20810 key(key),
20811 value(value)
20812{
20813}
20814
20815
20816////////////////////////////////////////////////////////////////////////////////////////////////////
20817//////////////////// QCPGraph
20818////////////////////////////////////////////////////////////////////////////////////////////////////
20819
20820/*! \class QCPGraph
20821 \brief A plottable representing a graph in a plot.
20822
20823 \image html QCPGraph.png
20824
20825 Usually you create new graphs by calling QCustomPlot::addGraph. The resulting instance can be
20826 accessed via QCustomPlot::graph.
20827
20828 To plot data, assign it with the \ref setData or \ref addData functions. Alternatively, you can
20829 also access and modify the data via the \ref data method, which returns a pointer to the internal
20830 \ref QCPGraphDataContainer.
20831
20832 Graphs are used to display single-valued data. Single-valued means that there should only be one
20833 data point per unique key coordinate. In other words, the graph can't have \a loops. If you do
20834 want to plot non-single-valued curves, rather use the QCPCurve plottable.
20835
20836 Gaps in the graph line can be created by adding data points with NaN as value
20837 (<tt>qQNaN()</tt> or <tt>std::numeric_limits<double>::quiet_NaN()</tt>) in between the two data points that shall be
20838 separated.
20839
20840 \section qcpgraph-appearance Changing the appearance
20841
20842 The appearance of the graph is mainly determined by the line style, scatter style, brush and pen
20843 of the graph (\ref setLineStyle, \ref setScatterStyle, \ref setBrush, \ref setPen).
20844
20845 \subsection filling Filling under or between graphs
20846
20847 QCPGraph knows two types of fills: Normal graph fills towards the zero-value-line parallel to
20848 the key axis of the graph, and fills between two graphs, called channel fills. To enable a fill,
20849 just set a brush with \ref setBrush which is neither Qt::NoBrush nor fully transparent.
20850
20851 By default, a normal fill towards the zero-value-line will be drawn. To set up a channel fill
20852 between this graph and another one, call \ref setChannelFillGraph with the other graph as
20853 parameter.
20854
20855 \see QCustomPlot::addGraph, QCustomPlot::graph
20856*/
20857
20858/* start of documentation of inline functions */
20859
20860/*! \fn QSharedPointer<QCPGraphDataContainer> QCPGraph::data() const
20861
20862 Returns a shared pointer to the internal data storage of type \ref QCPGraphDataContainer. You may
20863 use it to directly manipulate the data, which may be more convenient and faster than using the
20864 regular \ref setData or \ref addData methods.
20865*/
20866
20867/* end of documentation of inline functions */
20868
20869/*!
20870 Constructs a graph which uses \a keyAxis as its key axis ("x") and \a valueAxis as its value
20871 axis ("y"). \a keyAxis and \a valueAxis must reside in the same QCustomPlot instance and not have
20872 the same orientation. If either of these restrictions is violated, a corresponding message is
20873 printed to the debug output (qDebug), the construction is not aborted, though.
20874
20875 The created QCPGraph is automatically registered with the QCustomPlot instance inferred from \a
20876 keyAxis. This QCustomPlot instance takes ownership of the QCPGraph, so do not delete it manually
20877 but use QCustomPlot::removePlottable() instead.
20878
20879 To directly create a graph inside a plot, you can also use the simpler QCustomPlot::addGraph function.
20880*/
20881QCPGraph::QCPGraph(QCPAxis *keyAxis, QCPAxis *valueAxis) :
20882 QCPAbstractPlottable1D<QCPGraphData>(keyAxis, valueAxis),
20883 mLineStyle{},
20884 mScatterSkip{},
20885 mAdaptiveSampling{}
20886{
20887 // special handling for QCPGraphs to maintain the simple graph interface:
20888 mParentPlot->registerGraph(this);
20889
20890 setPen(QPen(Qt::blue, 0));
20892
20894 setScatterSkip(0);
20895 setChannelFillGraph(nullptr);
20896 setAdaptiveSampling(true);
20897}
20898
20899QCPGraph::~QCPGraph()
20900{
20901}
20902
20903/*! \overload
20904
20905 Replaces the current data container with the provided \a data container.
20906
20907 Since a QSharedPointer is used, multiple QCPGraphs may share the same data container safely.
20908 Modifying the data in the container will then affect all graphs that share the container. Sharing
20909 can be achieved by simply exchanging the data containers wrapped in shared pointers:
20910 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpgraph-datasharing-1
20911
20912 If you do not wish to share containers, but create a copy from an existing container, rather use
20913 the \ref QCPDataContainer<DataType>::set method on the graph's data container directly:
20914 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpgraph-datasharing-2
20915
20916 \see addData
20917*/
20919{
20920 mDataContainer = data;
20921}
20922
20923/*! \overload
20924
20925 Replaces the current data with the provided points in \a keys and \a values. The provided
20926 vectors should have equal length. Else, the number of added points will be the size of the
20927 smallest vector.
20928
20929 If you can guarantee that the passed data points are sorted by \a keys in ascending order, you
20930 can set \a alreadySorted to true, to improve performance by saving a sorting run.
20931
20932 \see addData
20933*/
20934void QCPGraph::setData(const QVector<double> &keys, const QVector<double> &values, bool alreadySorted)
20935{
20936 mDataContainer->clear();
20937 addData(keys, values, alreadySorted);
20938}
20939
20940/*!
20941 Sets how the single data points are connected in the plot. For scatter-only plots, set \a ls to
20942 \ref lsNone and \ref setScatterStyle to the desired scatter style.
20943
20944 \see setScatterStyle
20945*/
20947{
20948 mLineStyle = ls;
20949}
20950
20951/*!
20952 Sets the visual appearance of single data points in the plot. If set to \ref QCPScatterStyle::ssNone, no scatter points
20953 are drawn (e.g. for line-only-plots with appropriate line style).
20954
20955 \see QCPScatterStyle, setLineStyle
20956*/
20958{
20959 mScatterStyle = style;
20960}
20961
20962/*!
20963 If scatters are displayed (scatter style not \ref QCPScatterStyle::ssNone), \a skip number of
20964 scatter points are skipped/not drawn after every drawn scatter point.
20965
20966 This can be used to make the data appear sparser while for example still having a smooth line,
20967 and to improve performance for very high density plots.
20968
20969 If \a skip is set to 0 (default), all scatter points are drawn.
20970
20971 \see setScatterStyle
20972*/
20974{
20975 mScatterSkip = qMax(0, skip);
20976}
20977
20978/*!
20979 Sets the target graph for filling the area between this graph and \a targetGraph with the current
20980 brush (\ref setBrush).
20981
20982 When \a targetGraph is set to 0, a normal graph fill to the zero-value-line will be shown. To
20983 disable any filling, set the brush to Qt::NoBrush.
20984
20985 \see setBrush
20986*/
20988{
20989 // prevent setting channel target to this graph itself:
20990 if (targetGraph == this)
20991 {
20992 qDebug() << Q_FUNC_INFO << "targetGraph is this graph itself";
20993 mChannelFillGraph = nullptr;
20994 return;
20995 }
20996 // prevent setting channel target to a graph not in the plot:
20997 if (targetGraph && targetGraph->mParentPlot != mParentPlot)
20998 {
20999 qDebug() << Q_FUNC_INFO << "targetGraph not in same plot";
21000 mChannelFillGraph = nullptr;
21001 return;
21002 }
21003
21004 mChannelFillGraph = targetGraph;
21005}
21006
21007/*!
21008 Sets whether adaptive sampling shall be used when plotting this graph. QCustomPlot's adaptive
21009 sampling technique can drastically improve the replot performance for graphs with a larger number
21010 of points (e.g. above 10,000), without notably changing the appearance of the graph.
21011
21012 By default, adaptive sampling is enabled. Even if enabled, QCustomPlot decides whether adaptive
21013 sampling shall actually be used on a per-graph basis. So leaving adaptive sampling enabled has no
21014 disadvantage in almost all cases.
21015
21016 \image html adaptive-sampling-line.png "A line plot of 500,000 points without and with adaptive sampling"
21017
21018 As can be seen, line plots experience no visual degradation from adaptive sampling. Outliers are
21019 reproduced reliably, as well as the overall shape of the data set. The replot time reduces
21020 dramatically though. This allows QCustomPlot to display large amounts of data in realtime.
21021
21022 \image html adaptive-sampling-scatter.png "A scatter plot of 100,000 points without and with adaptive sampling"
21023
21024 Care must be taken when using high-density scatter plots in combination with adaptive sampling.
21025 The adaptive sampling algorithm treats scatter plots more carefully than line plots which still
21026 gives a significant reduction of replot times, but not quite as much as for line plots. This is
21027 because scatter plots inherently need more data points to be preserved in order to still resemble
21028 the original, non-adaptive-sampling plot. As shown above, the results still aren't quite
21029 identical, as banding occurs for the outer data points. This is in fact intentional, such that
21030 the boundaries of the data cloud stay visible to the viewer. How strong the banding appears,
21031 depends on the point density, i.e. the number of points in the plot.
21032
21033 For some situations with scatter plots it might thus be desirable to manually turn adaptive
21034 sampling off. For example, when saving the plot to disk. This can be achieved by setting \a
21035 enabled to false before issuing a command like \ref QCustomPlot::savePng, and setting \a enabled
21036 back to true afterwards.
21037*/
21039{
21040 mAdaptiveSampling = enabled;
21041}
21042
21043/*! \overload
21044
21045 Adds the provided points in \a keys and \a values to the current data. The provided vectors
21046 should have equal length. Else, the number of added points will be the size of the smallest
21047 vector.
21048
21049 If you can guarantee that the passed data points are sorted by \a keys in ascending order, you
21050 can set \a alreadySorted to true, to improve performance by saving a sorting run.
21051
21052 Alternatively, you can also access and modify the data directly via the \ref data method, which
21053 returns a pointer to the internal data container.
21054*/
21055void QCPGraph::addData(const QVector<double> &keys, const QVector<double> &values, bool alreadySorted)
21056{
21057 if (keys.size() != values.size())
21058 qDebug() << Q_FUNC_INFO << "keys and values have different sizes:" << keys.size() << values.size();
21059 const int n = qMin(keys.size(), values.size());
21060 QVector<QCPGraphData> tempData(n);
21061 QVector<QCPGraphData>::iterator it = tempData.begin();
21062 const QVector<QCPGraphData>::iterator itEnd = tempData.end();
21063 int i = 0;
21064 while (it != itEnd)
21065 {
21066 it->key = keys[i];
21067 it->value = values[i];
21068 ++it;
21069 ++i;
21070 }
21071 mDataContainer->add(tempData, alreadySorted); // don't modify tempData beyond this to prevent copy on write
21072}
21073
21074/*! \overload
21075
21076 Adds the provided data point as \a key and \a value to the current data.
21077
21078 Alternatively, you can also access and modify the data directly via the \ref data method, which
21079 returns a pointer to the internal data container.
21080*/
21081void QCPGraph::addData(double key, double value)
21082{
21083 mDataContainer->add(QCPGraphData(key, value));
21084}
21085
21086/*!
21087 Implements a selectTest specific to this plottable's point geometry.
21088
21089 If \a details is not 0, it will be set to a \ref QCPDataSelection, describing the closest data
21090 point to \a pos.
21091
21092 \seebaseclassmethod \ref QCPAbstractPlottable::selectTest
21093*/
21094double QCPGraph::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
21095{
21096 if ((onlySelectable && mSelectable == QCP::stNone) || mDataContainer->isEmpty())
21097 return -1;
21098 if (!mKeyAxis || !mValueAxis)
21099 return -1;
21100
21101 if (mKeyAxis.data()->axisRect()->rect().contains(pos.toPoint()) || mParentPlot->interactions().testFlag(QCP::iSelectPlottablesBeyondAxisRect))
21102 {
21103 QCPGraphDataContainer::const_iterator closestDataPoint = mDataContainer->constEnd();
21104 double result = pointDistance(pos, closestDataPoint);
21105 if (details)
21106 {
21107 int pointIndex = int(closestDataPoint-mDataContainer->constBegin());
21108 details->setValue(QCPDataSelection(QCPDataRange(pointIndex, pointIndex+1)));
21109 }
21110 return result;
21111 } else
21112 return -1;
21113}
21114
21115/* inherits documentation from base class */
21116QCPRange QCPGraph::getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain) const
21117{
21118 return mDataContainer->keyRange(foundRange, inSignDomain);
21119}
21120
21121/* inherits documentation from base class */
21122QCPRange QCPGraph::getValueRange(bool &foundRange, QCP::SignDomain inSignDomain, const QCPRange &inKeyRange) const
21123{
21124 return mDataContainer->valueRange(foundRange, inSignDomain, inKeyRange);
21125}
21126
21127/* inherits documentation from base class */
21129{
21130 if (!mKeyAxis || !mValueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
21131 if (mKeyAxis.data()->range().size() <= 0 || mDataContainer->isEmpty()) return;
21132 if (mLineStyle == lsNone && mScatterStyle.isNone()) return;
21133
21134 QVector<QPointF> lines, scatters; // line and (if necessary) scatter pixel coordinates will be stored here while iterating over segments
21135
21136 // loop over and draw segments of unselected/selected data:
21137 QList<QCPDataRange> selectedSegments, unselectedSegments, allSegments;
21138 getDataSegments(selectedSegments, unselectedSegments);
21139 allSegments << unselectedSegments << selectedSegments;
21140 for (int i=0; i<allSegments.size(); ++i)
21141 {
21142 bool isSelectedSegment = i >= unselectedSegments.size();
21143 // get line pixel points appropriate to line style:
21144 QCPDataRange lineDataRange = isSelectedSegment ? allSegments.at(i) : allSegments.at(i).adjusted(-1, 1); // unselected segments extend lines to bordering selected data point (safe to exceed total data bounds in first/last segment, getLines takes care)
21145 getLines(&lines, lineDataRange);
21146
21147 // check data validity if flag set:
21148#ifdef QCUSTOMPLOT_CHECK_DATA
21150 for (it = mDataContainer->constBegin(); it != mDataContainer->constEnd(); ++it)
21151 {
21152 if (QCP::isInvalidData(it->key, it->value))
21153 qDebug() << Q_FUNC_INFO << "Data point at" << it->key << "invalid." << "Plottable name:" << name();
21154 }
21155#endif
21156
21157 // draw fill of graph:
21158 if (isSelectedSegment && mSelectionDecorator)
21159 mSelectionDecorator->applyBrush(painter);
21160 else
21161 painter->setBrush(mBrush);
21162 painter->setPen(Qt::NoPen);
21163 drawFill(painter, &lines);
21164
21165 // draw line:
21166 if (mLineStyle != lsNone)
21167 {
21168 if (isSelectedSegment && mSelectionDecorator)
21169 mSelectionDecorator->applyPen(painter);
21170 else
21171 painter->setPen(mPen);
21172 painter->setBrush(Qt::NoBrush);
21173 if (mLineStyle == lsImpulse)
21174 drawImpulsePlot(painter, lines);
21175 else
21176 drawLinePlot(painter, lines); // also step plots can be drawn as a line plot
21177 }
21178
21179 // draw scatters:
21180 QCPScatterStyle finalScatterStyle = mScatterStyle;
21181 if (isSelectedSegment && mSelectionDecorator)
21182 finalScatterStyle = mSelectionDecorator->getFinalScatterStyle(mScatterStyle);
21183 if (!finalScatterStyle.isNone())
21184 {
21185 getScatters(&scatters, allSegments.at(i));
21186 drawScatterPlot(painter, scatters, finalScatterStyle);
21187 }
21188 }
21189
21190 // draw other selection decoration that isn't just line/scatter pens and brushes:
21191 if (mSelectionDecorator)
21192 mSelectionDecorator->drawDecoration(painter, selection());
21193}
21194
21195/* inherits documentation from base class */
21196void QCPGraph::drawLegendIcon(QCPPainter *painter, const QRectF &rect) const
21197{
21198 // draw fill:
21199 if (mBrush.style() != Qt::NoBrush)
21200 {
21202 painter->fillRect(QRectF(rect.left(), rect.top()+rect.height()/2.0, rect.width(), rect.height()/3.0), mBrush);
21203 }
21204 // draw line vertically centered:
21205 if (mLineStyle != lsNone)
21206 {
21208 painter->setPen(mPen);
21209 painter->drawLine(QLineF(rect.left(), rect.top()+rect.height()/2.0, rect.right()+5, rect.top()+rect.height()/2.0)); // +5 on x2 else last segment is missing from dashed/dotted pens
21210 }
21211 // draw scatter symbol:
21212 if (!mScatterStyle.isNone())
21213 {
21215 // scale scatter pixmap if it's too large to fit in legend icon rect:
21216 if (mScatterStyle.shape() == QCPScatterStyle::ssPixmap && (mScatterStyle.pixmap().size().width() > rect.width() || mScatterStyle.pixmap().size().height() > rect.height()))
21217 {
21218 QCPScatterStyle scaledStyle(mScatterStyle);
21219 scaledStyle.setPixmap(scaledStyle.pixmap().scaled(rect.size().toSize(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
21220 scaledStyle.applyTo(painter, mPen);
21221 scaledStyle.drawShape(painter, QRectF(rect).center());
21222 } else
21223 {
21224 mScatterStyle.applyTo(painter, mPen);
21225 mScatterStyle.drawShape(painter, QRectF(rect).center());
21226 }
21227 }
21228}
21229
21230/*! \internal
21231
21232 This method retrieves an optimized set of data points via \ref getOptimizedLineData, and branches
21233 out to the line style specific functions such as \ref dataToLines, \ref dataToStepLeftLines, etc.
21234 according to the line style of the graph.
21235
21236 \a lines will be filled with points in pixel coordinates, that can be drawn with the according
21237 draw functions like \ref drawLinePlot and \ref drawImpulsePlot. The points returned in \a lines
21238 aren't necessarily the original data points. For example, step line styles require additional
21239 points to form the steps when drawn. If the line style of the graph is \ref lsNone, the \a
21240 lines vector will be empty.
21241
21242 \a dataRange specifies the beginning and ending data indices that will be taken into account for
21243 conversion. In this function, the specified range may exceed the total data bounds without harm:
21244 a correspondingly trimmed data range will be used. This takes the burden off the user of this
21245 function to check for valid indices in \a dataRange, e.g. when extending ranges coming from \ref
21246 getDataSegments.
21247
21248 \see getScatters
21249*/
21250void QCPGraph::getLines(QVector<QPointF> *lines, const QCPDataRange &dataRange) const
21251{
21252 if (!lines) return;
21254 getVisibleDataBounds(begin, end, dataRange);
21255 if (begin == end)
21256 {
21257 lines->clear();
21258 return;
21259 }
21260
21261 QVector<QCPGraphData> lineData;
21262 if (mLineStyle != lsNone)
21263 getOptimizedLineData(&lineData, begin, end);
21264
21265 if (mKeyAxis->rangeReversed() != (mKeyAxis->orientation() == Qt::Vertical)) // make sure key pixels are sorted ascending in lineData (significantly simplifies following processing)
21266 std::reverse(lineData.begin(), lineData.end());
21267
21268 switch (mLineStyle)
21269 {
21270 case lsNone: lines->clear(); break;
21271 case lsLine: *lines = dataToLines(lineData); break;
21272 case lsStepLeft: *lines = dataToStepLeftLines(lineData); break;
21273 case lsStepRight: *lines = dataToStepRightLines(lineData); break;
21274 case lsStepCenter: *lines = dataToStepCenterLines(lineData); break;
21275 case lsImpulse: *lines = dataToImpulseLines(lineData); break;
21276 }
21277}
21278
21279/*! \internal
21280
21281 This method retrieves an optimized set of data points via \ref getOptimizedScatterData and then
21282 converts them to pixel coordinates. The resulting points are returned in \a scatters, and can be
21283 passed to \ref drawScatterPlot.
21284
21285 \a dataRange specifies the beginning and ending data indices that will be taken into account for
21286 conversion. In this function, the specified range may exceed the total data bounds without harm:
21287 a correspondingly trimmed data range will be used. This takes the burden off the user of this
21288 function to check for valid indices in \a dataRange, e.g. when extending ranges coming from \ref
21289 getDataSegments.
21290*/
21291void QCPGraph::getScatters(QVector<QPointF> *scatters, const QCPDataRange &dataRange) const
21292{
21293 if (!scatters) return;
21294 QCPAxis *keyAxis = mKeyAxis.data();
21295 QCPAxis *valueAxis = mValueAxis.data();
21296 if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; scatters->clear(); return; }
21297
21299 getVisibleDataBounds(begin, end, dataRange);
21300 if (begin == end)
21301 {
21302 scatters->clear();
21303 return;
21304 }
21305
21307 getOptimizedScatterData(&data, begin, end);
21308
21309 if (mKeyAxis->rangeReversed() != (mKeyAxis->orientation() == Qt::Vertical)) // make sure key pixels are sorted ascending in data (significantly simplifies following processing)
21310 std::reverse(data.begin(), data.end());
21311
21312 scatters->resize(data.size());
21313 if (keyAxis->orientation() == Qt::Vertical)
21314 {
21315 for (int i=0; i<data.size(); ++i)
21316 {
21317 if (!qIsNaN(data.at(i).value))
21318 {
21319 (*scatters)[i].setX(valueAxis->coordToPixel(data.at(i).value));
21320 (*scatters)[i].setY(keyAxis->coordToPixel(data.at(i).key));
21321 }
21322 }
21323 } else
21324 {
21325 for (int i=0; i<data.size(); ++i)
21326 {
21327 if (!qIsNaN(data.at(i).value))
21328 {
21329 (*scatters)[i].setX(keyAxis->coordToPixel(data.at(i).key));
21330 (*scatters)[i].setY(valueAxis->coordToPixel(data.at(i).value));
21331 }
21332 }
21333 }
21334}
21335
21336/*! \internal
21337
21338 Takes raw data points in plot coordinates as \a data, and returns a vector containing pixel
21339 coordinate points which are suitable for drawing the line style \ref lsLine.
21340
21341 The source of \a data is usually \ref getOptimizedLineData, and this method is called in \a
21342 getLines if the line style is set accordingly.
21343
21344 \see dataToStepLeftLines, dataToStepRightLines, dataToStepCenterLines, dataToImpulseLines, getLines, drawLinePlot
21345*/
21347{
21348 QVector<QPointF> result;
21349 QCPAxis *keyAxis = mKeyAxis.data();
21350 QCPAxis *valueAxis = mValueAxis.data();
21351 if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return result; }
21352
21353 result.resize(data.size());
21354
21355 // transform data points to pixels:
21356 if (keyAxis->orientation() == Qt::Vertical)
21357 {
21358 for (int i=0; i<data.size(); ++i)
21359 {
21360 result[i].setX(valueAxis->coordToPixel(data.at(i).value));
21361 result[i].setY(keyAxis->coordToPixel(data.at(i).key));
21362 }
21363 } else // key axis is horizontal
21364 {
21365 for (int i=0; i<data.size(); ++i)
21366 {
21367 result[i].setX(keyAxis->coordToPixel(data.at(i).key));
21368 result[i].setY(valueAxis->coordToPixel(data.at(i).value));
21369 }
21370 }
21371 return result;
21372}
21373
21374/*! \internal
21375
21376 Takes raw data points in plot coordinates as \a data, and returns a vector containing pixel
21377 coordinate points which are suitable for drawing the line style \ref lsStepLeft.
21378
21379 The source of \a data is usually \ref getOptimizedLineData, and this method is called in \a
21380 getLines if the line style is set accordingly.
21381
21382 \see dataToLines, dataToStepRightLines, dataToStepCenterLines, dataToImpulseLines, getLines, drawLinePlot
21383*/
21385{
21386 QVector<QPointF> result;
21387 QCPAxis *keyAxis = mKeyAxis.data();
21388 QCPAxis *valueAxis = mValueAxis.data();
21389 if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return result; }
21390
21391 result.resize(data.size()*2);
21392
21393 // calculate steps from data and transform to pixel coordinates:
21394 if (keyAxis->orientation() == Qt::Vertical)
21395 {
21396 double lastValue = valueAxis->coordToPixel(data.first().value);
21397 for (int i=0; i<data.size(); ++i)
21398 {
21399 const double key = keyAxis->coordToPixel(data.at(i).key);
21400 result[i*2+0].setX(lastValue);
21401 result[i*2+0].setY(key);
21402 lastValue = valueAxis->coordToPixel(data.at(i).value);
21403 result[i*2+1].setX(lastValue);
21404 result[i*2+1].setY(key);
21405 }
21406 } else // key axis is horizontal
21407 {
21408 double lastValue = valueAxis->coordToPixel(data.first().value);
21409 for (int i=0; i<data.size(); ++i)
21410 {
21411 const double key = keyAxis->coordToPixel(data.at(i).key);
21412 result[i*2+0].setX(key);
21413 result[i*2+0].setY(lastValue);
21414 lastValue = valueAxis->coordToPixel(data.at(i).value);
21415 result[i*2+1].setX(key);
21416 result[i*2+1].setY(lastValue);
21417 }
21418 }
21419 return result;
21420}
21421
21422/*! \internal
21423
21424 Takes raw data points in plot coordinates as \a data, and returns a vector containing pixel
21425 coordinate points which are suitable for drawing the line style \ref lsStepRight.
21426
21427 The source of \a data is usually \ref getOptimizedLineData, and this method is called in \a
21428 getLines if the line style is set accordingly.
21429
21430 \see dataToLines, dataToStepLeftLines, dataToStepCenterLines, dataToImpulseLines, getLines, drawLinePlot
21431*/
21433{
21434 QVector<QPointF> result;
21435 QCPAxis *keyAxis = mKeyAxis.data();
21436 QCPAxis *valueAxis = mValueAxis.data();
21437 if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return result; }
21438
21439 result.resize(data.size()*2);
21440
21441 // calculate steps from data and transform to pixel coordinates:
21442 if (keyAxis->orientation() == Qt::Vertical)
21443 {
21444 double lastKey = keyAxis->coordToPixel(data.first().key);
21445 for (int i=0; i<data.size(); ++i)
21446 {
21447 const double value = valueAxis->coordToPixel(data.at(i).value);
21448 result[i*2+0].setX(value);
21449 result[i*2+0].setY(lastKey);
21450 lastKey = keyAxis->coordToPixel(data.at(i).key);
21451 result[i*2+1].setX(value);
21452 result[i*2+1].setY(lastKey);
21453 }
21454 } else // key axis is horizontal
21455 {
21456 double lastKey = keyAxis->coordToPixel(data.first().key);
21457 for (int i=0; i<data.size(); ++i)
21458 {
21459 const double value = valueAxis->coordToPixel(data.at(i).value);
21460 result[i*2+0].setX(lastKey);
21461 result[i*2+0].setY(value);
21462 lastKey = keyAxis->coordToPixel(data.at(i).key);
21463 result[i*2+1].setX(lastKey);
21464 result[i*2+1].setY(value);
21465 }
21466 }
21467 return result;
21468}
21469
21470/*! \internal
21471
21472 Takes raw data points in plot coordinates as \a data, and returns a vector containing pixel
21473 coordinate points which are suitable for drawing the line style \ref lsStepCenter.
21474
21475 The source of \a data is usually \ref getOptimizedLineData, and this method is called in \a
21476 getLines if the line style is set accordingly.
21477
21478 \see dataToLines, dataToStepLeftLines, dataToStepRightLines, dataToImpulseLines, getLines, drawLinePlot
21479*/
21481{
21482 QVector<QPointF> result;
21483 QCPAxis *keyAxis = mKeyAxis.data();
21484 QCPAxis *valueAxis = mValueAxis.data();
21485 if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return result; }
21486
21487 result.resize(data.size()*2);
21488
21489 // calculate steps from data and transform to pixel coordinates:
21490 if (keyAxis->orientation() == Qt::Vertical)
21491 {
21492 double lastKey = keyAxis->coordToPixel(data.first().key);
21493 double lastValue = valueAxis->coordToPixel(data.first().value);
21494 result[0].setX(lastValue);
21495 result[0].setY(lastKey);
21496 for (int i=1; i<data.size(); ++i)
21497 {
21498 const double key = (keyAxis->coordToPixel(data.at(i).key)+lastKey)*0.5;
21499 result[i*2-1].setX(lastValue);
21500 result[i*2-1].setY(key);
21501 lastValue = valueAxis->coordToPixel(data.at(i).value);
21502 lastKey = keyAxis->coordToPixel(data.at(i).key);
21503 result[i*2+0].setX(lastValue);
21504 result[i*2+0].setY(key);
21505 }
21506 result[data.size()*2-1].setX(lastValue);
21507 result[data.size()*2-1].setY(lastKey);
21508 } else // key axis is horizontal
21509 {
21510 double lastKey = keyAxis->coordToPixel(data.first().key);
21511 double lastValue = valueAxis->coordToPixel(data.first().value);
21512 result[0].setX(lastKey);
21513 result[0].setY(lastValue);
21514 for (int i=1; i<data.size(); ++i)
21515 {
21516 const double key = (keyAxis->coordToPixel(data.at(i).key)+lastKey)*0.5;
21517 result[i*2-1].setX(key);
21518 result[i*2-1].setY(lastValue);
21519 lastValue = valueAxis->coordToPixel(data.at(i).value);
21520 lastKey = keyAxis->coordToPixel(data.at(i).key);
21521 result[i*2+0].setX(key);
21522 result[i*2+0].setY(lastValue);
21523 }
21524 result[data.size()*2-1].setX(lastKey);
21525 result[data.size()*2-1].setY(lastValue);
21526 }
21527 return result;
21528}
21529
21530/*! \internal
21531
21532 Takes raw data points in plot coordinates as \a data, and returns a vector containing pixel
21533 coordinate points which are suitable for drawing the line style \ref lsImpulse.
21534
21535 The source of \a data is usually \ref getOptimizedLineData, and this method is called in \a
21536 getLines if the line style is set accordingly.
21537
21538 \see dataToLines, dataToStepLeftLines, dataToStepRightLines, dataToStepCenterLines, getLines, drawImpulsePlot
21539*/
21541{
21542 QVector<QPointF> result;
21543 QCPAxis *keyAxis = mKeyAxis.data();
21544 QCPAxis *valueAxis = mValueAxis.data();
21545 if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return result; }
21546
21547 result.resize(data.size()*2);
21548
21549 // transform data points to pixels:
21550 if (keyAxis->orientation() == Qt::Vertical)
21551 {
21552 for (int i=0; i<data.size(); ++i)
21553 {
21554 const QCPGraphData &current = data.at(i);
21555 if (!qIsNaN(current.value))
21556 {
21557 const double key = keyAxis->coordToPixel(current.key);
21558 result[i*2+0].setX(valueAxis->coordToPixel(0));
21559 result[i*2+0].setY(key);
21560 result[i*2+1].setX(valueAxis->coordToPixel(current.value));
21561 result[i*2+1].setY(key);
21562 } else
21563 {
21564 result[i*2+0] = QPointF(0, 0);
21565 result[i*2+1] = QPointF(0, 0);
21566 }
21567 }
21568 } else // key axis is horizontal
21569 {
21570 for (int i=0; i<data.size(); ++i)
21571 {
21572 const QCPGraphData &current = data.at(i);
21573 if (!qIsNaN(current.value))
21574 {
21575 const double key = keyAxis->coordToPixel(data.at(i).key);
21576 result[i*2+0].setX(key);
21577 result[i*2+0].setY(valueAxis->coordToPixel(0));
21578 result[i*2+1].setX(key);
21579 result[i*2+1].setY(valueAxis->coordToPixel(data.at(i).value));
21580 } else
21581 {
21582 result[i*2+0] = QPointF(0, 0);
21583 result[i*2+1] = QPointF(0, 0);
21584 }
21585 }
21586 }
21587 return result;
21588}
21589
21590/*! \internal
21591
21592 Draws the fill of the graph using the specified \a painter, with the currently set brush.
21593
21594 Depending on whether a normal fill or a channel fill (\ref setChannelFillGraph) is needed, \ref
21595 getFillPolygon or \ref getChannelFillPolygon are used to find the according fill polygons.
21596
21597 In order to handle NaN Data points correctly (the fill needs to be split into disjoint areas),
21598 this method first determines a list of non-NaN segments with \ref getNonNanSegments, on which to
21599 operate. In the channel fill case, \ref getOverlappingSegments is used to consolidate the non-NaN
21600 segments of the two involved graphs, before passing the overlapping pairs to \ref
21601 getChannelFillPolygon.
21602
21603 Pass the points of this graph's line as \a lines, in pixel coordinates.
21604
21605 \see drawLinePlot, drawImpulsePlot, drawScatterPlot
21606*/
21608{
21609 if (mLineStyle == lsImpulse) return; // fill doesn't make sense for impulse plot
21610 if (painter->brush().style() == Qt::NoBrush || painter->brush().color().alpha() == 0) return;
21611
21613 const QVector<QCPDataRange> segments = getNonNanSegments(lines, keyAxis()->orientation());
21614 if (!mChannelFillGraph)
21615 {
21616 // draw base fill under graph, fill goes all the way to the zero-value-line:
21617 foreach (QCPDataRange segment, segments)
21618 painter->drawPolygon(getFillPolygon(lines, segment));
21619 } else
21620 {
21621 // draw fill between this graph and mChannelFillGraph:
21622 QVector<QPointF> otherLines;
21623 mChannelFillGraph->getLines(&otherLines, QCPDataRange(0, mChannelFillGraph->dataCount()));
21624 if (!otherLines.isEmpty())
21625 {
21626 QVector<QCPDataRange> otherSegments = getNonNanSegments(&otherLines, mChannelFillGraph->keyAxis()->orientation());
21627 QVector<QPair<QCPDataRange, QCPDataRange> > segmentPairs = getOverlappingSegments(segments, lines, otherSegments, &otherLines);
21628 for (int i=0; i<segmentPairs.size(); ++i)
21629 painter->drawPolygon(getChannelFillPolygon(lines, segmentPairs.at(i).first, &otherLines, segmentPairs.at(i).second));
21630 }
21631 }
21632}
21633
21634/*! \internal
21635
21636 Draws scatter symbols at every point passed in \a scatters, given in pixel coordinates. The
21637 scatters will be drawn with \a painter and have the appearance as specified in \a style.
21638
21639 \see drawLinePlot, drawImpulsePlot
21640*/
21641void QCPGraph::drawScatterPlot(QCPPainter *painter, const QVector<QPointF> &scatters, const QCPScatterStyle &style) const
21642{
21644 style.applyTo(painter, mPen);
21645 foreach (const QPointF &scatter, scatters)
21646 style.drawShape(painter, scatter.x(), scatter.y());
21647}
21648
21649/*! \internal
21650
21651 Draws lines between the points in \a lines, given in pixel coordinates.
21652
21653 \see drawScatterPlot, drawImpulsePlot, QCPAbstractPlottable1D::drawPolyline
21654*/
21655void QCPGraph::drawLinePlot(QCPPainter *painter, const QVector<QPointF> &lines) const
21656{
21657 if (painter->pen().style() != Qt::NoPen && painter->pen().color().alpha() != 0)
21658 {
21660 drawPolyline(painter, lines);
21661 }
21662}
21663
21664/*! \internal
21665
21666 Draws impulses from the provided data, i.e. it connects all line pairs in \a lines, given in
21667 pixel coordinates. The \a lines necessary for impulses are generated by \ref dataToImpulseLines
21668 from the regular graph data points.
21669
21670 \see drawLinePlot, drawScatterPlot
21671*/
21673{
21674 if (painter->pen().style() != Qt::NoPen && painter->pen().color().alpha() != 0)
21675 {
21677 QPen oldPen = painter->pen();
21678 QPen newPen = painter->pen();
21679 newPen.setCapStyle(Qt::FlatCap); // so impulse line doesn't reach beyond zero-line
21680 painter->setPen(newPen);
21681 painter->drawLines(lines);
21682 painter->setPen(oldPen);
21683 }
21684}
21685
21686/*! \internal
21687
21688 Returns via \a lineData the data points that need to be visualized for this graph when plotting
21689 graph lines, taking into consideration the currently visible axis ranges and, if \ref
21690 setAdaptiveSampling is enabled, local point densities. The considered data can be restricted
21691 further by \a begin and \a end, e.g. to only plot a certain segment of the data (see \ref
21692 getDataSegments).
21693
21694 This method is used by \ref getLines to retrieve the basic working set of data.
21695
21696 \see getOptimizedScatterData
21697*/
21699{
21700 if (!lineData) return;
21701 QCPAxis *keyAxis = mKeyAxis.data();
21702 QCPAxis *valueAxis = mValueAxis.data();
21703 if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
21704 if (begin == end) return;
21705
21706 int dataCount = int(end-begin);
21707 int maxCount = (std::numeric_limits<int>::max)();
21708 if (mAdaptiveSampling)
21709 {
21710 double keyPixelSpan = qAbs(keyAxis->coordToPixel(begin->key)-keyAxis->coordToPixel((end-1)->key));
21711 if (2*keyPixelSpan+2 < static_cast<double>((std::numeric_limits<int>::max)()))
21712 maxCount = int(2*keyPixelSpan+2);
21713 }
21714
21715 if (mAdaptiveSampling && dataCount >= maxCount) // use adaptive sampling only if there are at least two points per pixel on average
21716 {
21718 double minValue = it->value;
21719 double maxValue = it->value;
21720 QCPGraphDataContainer::const_iterator currentIntervalFirstPoint = it;
21721 int reversedFactor = keyAxis->pixelOrientation(); // is used to calculate keyEpsilon pixel into the correct direction
21722 int reversedRound = reversedFactor==-1 ? 1 : 0; // is used to switch between floor (normal) and ceil (reversed) rounding of currentIntervalStartKey
21723 double currentIntervalStartKey = keyAxis->pixelToCoord(int(keyAxis->coordToPixel(begin->key)+reversedRound));
21724 double lastIntervalEndKey = currentIntervalStartKey;
21725 double keyEpsilon = qAbs(currentIntervalStartKey-keyAxis->pixelToCoord(keyAxis->coordToPixel(currentIntervalStartKey)+1.0*reversedFactor)); // interval of one pixel on screen when mapped to plot key coordinates
21726 bool keyEpsilonVariable = keyAxis->scaleType() == QCPAxis::stLogarithmic; // indicates whether keyEpsilon needs to be updated after every interval (for log axes)
21727 int intervalDataCount = 1;
21728 ++it; // advance iterator to second data point because adaptive sampling works in 1 point retrospect
21729 while (it != end)
21730 {
21731 if (it->key < currentIntervalStartKey+keyEpsilon) // data point is still within same pixel, so skip it and expand value span of this cluster if necessary
21732 {
21733 if (it->value < minValue)
21734 minValue = it->value;
21735 else if (it->value > maxValue)
21736 maxValue = it->value;
21737 ++intervalDataCount;
21738 } else // new pixel interval started
21739 {
21740 if (intervalDataCount >= 2) // last pixel had multiple data points, consolidate them to a cluster
21741 {
21742 if (lastIntervalEndKey < currentIntervalStartKey-keyEpsilon) // last point is further away, so first point of this cluster must be at a real data point
21743 lineData->append(QCPGraphData(currentIntervalStartKey+keyEpsilon*0.2, currentIntervalFirstPoint->value));
21744 lineData->append(QCPGraphData(currentIntervalStartKey+keyEpsilon*0.25, minValue));
21745 lineData->append(QCPGraphData(currentIntervalStartKey+keyEpsilon*0.75, maxValue));
21746 if (it->key > currentIntervalStartKey+keyEpsilon*2) // new pixel started further away from previous cluster, so make sure the last point of the cluster is at a real data point
21747 lineData->append(QCPGraphData(currentIntervalStartKey+keyEpsilon*0.8, (it-1)->value));
21748 } else
21749 lineData->append(QCPGraphData(currentIntervalFirstPoint->key, currentIntervalFirstPoint->value));
21750 lastIntervalEndKey = (it-1)->key;
21751 minValue = it->value;
21752 maxValue = it->value;
21753 currentIntervalFirstPoint = it;
21754 currentIntervalStartKey = keyAxis->pixelToCoord(int(keyAxis->coordToPixel(it->key)+reversedRound));
21755 if (keyEpsilonVariable)
21756 keyEpsilon = qAbs(currentIntervalStartKey-keyAxis->pixelToCoord(keyAxis->coordToPixel(currentIntervalStartKey)+1.0*reversedFactor));
21757 intervalDataCount = 1;
21758 }
21759 ++it;
21760 }
21761 // handle last interval:
21762 if (intervalDataCount >= 2) // last pixel had multiple data points, consolidate them to a cluster
21763 {
21764 if (lastIntervalEndKey < currentIntervalStartKey-keyEpsilon) // last point wasn't a cluster, so first point of this cluster must be at a real data point
21765 lineData->append(QCPGraphData(currentIntervalStartKey+keyEpsilon*0.2, currentIntervalFirstPoint->value));
21766 lineData->append(QCPGraphData(currentIntervalStartKey+keyEpsilon*0.25, minValue));
21767 lineData->append(QCPGraphData(currentIntervalStartKey+keyEpsilon*0.75, maxValue));
21768 } else
21769 lineData->append(QCPGraphData(currentIntervalFirstPoint->key, currentIntervalFirstPoint->value));
21770
21771 } else // don't use adaptive sampling algorithm, transfer points one-to-one from the data container into the output
21772 {
21773 lineData->resize(dataCount);
21774 std::copy(begin, end, lineData->begin());
21775 }
21776}
21777
21778/*! \internal
21779
21780 Returns via \a scatterData the data points that need to be visualized for this graph when
21781 plotting scatter points, taking into consideration the currently visible axis ranges and, if \ref
21782 setAdaptiveSampling is enabled, local point densities. The considered data can be restricted
21783 further by \a begin and \a end, e.g. to only plot a certain segment of the data (see \ref
21784 getDataSegments).
21785
21786 This method is used by \ref getScatters to retrieve the basic working set of data.
21787
21788 \see getOptimizedLineData
21789*/
21791{
21792 if (!scatterData) return;
21793 QCPAxis *keyAxis = mKeyAxis.data();
21794 QCPAxis *valueAxis = mValueAxis.data();
21795 if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
21796
21797 const int scatterModulo = mScatterSkip+1;
21798 const bool doScatterSkip = mScatterSkip > 0;
21799 int beginIndex = int(begin-mDataContainer->constBegin());
21800 int endIndex = int(end-mDataContainer->constBegin());
21801 while (doScatterSkip && begin != end && beginIndex % scatterModulo != 0) // advance begin iterator to first non-skipped scatter
21802 {
21803 ++beginIndex;
21804 ++begin;
21805 }
21806 if (begin == end) return;
21807 int dataCount = int(end-begin);
21808 int maxCount = (std::numeric_limits<int>::max)();
21809 if (mAdaptiveSampling)
21810 {
21811 int keyPixelSpan = int(qAbs(keyAxis->coordToPixel(begin->key)-keyAxis->coordToPixel((end-1)->key)));
21812 maxCount = 2*keyPixelSpan+2;
21813 }
21814
21815 if (mAdaptiveSampling && dataCount >= maxCount) // use adaptive sampling only if there are at least two points per pixel on average
21816 {
21817 double valueMaxRange = valueAxis->range().upper;
21818 double valueMinRange = valueAxis->range().lower;
21820 int itIndex = int(beginIndex);
21821 double minValue = it->value;
21822 double maxValue = it->value;
21825 QCPGraphDataContainer::const_iterator currentIntervalStart = it;
21826 int reversedFactor = keyAxis->pixelOrientation(); // is used to calculate keyEpsilon pixel into the correct direction
21827 int reversedRound = reversedFactor==-1 ? 1 : 0; // is used to switch between floor (normal) and ceil (reversed) rounding of currentIntervalStartKey
21828 double currentIntervalStartKey = keyAxis->pixelToCoord(int(keyAxis->coordToPixel(begin->key)+reversedRound));
21829 double keyEpsilon = qAbs(currentIntervalStartKey-keyAxis->pixelToCoord(keyAxis->coordToPixel(currentIntervalStartKey)+1.0*reversedFactor)); // interval of one pixel on screen when mapped to plot key coordinates
21830 bool keyEpsilonVariable = keyAxis->scaleType() == QCPAxis::stLogarithmic; // indicates whether keyEpsilon needs to be updated after every interval (for log axes)
21831 int intervalDataCount = 1;
21832 // advance iterator to second (non-skipped) data point because adaptive sampling works in 1 point retrospect:
21833 if (!doScatterSkip)
21834 ++it;
21835 else
21836 {
21837 itIndex += scatterModulo;
21838 if (itIndex < endIndex) // make sure we didn't jump over end
21839 it += scatterModulo;
21840 else
21841 {
21842 it = end;
21843 itIndex = endIndex;
21844 }
21845 }
21846 // main loop over data points:
21847 while (it != end)
21848 {
21849 if (it->key < currentIntervalStartKey+keyEpsilon) // data point is still within same pixel, so skip it and expand value span of this pixel if necessary
21850 {
21851 if (it->value < minValue && it->value > valueMinRange && it->value < valueMaxRange)
21852 {
21853 minValue = it->value;
21854 minValueIt = it;
21855 } else if (it->value > maxValue && it->value > valueMinRange && it->value < valueMaxRange)
21856 {
21857 maxValue = it->value;
21858 maxValueIt = it;
21859 }
21860 ++intervalDataCount;
21861 } else // new pixel started
21862 {
21863 if (intervalDataCount >= 2) // last pixel had multiple data points, consolidate them
21864 {
21865 // determine value pixel span and add as many points in interval to maintain certain vertical data density (this is specific to scatter plot):
21866 double valuePixelSpan = qAbs(valueAxis->coordToPixel(minValue)-valueAxis->coordToPixel(maxValue));
21867 int dataModulo = qMax(1, qRound(intervalDataCount/(valuePixelSpan/4.0))); // approximately every 4 value pixels one data point on average
21868 QCPGraphDataContainer::const_iterator intervalIt = currentIntervalStart;
21869 int c = 0;
21870 while (intervalIt != it)
21871 {
21872 if ((c % dataModulo == 0 || intervalIt == minValueIt || intervalIt == maxValueIt) && intervalIt->value > valueMinRange && intervalIt->value < valueMaxRange)
21873 scatterData->append(*intervalIt);
21874 ++c;
21875 if (!doScatterSkip)
21876 ++intervalIt;
21877 else
21878 intervalIt += scatterModulo; // since we know indices of "currentIntervalStart", "intervalIt" and "it" are multiples of scatterModulo, we can't accidentally jump over "it" here
21879 }
21880 } else if (currentIntervalStart->value > valueMinRange && currentIntervalStart->value < valueMaxRange)
21881 scatterData->append(*currentIntervalStart);
21882 minValue = it->value;
21883 maxValue = it->value;
21884 currentIntervalStart = it;
21885 currentIntervalStartKey = keyAxis->pixelToCoord(int(keyAxis->coordToPixel(it->key)+reversedRound));
21886 if (keyEpsilonVariable)
21887 keyEpsilon = qAbs(currentIntervalStartKey-keyAxis->pixelToCoord(keyAxis->coordToPixel(currentIntervalStartKey)+1.0*reversedFactor));
21888 intervalDataCount = 1;
21889 }
21890 // advance to next data point:
21891 if (!doScatterSkip)
21892 ++it;
21893 else
21894 {
21895 itIndex += scatterModulo;
21896 if (itIndex < endIndex) // make sure we didn't jump over end
21897 it += scatterModulo;
21898 else
21899 {
21900 it = end;
21901 itIndex = endIndex;
21902 }
21903 }
21904 }
21905 // handle last interval:
21906 if (intervalDataCount >= 2) // last pixel had multiple data points, consolidate them
21907 {
21908 // determine value pixel span and add as many points in interval to maintain certain vertical data density (this is specific to scatter plot):
21909 double valuePixelSpan = qAbs(valueAxis->coordToPixel(minValue)-valueAxis->coordToPixel(maxValue));
21910 int dataModulo = qMax(1, qRound(intervalDataCount/(valuePixelSpan/4.0))); // approximately every 4 value pixels one data point on average
21911 QCPGraphDataContainer::const_iterator intervalIt = currentIntervalStart;
21912 int intervalItIndex = int(intervalIt-mDataContainer->constBegin());
21913 int c = 0;
21914 while (intervalIt != it)
21915 {
21916 if ((c % dataModulo == 0 || intervalIt == minValueIt || intervalIt == maxValueIt) && intervalIt->value > valueMinRange && intervalIt->value < valueMaxRange)
21917 scatterData->append(*intervalIt);
21918 ++c;
21919 if (!doScatterSkip)
21920 ++intervalIt;
21921 else // here we can't guarantee that adding scatterModulo doesn't exceed "it" (because "it" is equal to "end" here, and "end" isn't scatterModulo-aligned), so check via index comparison:
21922 {
21923 intervalItIndex += scatterModulo;
21924 if (intervalItIndex < itIndex)
21925 intervalIt += scatterModulo;
21926 else
21927 {
21928 intervalIt = it;
21929 intervalItIndex = itIndex;
21930 }
21931 }
21932 }
21933 } else if (currentIntervalStart->value > valueMinRange && currentIntervalStart->value < valueMaxRange)
21934 scatterData->append(*currentIntervalStart);
21935
21936 } else // don't use adaptive sampling algorithm, transfer points one-to-one from the data container into the output
21937 {
21939 int itIndex = beginIndex;
21940 scatterData->reserve(dataCount);
21941 while (it != end)
21942 {
21943 scatterData->append(*it);
21944 // advance to next data point:
21945 if (!doScatterSkip)
21946 ++it;
21947 else
21948 {
21949 itIndex += scatterModulo;
21950 if (itIndex < endIndex)
21951 it += scatterModulo;
21952 else
21953 {
21954 it = end;
21955 itIndex = endIndex;
21956 }
21957 }
21958 }
21959 }
21960}
21961
21962/*!
21963 This method outputs the currently visible data range via \a begin and \a end. The returned range
21964 will also never exceed \a rangeRestriction.
21965
21966 This method takes into account that the drawing of data lines at the axis rect border always
21967 requires the points just outside the visible axis range. So \a begin and \a end may actually
21968 indicate a range that contains one additional data point to the left and right of the visible
21969 axis range.
21970*/
21972{
21973 if (rangeRestriction.isEmpty())
21974 {
21975 end = mDataContainer->constEnd();
21976 begin = end;
21977 } else
21978 {
21979 QCPAxis *keyAxis = mKeyAxis.data();
21980 QCPAxis *valueAxis = mValueAxis.data();
21981 if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
21982 // get visible data range:
21983 begin = mDataContainer->findBegin(keyAxis->range().lower);
21984 end = mDataContainer->findEnd(keyAxis->range().upper);
21985 // limit lower/upperEnd to rangeRestriction:
21986 mDataContainer->limitIteratorsToDataRange(begin, end, rangeRestriction); // this also ensures rangeRestriction outside data bounds doesn't break anything
21987 }
21988}
21989
21990/*! \internal
21991
21992 This method goes through the passed points in \a lineData and returns a list of the segments
21993 which don't contain NaN data points.
21994
21995 \a keyOrientation defines whether the \a x or \a y member of the passed QPointF is used to check
21996 for NaN. If \a keyOrientation is \c Qt::Horizontal, the \a y member is checked, if it is \c
21997 Qt::Vertical, the \a x member is checked.
21998
21999 \see getOverlappingSegments, drawFill
22000*/
22002{
22003 QVector<QCPDataRange> result;
22004 const int n = lineData->size();
22005
22006 QCPDataRange currentSegment(-1, -1);
22007 int i = 0;
22008
22009 if (keyOrientation == Qt::Horizontal)
22010 {
22011 while (i < n)
22012 {
22013 while (i < n && qIsNaN(lineData->at(i).y())) // seek next non-NaN data point
22014 ++i;
22015 if (i == n)
22016 break;
22017 currentSegment.setBegin(i++);
22018 while (i < n && !qIsNaN(lineData->at(i).y())) // seek next NaN data point or end of data
22019 ++i;
22020 currentSegment.setEnd(i++);
22021 result.append(currentSegment);
22022 }
22023 } else // keyOrientation == Qt::Vertical
22024 {
22025 while (i < n)
22026 {
22027 while (i < n && qIsNaN(lineData->at(i).x())) // seek next non-NaN data point
22028 ++i;
22029 if (i == n)
22030 break;
22031 currentSegment.setBegin(i++);
22032 while (i < n && !qIsNaN(lineData->at(i).x())) // seek next NaN data point or end of data
22033 ++i;
22034 currentSegment.setEnd(i++);
22035 result.append(currentSegment);
22036 }
22037 }
22038 return result;
22039}
22040
22041/*! \internal
22042
22043 This method takes two segment lists (e.g. created by \ref getNonNanSegments) \a thisSegments and
22044 \a otherSegments, and their associated point data \a thisData and \a otherData.
22045
22046 It returns all pairs of segments (the first from \a thisSegments, the second from \a
22047 otherSegments), which overlap in plot coordinates.
22048
22049 This method is useful in the case of a channel fill between two graphs, when only those non-NaN
22050 segments which actually overlap in their key coordinate shall be considered for drawing a channel
22051 fill polygon.
22052
22053 It is assumed that the passed segments in \a thisSegments are ordered ascending by index, and
22054 that the segments don't overlap themselves. The same is assumed for the segments in \a
22055 otherSegments. This is fulfilled when the segments are obtained via \ref getNonNanSegments.
22056
22057 \see getNonNanSegments, segmentsIntersect, drawFill, getChannelFillPolygon
22058*/
22060{
22062 if (thisData->isEmpty() || otherData->isEmpty() || thisSegments.isEmpty() || otherSegments.isEmpty())
22063 return result;
22064
22065 int thisIndex = 0;
22066 int otherIndex = 0;
22067 const bool verticalKey = mKeyAxis->orientation() == Qt::Vertical;
22068 while (thisIndex < thisSegments.size() && otherIndex < otherSegments.size())
22069 {
22070 if (thisSegments.at(thisIndex).size() < 2) // segments with fewer than two points won't have a fill anyhow
22071 {
22072 ++thisIndex;
22073 continue;
22074 }
22075 if (otherSegments.at(otherIndex).size() < 2) // segments with fewer than two points won't have a fill anyhow
22076 {
22077 ++otherIndex;
22078 continue;
22079 }
22080 double thisLower, thisUpper, otherLower, otherUpper;
22081 if (!verticalKey)
22082 {
22083 thisLower = thisData->at(thisSegments.at(thisIndex).begin()).x();
22084 thisUpper = thisData->at(thisSegments.at(thisIndex).end()-1).x();
22085 otherLower = otherData->at(otherSegments.at(otherIndex).begin()).x();
22086 otherUpper = otherData->at(otherSegments.at(otherIndex).end()-1).x();
22087 } else
22088 {
22089 thisLower = thisData->at(thisSegments.at(thisIndex).begin()).y();
22090 thisUpper = thisData->at(thisSegments.at(thisIndex).end()-1).y();
22091 otherLower = otherData->at(otherSegments.at(otherIndex).begin()).y();
22092 otherUpper = otherData->at(otherSegments.at(otherIndex).end()-1).y();
22093 }
22094
22095 int bPrecedence;
22096 if (segmentsIntersect(thisLower, thisUpper, otherLower, otherUpper, bPrecedence))
22097 result.append(QPair<QCPDataRange, QCPDataRange>(thisSegments.at(thisIndex), otherSegments.at(otherIndex)));
22098
22099 if (bPrecedence <= 0) // otherSegment doesn't reach as far as thisSegment, so continue with next otherSegment, keeping current thisSegment
22100 ++otherIndex;
22101 else // otherSegment reaches further than thisSegment, so continue with next thisSegment, keeping current otherSegment
22102 ++thisIndex;
22103 }
22104
22105 return result;
22106}
22107
22108/*! \internal
22109
22110 Returns whether the segments defined by the coordinates (aLower, aUpper) and (bLower, bUpper)
22111 have overlap.
22112
22113 The output parameter \a bPrecedence indicates whether the \a b segment reaches farther than the
22114 \a a segment or not. If \a bPrecedence returns 1, segment \a b reaches the farthest to higher
22115 coordinates (i.e. bUpper > aUpper). If it returns -1, segment \a a reaches the farthest. Only if
22116 both segment's upper bounds are identical, 0 is returned as \a bPrecedence.
22117
22118 It is assumed that the lower bounds always have smaller or equal values than the upper bounds.
22119
22120 \see getOverlappingSegments
22121*/
22122bool QCPGraph::segmentsIntersect(double aLower, double aUpper, double bLower, double bUpper, int &bPrecedence) const
22123{
22124 bPrecedence = 0;
22125 if (aLower > bUpper)
22126 {
22127 bPrecedence = -1;
22128 return false;
22129 } else if (bLower > aUpper)
22130 {
22131 bPrecedence = 1;
22132 return false;
22133 } else
22134 {
22135 if (aUpper > bUpper)
22136 bPrecedence = -1;
22137 else if (aUpper < bUpper)
22138 bPrecedence = 1;
22139
22140 return true;
22141 }
22142}
22143
22144/*! \internal
22145
22146 Returns the point which closes the fill polygon on the zero-value-line parallel to the key axis.
22147 The logarithmic axis scale case is a bit special, since the zero-value-line in pixel coordinates
22148 is in positive or negative infinity. So this case is handled separately by just closing the fill
22149 polygon on the axis which lies in the direction towards the zero value.
22150
22151 \a matchingDataPoint will provide the key (in pixels) of the returned point. Depending on whether
22152 the key axis of this graph is horizontal or vertical, \a matchingDataPoint will provide the x or
22153 y value of the returned point, respectively.
22154*/
22156{
22157 QCPAxis *keyAxis = mKeyAxis.data();
22158 QCPAxis *valueAxis = mValueAxis.data();
22159 if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return {}; }
22160
22161 QPointF result;
22162 if (valueAxis->scaleType() == QCPAxis::stLinear)
22163 {
22164 if (keyAxis->orientation() == Qt::Horizontal)
22165 {
22166 result.setX(matchingDataPoint.x());
22167 result.setY(valueAxis->coordToPixel(0));
22168 } else // keyAxis->orientation() == Qt::Vertical
22169 {
22170 result.setX(valueAxis->coordToPixel(0));
22171 result.setY(matchingDataPoint.y());
22172 }
22173 } else // valueAxis->mScaleType == QCPAxis::stLogarithmic
22174 {
22175 // In logarithmic scaling we can't just draw to value 0 so we just fill all the way
22176 // to the axis which is in the direction towards 0
22177 if (keyAxis->orientation() == Qt::Vertical)
22178 {
22179 if ((valueAxis->range().upper < 0 && !valueAxis->rangeReversed()) ||
22180 (valueAxis->range().upper > 0 && valueAxis->rangeReversed())) // if range is negative, zero is on opposite side of key axis
22181 result.setX(keyAxis->axisRect()->right());
22182 else
22183 result.setX(keyAxis->axisRect()->left());
22184 result.setY(matchingDataPoint.y());
22185 } else if (keyAxis->axisType() == QCPAxis::atTop || keyAxis->axisType() == QCPAxis::atBottom)
22186 {
22187 result.setX(matchingDataPoint.x());
22188 if ((valueAxis->range().upper < 0 && !valueAxis->rangeReversed()) ||
22189 (valueAxis->range().upper > 0 && valueAxis->rangeReversed())) // if range is negative, zero is on opposite side of key axis
22190 result.setY(keyAxis->axisRect()->top());
22191 else
22192 result.setY(keyAxis->axisRect()->bottom());
22193 }
22194 }
22195 return result;
22196}
22197
22198/*! \internal
22199
22200 Returns the polygon needed for drawing normal fills between this graph and the key axis.
22201
22202 Pass the graph's data points (in pixel coordinates) as \a lineData, and specify the \a segment
22203 which shall be used for the fill. The collection of \a lineData points described by \a segment
22204 must not contain NaN data points (see \ref getNonNanSegments).
22205
22206 The returned fill polygon will be closed at the key axis (the zero-value line) for linear value
22207 axes. For logarithmic value axes the polygon will reach just beyond the corresponding axis rect
22208 side (see \ref getFillBasePoint).
22209
22210 For increased performance (due to implicit sharing), keep the returned QPolygonF const.
22211
22212 \see drawFill, getNonNanSegments
22213*/
22215{
22216 if (segment.size() < 2)
22217 return QPolygonF();
22218 QPolygonF result(segment.size()+2);
22219
22220 result[0] = getFillBasePoint(lineData->at(segment.begin()));
22221 std::copy(lineData->constBegin()+segment.begin(), lineData->constBegin()+segment.end(), result.begin()+1);
22222 result[result.size()-1] = getFillBasePoint(lineData->at(segment.end()-1));
22223
22224 return result;
22225}
22226
22227/*! \internal
22228
22229 Returns the polygon needed for drawing (partial) channel fills between this graph and the graph
22230 specified by \ref setChannelFillGraph.
22231
22232 The data points of this graph are passed as pixel coordinates via \a thisData, the data of the
22233 other graph as \a otherData. The returned polygon will be calculated for the specified data
22234 segments \a thisSegment and \a otherSegment, pertaining to the respective \a thisData and \a
22235 otherData, respectively.
22236
22237 The passed \a thisSegment and \a otherSegment should correspond to the segment pairs returned by
22238 \ref getOverlappingSegments, to make sure only segments that actually have key coordinate overlap
22239 need to be processed here.
22240
22241 For increased performance due to implicit sharing, keep the returned QPolygonF const.
22242
22243 \see drawFill, getOverlappingSegments, getNonNanSegments
22244*/
22245const QPolygonF QCPGraph::getChannelFillPolygon(const QVector<QPointF> *thisData, QCPDataRange thisSegment, const QVector<QPointF> *otherData, QCPDataRange otherSegment) const
22246{
22247 if (!mChannelFillGraph)
22248 return QPolygonF();
22249
22250 QCPAxis *keyAxis = mKeyAxis.data();
22251 QCPAxis *valueAxis = mValueAxis.data();
22252 if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return QPolygonF(); }
22253 if (!mChannelFillGraph.data()->mKeyAxis) { qDebug() << Q_FUNC_INFO << "channel fill target key axis invalid"; return QPolygonF(); }
22254
22255 if (mChannelFillGraph.data()->mKeyAxis.data()->orientation() != keyAxis->orientation())
22256 return QPolygonF(); // don't have same axis orientation, can't fill that (Note: if keyAxis fits, valueAxis will fit too, because it's always orthogonal to keyAxis)
22257
22258 if (thisData->isEmpty()) return QPolygonF();
22259 QVector<QPointF> thisSegmentData(thisSegment.size());
22260 QVector<QPointF> otherSegmentData(otherSegment.size());
22261 std::copy(thisData->constBegin()+thisSegment.begin(), thisData->constBegin()+thisSegment.end(), thisSegmentData.begin());
22262 std::copy(otherData->constBegin()+otherSegment.begin(), otherData->constBegin()+otherSegment.end(), otherSegmentData.begin());
22263 // pointers to be able to swap them, depending which data range needs cropping:
22264 QVector<QPointF> *staticData = &thisSegmentData;
22265 QVector<QPointF> *croppedData = &otherSegmentData;
22266
22267 // crop both vectors to ranges in which the keys overlap (which coord is key, depends on axisType):
22268 if (keyAxis->orientation() == Qt::Horizontal)
22269 {
22270 // x is key
22271 // crop lower bound:
22272 if (staticData->first().x() < croppedData->first().x()) // other one must be cropped
22273 qSwap(staticData, croppedData);
22274 const int lowBound = findIndexBelowX(croppedData, staticData->first().x());
22275 if (lowBound == -1) return QPolygonF(); // key ranges have no overlap
22276 croppedData->remove(0, lowBound);
22277 // set lowest point of cropped data to fit exactly key position of first static data point via linear interpolation:
22278 if (croppedData->size() < 2) return QPolygonF(); // need at least two points for interpolation
22279 double slope;
22280 if (!qFuzzyCompare(croppedData->at(1).x(), croppedData->at(0).x()))
22281 slope = (croppedData->at(1).y()-croppedData->at(0).y())/(croppedData->at(1).x()-croppedData->at(0).x());
22282 else
22283 slope = 0;
22284 (*croppedData)[0].setY(croppedData->at(0).y()+slope*(staticData->first().x()-croppedData->at(0).x()));
22285 (*croppedData)[0].setX(staticData->first().x());
22286
22287 // crop upper bound:
22288 if (staticData->last().x() > croppedData->last().x()) // other one must be cropped
22289 qSwap(staticData, croppedData);
22290 int highBound = findIndexAboveX(croppedData, staticData->last().x());
22291 if (highBound == -1) return QPolygonF(); // key ranges have no overlap
22292 croppedData->remove(highBound+1, croppedData->size()-(highBound+1));
22293 // set highest point of cropped data to fit exactly key position of last static data point via linear interpolation:
22294 if (croppedData->size() < 2) return QPolygonF(); // need at least two points for interpolation
22295 const int li = croppedData->size()-1; // last index
22296 if (!qFuzzyCompare(croppedData->at(li).x(), croppedData->at(li-1).x()))
22297 slope = (croppedData->at(li).y()-croppedData->at(li-1).y())/(croppedData->at(li).x()-croppedData->at(li-1).x());
22298 else
22299 slope = 0;
22300 (*croppedData)[li].setY(croppedData->at(li-1).y()+slope*(staticData->last().x()-croppedData->at(li-1).x()));
22301 (*croppedData)[li].setX(staticData->last().x());
22302 } else // mKeyAxis->orientation() == Qt::Vertical
22303 {
22304 // y is key
22305 // crop lower bound:
22306 if (staticData->first().y() < croppedData->first().y()) // other one must be cropped
22307 qSwap(staticData, croppedData);
22308 int lowBound = findIndexBelowY(croppedData, staticData->first().y());
22309 if (lowBound == -1) return QPolygonF(); // key ranges have no overlap
22310 croppedData->remove(0, lowBound);
22311 // set lowest point of cropped data to fit exactly key position of first static data point via linear interpolation:
22312 if (croppedData->size() < 2) return QPolygonF(); // need at least two points for interpolation
22313 double slope;
22314 if (!qFuzzyCompare(croppedData->at(1).y(), croppedData->at(0).y())) // avoid division by zero in step plots
22315 slope = (croppedData->at(1).x()-croppedData->at(0).x())/(croppedData->at(1).y()-croppedData->at(0).y());
22316 else
22317 slope = 0;
22318 (*croppedData)[0].setX(croppedData->at(0).x()+slope*(staticData->first().y()-croppedData->at(0).y()));
22319 (*croppedData)[0].setY(staticData->first().y());
22320
22321 // crop upper bound:
22322 if (staticData->last().y() > croppedData->last().y()) // other one must be cropped
22323 qSwap(staticData, croppedData);
22324 int highBound = findIndexAboveY(croppedData, staticData->last().y());
22325 if (highBound == -1) return QPolygonF(); // key ranges have no overlap
22326 croppedData->remove(highBound+1, croppedData->size()-(highBound+1));
22327 // set highest point of cropped data to fit exactly key position of last static data point via linear interpolation:
22328 if (croppedData->size() < 2) return QPolygonF(); // need at least two points for interpolation
22329 int li = croppedData->size()-1; // last index
22330 if (!qFuzzyCompare(croppedData->at(li).y(), croppedData->at(li-1).y())) // avoid division by zero in step plots
22331 slope = (croppedData->at(li).x()-croppedData->at(li-1).x())/(croppedData->at(li).y()-croppedData->at(li-1).y());
22332 else
22333 slope = 0;
22334 (*croppedData)[li].setX(croppedData->at(li-1).x()+slope*(staticData->last().y()-croppedData->at(li-1).y()));
22335 (*croppedData)[li].setY(staticData->last().y());
22336 }
22337
22338 // return joined:
22339 for (int i=otherSegmentData.size()-1; i>=0; --i) // insert reversed, otherwise the polygon will be twisted
22340 thisSegmentData << otherSegmentData.at(i);
22341 return QPolygonF(thisSegmentData);
22342}
22343
22344/*! \internal
22345
22346 Finds the smallest index of \a data, whose points x value is just above \a x. Assumes x values in
22347 \a data points are ordered ascending, as is ensured by \ref getLines/\ref getScatters if the key
22348 axis is horizontal.
22349
22350 Used to calculate the channel fill polygon, see \ref getChannelFillPolygon.
22351*/
22352int QCPGraph::findIndexAboveX(const QVector<QPointF> *data, double x) const
22353{
22354 for (int i=data->size()-1; i>=0; --i)
22355 {
22356 if (data->at(i).x() < x)
22357 {
22358 if (i<data->size()-1)
22359 return i+1;
22360 else
22361 return data->size()-1;
22362 }
22363 }
22364 return -1;
22365}
22366
22367/*! \internal
22368
22369 Finds the highest index of \a data, whose points x value is just below \a x. Assumes x values in
22370 \a data points are ordered ascending, as is ensured by \ref getLines/\ref getScatters if the key
22371 axis is horizontal.
22372
22373 Used to calculate the channel fill polygon, see \ref getChannelFillPolygon.
22374*/
22375int QCPGraph::findIndexBelowX(const QVector<QPointF> *data, double x) const
22376{
22377 for (int i=0; i<data->size(); ++i)
22378 {
22379 if (data->at(i).x() > x)
22380 {
22381 if (i>0)
22382 return i-1;
22383 else
22384 return 0;
22385 }
22386 }
22387 return -1;
22388}
22389
22390/*! \internal
22391
22392 Finds the smallest index of \a data, whose points y value is just above \a y. Assumes y values in
22393 \a data points are ordered ascending, as is ensured by \ref getLines/\ref getScatters if the key
22394 axis is vertical.
22395
22396 Used to calculate the channel fill polygon, see \ref getChannelFillPolygon.
22397*/
22398int QCPGraph::findIndexAboveY(const QVector<QPointF> *data, double y) const
22399{
22400 for (int i=data->size()-1; i>=0; --i)
22401 {
22402 if (data->at(i).y() < y)
22403 {
22404 if (i<data->size()-1)
22405 return i+1;
22406 else
22407 return data->size()-1;
22408 }
22409 }
22410 return -1;
22411}
22412
22413/*! \internal
22414
22415 Calculates the minimum distance in pixels the graph's representation has from the given \a
22416 pixelPoint. This is used to determine whether the graph was clicked or not, e.g. in \ref
22417 selectTest. The closest data point to \a pixelPoint is returned in \a closestData. Note that if
22418 the graph has a line representation, the returned distance may be smaller than the distance to
22419 the \a closestData point, since the distance to the graph line is also taken into account.
22420
22421 If either the graph has no data or if the line style is \ref lsNone and the scatter style's shape
22422 is \ref QCPScatterStyle::ssNone (i.e. there is no visual representation of the graph), returns -1.0.
22423*/
22424double QCPGraph::pointDistance(const QPointF &pixelPoint, QCPGraphDataContainer::const_iterator &closestData) const
22425{
22426 closestData = mDataContainer->constEnd();
22427 if (mDataContainer->isEmpty())
22428 return -1.0;
22429 if (mLineStyle == lsNone && mScatterStyle.isNone())
22430 return -1.0;
22431
22432 // calculate minimum distances to graph data points and find closestData iterator:
22433 double minDistSqr = (std::numeric_limits<double>::max)();
22434 // determine which key range comes into question, taking selection tolerance around pos into account:
22435 double posKeyMin, posKeyMax, dummy;
22436 pixelsToCoords(pixelPoint-QPointF(mParentPlot->selectionTolerance(), mParentPlot->selectionTolerance()), posKeyMin, dummy);
22437 pixelsToCoords(pixelPoint+QPointF(mParentPlot->selectionTolerance(), mParentPlot->selectionTolerance()), posKeyMax, dummy);
22438 if (posKeyMin > posKeyMax)
22439 qSwap(posKeyMin, posKeyMax);
22440 // iterate over found data points and then choose the one with the shortest distance to pos:
22441 QCPGraphDataContainer::const_iterator begin = mDataContainer->findBegin(posKeyMin, true);
22442 QCPGraphDataContainer::const_iterator end = mDataContainer->findEnd(posKeyMax, true);
22443 for (QCPGraphDataContainer::const_iterator it=begin; it!=end; ++it)
22444 {
22445 const double currentDistSqr = QCPVector2D(coordsToPixels(it->key, it->value)-pixelPoint).lengthSquared();
22446 if (currentDistSqr < minDistSqr)
22447 {
22448 minDistSqr = currentDistSqr;
22449 closestData = it;
22450 }
22451 }
22452
22453 // calculate distance to graph line if there is one (if so, will probably be smaller than distance to closest data point):
22454 if (mLineStyle != lsNone)
22455 {
22456 // line displayed, calculate distance to line segments:
22457 QVector<QPointF> lineData;
22458 getLines(&lineData, QCPDataRange(0, dataCount())); // don't limit data range further since with sharp data spikes, line segments may be closer to test point than segments with closer key coordinate
22459 QCPVector2D p(pixelPoint);
22460 const int step = mLineStyle==lsImpulse ? 2 : 1; // impulse plot differs from other line styles in that the lineData points are only pairwise connected
22461 for (int i=0; i<lineData.size()-1; i+=step)
22462 {
22463 const double currentDistSqr = p.distanceSquaredToLine(lineData.at(i), lineData.at(i+1));
22464 if (currentDistSqr < minDistSqr)
22465 minDistSqr = currentDistSqr;
22466 }
22467 }
22468
22469 return qSqrt(minDistSqr);
22470}
22471
22472/*! \internal
22473
22474 Finds the highest index of \a data, whose points y value is just below \a y. Assumes y values in
22475 \a data points are ordered ascending, as is ensured by \ref getLines/\ref getScatters if the key
22476 axis is vertical.
22477
22478 Used to calculate the channel fill polygon, see \ref getChannelFillPolygon.
22479*/
22480int QCPGraph::findIndexBelowY(const QVector<QPointF> *data, double y) const
22481{
22482 for (int i=0; i<data->size(); ++i)
22483 {
22484 if (data->at(i).y() > y)
22485 {
22486 if (i>0)
22487 return i-1;
22488 else
22489 return 0;
22490 }
22491 }
22492 return -1;
22493}
22494/* end of 'src/plottables/plottable-graph.cpp' */
22495
22496
22497/* including file 'src/plottables/plottable-curve.cpp' */
22498/* modified 2022-11-06T12:45:56, size 63851 */
22499
22500////////////////////////////////////////////////////////////////////////////////////////////////////
22501//////////////////// QCPCurveData
22502////////////////////////////////////////////////////////////////////////////////////////////////////
22503
22504/*! \class QCPCurveData
22505 \brief Holds the data of one single data point for QCPCurve.
22506
22507 The stored data is:
22508 \li \a t: the free ordering parameter of this curve point, like in the mathematical vector <em>(x(t), y(t))</em>. (This is the \a sortKey)
22509 \li \a key: coordinate on the key axis of this curve point (this is the \a mainKey)
22510 \li \a value: coordinate on the value axis of this curve point (this is the \a mainValue)
22511
22512 The container for storing multiple data points is \ref QCPCurveDataContainer. It is a typedef for
22513 \ref QCPDataContainer with \ref QCPCurveData as the DataType template parameter. See the
22514 documentation there for an explanation regarding the data type's generic methods.
22515
22516 \see QCPCurveDataContainer
22517*/
22518
22519/* start documentation of inline functions */
22520
22521/*! \fn double QCPCurveData::sortKey() const
22522
22523 Returns the \a t member of this data point.
22524
22525 For a general explanation of what this method is good for in the context of the data container,
22526 see the documentation of \ref QCPDataContainer.
22527*/
22528
22529/*! \fn static QCPCurveData QCPCurveData::fromSortKey(double sortKey)
22530
22531 Returns a data point with the specified \a sortKey (assigned to the data point's \a t member).
22532 All other members are set to zero.
22533
22534 For a general explanation of what this method is good for in the context of the data container,
22535 see the documentation of \ref QCPDataContainer.
22536*/
22537
22538/*! \fn static static bool QCPCurveData::sortKeyIsMainKey()
22539
22540 Since the member \a key is the data point key coordinate and the member \a t is the data ordering
22541 parameter, this method returns false.
22542
22543 For a general explanation of what this method is good for in the context of the data container,
22544 see the documentation of \ref QCPDataContainer.
22545*/
22546
22547/*! \fn double QCPCurveData::mainKey() const
22548
22549 Returns the \a key member of this data point.
22550
22551 For a general explanation of what this method is good for in the context of the data container,
22552 see the documentation of \ref QCPDataContainer.
22553*/
22554
22555/*! \fn double QCPCurveData::mainValue() const
22556
22557 Returns the \a value member of this data point.
22558
22559 For a general explanation of what this method is good for in the context of the data container,
22560 see the documentation of \ref QCPDataContainer.
22561*/
22562
22563/*! \fn QCPRange QCPCurveData::valueRange() const
22564
22565 Returns a QCPRange with both lower and upper boundary set to \a value of this data point.
22566
22567 For a general explanation of what this method is good for in the context of the data container,
22568 see the documentation of \ref QCPDataContainer.
22569*/
22570
22571/* end documentation of inline functions */
22572
22573/*!
22574 Constructs a curve data point with t, key and value set to zero.
22575*/
22577 t(0),
22578 key(0),
22579 value(0)
22580{
22581}
22582
22583/*!
22584 Constructs a curve data point with the specified \a t, \a key and \a value.
22585*/
22586QCPCurveData::QCPCurveData(double t, double key, double value) :
22587 t(t),
22588 key(key),
22589 value(value)
22590{
22591}
22592
22593
22594////////////////////////////////////////////////////////////////////////////////////////////////////
22595//////////////////// QCPCurve
22596////////////////////////////////////////////////////////////////////////////////////////////////////
22597
22598/*! \class QCPCurve
22599 \brief A plottable representing a parametric curve in a plot.
22600
22601 \image html QCPCurve.png
22602
22603 Unlike QCPGraph, plottables of this type may have multiple points with the same key coordinate,
22604 so their visual representation can have \a loops. This is realized by introducing a third
22605 coordinate \a t, which defines the order of the points described by the other two coordinates \a
22606 x and \a y.
22607
22608 To plot data, assign it with the \ref setData or \ref addData functions. Alternatively, you can
22609 also access and modify the curve's data via the \ref data method, which returns a pointer to the
22610 internal \ref QCPCurveDataContainer.
22611
22612 Gaps in the curve can be created by adding data points with NaN as key and value
22613 (<tt>qQNaN()</tt> or <tt>std::numeric_limits<double>::quiet_NaN()</tt>) in between the two data points that shall be
22614 separated.
22615
22616 \section qcpcurve-appearance Changing the appearance
22617
22618 The appearance of the curve is determined by the pen and the brush (\ref setPen, \ref setBrush).
22619
22620 \section qcpcurve-usage Usage
22621
22622 Like all data representing objects in QCustomPlot, the QCPCurve is a plottable
22623 (QCPAbstractPlottable). So the plottable-interface of QCustomPlot applies
22624 (QCustomPlot::plottable, QCustomPlot::removePlottable, etc.)
22625
22626 Usually, you first create an instance:
22627 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcurve-creation-1
22628 which registers it with the QCustomPlot instance of the passed axes. Note that this QCustomPlot instance takes
22629 ownership of the plottable, so do not delete it manually but use QCustomPlot::removePlottable() instead.
22630 The newly created plottable can be modified, e.g.:
22631 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcurve-creation-2
22632*/
22633
22634/* start of documentation of inline functions */
22635
22636/*! \fn QSharedPointer<QCPCurveDataContainer> QCPCurve::data() const
22637
22638 Returns a shared pointer to the internal data storage of type \ref QCPCurveDataContainer. You may
22639 use it to directly manipulate the data, which may be more convenient and faster than using the
22640 regular \ref setData or \ref addData methods.
22641*/
22642
22643/* end of documentation of inline functions */
22644
22645/*!
22646 Constructs a curve which uses \a keyAxis as its key axis ("x") and \a valueAxis as its value
22647 axis ("y"). \a keyAxis and \a valueAxis must reside in the same QCustomPlot instance and not have
22648 the same orientation. If either of these restrictions is violated, a corresponding message is
22649 printed to the debug output (qDebug), the construction is not aborted, though.
22650
22651 The created QCPCurve is automatically registered with the QCustomPlot instance inferred from \a
22652 keyAxis. This QCustomPlot instance takes ownership of the QCPCurve, so do not delete it manually
22653 but use QCustomPlot::removePlottable() instead.
22654*/
22655QCPCurve::QCPCurve(QCPAxis *keyAxis, QCPAxis *valueAxis) :
22656 QCPAbstractPlottable1D<QCPCurveData>(keyAxis, valueAxis),
22657 mScatterSkip{},
22658 mLineStyle{}
22659{
22660 // modify inherited properties from abstract plottable:
22661 setPen(QPen(Qt::blue, 0));
22663
22666 setScatterSkip(0);
22667}
22668
22669QCPCurve::~QCPCurve()
22670{
22671}
22672
22673/*! \overload
22674
22675 Replaces the current data container with the provided \a data container.
22676
22677 Since a QSharedPointer is used, multiple QCPCurves may share the same data container safely.
22678 Modifying the data in the container will then affect all curves that share the container. Sharing
22679 can be achieved by simply exchanging the data containers wrapped in shared pointers:
22680 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcurve-datasharing-1
22681
22682 If you do not wish to share containers, but create a copy from an existing container, rather use
22683 the \ref QCPDataContainer<DataType>::set method on the curve's data container directly:
22684 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcurve-datasharing-2
22685
22686 \see addData
22687*/
22689{
22690 mDataContainer = data;
22691}
22692
22693/*! \overload
22694
22695 Replaces the current data with the provided points in \a t, \a keys and \a values. The provided
22696 vectors should have equal length. Else, the number of added points will be the size of the
22697 smallest vector.
22698
22699 If you can guarantee that the passed data points are sorted by \a t in ascending order, you can
22700 set \a alreadySorted to true, to improve performance by saving a sorting run.
22701
22702 \see addData
22703*/
22704void QCPCurve::setData(const QVector<double> &t, const QVector<double> &keys, const QVector<double> &values, bool alreadySorted)
22705{
22706 mDataContainer->clear();
22707 addData(t, keys, values, alreadySorted);
22708}
22709
22710
22711/*! \overload
22712
22713 Replaces the current data with the provided points in \a keys and \a values. The provided vectors
22714 should have equal length. Else, the number of added points will be the size of the smallest
22715 vector.
22716
22717 The t parameter of each data point will be set to the integer index of the respective key/value
22718 pair.
22719
22720 \see addData
22721*/
22722void QCPCurve::setData(const QVector<double> &keys, const QVector<double> &values)
22723{
22724 mDataContainer->clear();
22725 addData(keys, values);
22726}
22727
22728/*!
22729 Sets the visual appearance of single data points in the plot. If set to \ref
22730 QCPScatterStyle::ssNone, no scatter points are drawn (e.g. for line-only plots with appropriate
22731 line style).
22732
22733 \see QCPScatterStyle, setLineStyle
22734*/
22736{
22737 mScatterStyle = style;
22738}
22739
22740/*!
22741 If scatters are displayed (scatter style not \ref QCPScatterStyle::ssNone), \a skip number of
22742 scatter points are skipped/not drawn after every drawn scatter point.
22743
22744 This can be used to make the data appear sparser while for example still having a smooth line,
22745 and to improve performance for very high density plots.
22746
22747 If \a skip is set to 0 (default), all scatter points are drawn.
22748
22749 \see setScatterStyle
22750*/
22752{
22753 mScatterSkip = qMax(0, skip);
22754}
22755
22756/*!
22757 Sets how the single data points are connected in the plot or how they are represented visually
22758 apart from the scatter symbol. For scatter-only plots, set \a style to \ref lsNone and \ref
22759 setScatterStyle to the desired scatter style.
22760
22761 \see setScatterStyle
22762*/
22764{
22765 mLineStyle = style;
22766}
22767
22768/*! \overload
22769
22770 Adds the provided points in \a t, \a keys and \a values to the current data. The provided vectors
22771 should have equal length. Else, the number of added points will be the size of the smallest
22772 vector.
22773
22774 If you can guarantee that the passed data points are sorted by \a keys in ascending order, you
22775 can set \a alreadySorted to true, to improve performance by saving a sorting run.
22776
22777 Alternatively, you can also access and modify the data directly via the \ref data method, which
22778 returns a pointer to the internal data container.
22779*/
22780void QCPCurve::addData(const QVector<double> &t, const QVector<double> &keys, const QVector<double> &values, bool alreadySorted)
22781{
22782 if (t.size() != keys.size() || t.size() != values.size())
22783 qDebug() << Q_FUNC_INFO << "ts, keys and values have different sizes:" << t.size() << keys.size() << values.size();
22784 const int n = qMin(qMin(t.size(), keys.size()), values.size());
22785 QVector<QCPCurveData> tempData(n);
22786 QVector<QCPCurveData>::iterator it = tempData.begin();
22787 const QVector<QCPCurveData>::iterator itEnd = tempData.end();
22788 int i = 0;
22789 while (it != itEnd)
22790 {
22791 it->t = t[i];
22792 it->key = keys[i];
22793 it->value = values[i];
22794 ++it;
22795 ++i;
22796 }
22797 mDataContainer->add(tempData, alreadySorted); // don't modify tempData beyond this to prevent copy on write
22798}
22799
22800/*! \overload
22801
22802 Adds the provided points in \a keys and \a values to the current data. The provided vectors
22803 should have equal length. Else, the number of added points will be the size of the smallest
22804 vector.
22805
22806 The t parameter of each data point will be set to the integer index of the respective key/value
22807 pair.
22808
22809 Alternatively, you can also access and modify the data directly via the \ref data method, which
22810 returns a pointer to the internal data container.
22811*/
22812void QCPCurve::addData(const QVector<double> &keys, const QVector<double> &values)
22813{
22814 if (keys.size() != values.size())
22815 qDebug() << Q_FUNC_INFO << "keys and values have different sizes:" << keys.size() << values.size();
22816 const int n = qMin(keys.size(), values.size());
22817 double tStart;
22818 if (!mDataContainer->isEmpty())
22819 tStart = (mDataContainer->constEnd()-1)->t + 1.0;
22820 else
22821 tStart = 0;
22822 QVector<QCPCurveData> tempData(n);
22823 QVector<QCPCurveData>::iterator it = tempData.begin();
22824 const QVector<QCPCurveData>::iterator itEnd = tempData.end();
22825 int i = 0;
22826 while (it != itEnd)
22827 {
22828 it->t = tStart + i;
22829 it->key = keys[i];
22830 it->value = values[i];
22831 ++it;
22832 ++i;
22833 }
22834 mDataContainer->add(tempData, true); // don't modify tempData beyond this to prevent copy on write
22835}
22836
22837/*! \overload
22838 Adds the provided data point as \a t, \a key and \a value to the current data.
22839
22840 Alternatively, you can also access and modify the data directly via the \ref data method, which
22841 returns a pointer to the internal data container.
22842*/
22843void QCPCurve::addData(double t, double key, double value)
22844{
22845 mDataContainer->add(QCPCurveData(t, key, value));
22846}
22847
22848/*! \overload
22849
22850 Adds the provided data point as \a key and \a value to the current data.
22851
22852 The t parameter is generated automatically by increments of 1 for each point, starting at the
22853 highest t of previously existing data or 0, if the curve data is empty.
22854
22855 Alternatively, you can also access and modify the data directly via the \ref data method, which
22856 returns a pointer to the internal data container.
22857*/
22858void QCPCurve::addData(double key, double value)
22859{
22860 if (!mDataContainer->isEmpty())
22861 mDataContainer->add(QCPCurveData((mDataContainer->constEnd()-1)->t + 1.0, key, value));
22862 else
22863 mDataContainer->add(QCPCurveData(0.0, key, value));
22864}
22865
22866/*!
22867 Implements a selectTest specific to this plottable's point geometry.
22868
22869 If \a details is not 0, it will be set to a \ref QCPDataSelection, describing the closest data
22870 point to \a pos.
22871
22872 \seebaseclassmethod \ref QCPAbstractPlottable::selectTest
22873*/
22874double QCPCurve::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
22875{
22876 if ((onlySelectable && mSelectable == QCP::stNone) || mDataContainer->isEmpty())
22877 return -1;
22878 if (!mKeyAxis || !mValueAxis)
22879 return -1;
22880
22881 if (mKeyAxis.data()->axisRect()->rect().contains(pos.toPoint()) || mParentPlot->interactions().testFlag(QCP::iSelectPlottablesBeyondAxisRect))
22882 {
22883 QCPCurveDataContainer::const_iterator closestDataPoint = mDataContainer->constEnd();
22884 double result = pointDistance(pos, closestDataPoint);
22885 if (details)
22886 {
22887 int pointIndex = int( closestDataPoint-mDataContainer->constBegin() );
22888 details->setValue(QCPDataSelection(QCPDataRange(pointIndex, pointIndex+1)));
22889 }
22890 return result;
22891 } else
22892 return -1;
22893}
22894
22895/* inherits documentation from base class */
22896QCPRange QCPCurve::getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain) const
22897{
22898 return mDataContainer->keyRange(foundRange, inSignDomain);
22899}
22900
22901/* inherits documentation from base class */
22902QCPRange QCPCurve::getValueRange(bool &foundRange, QCP::SignDomain inSignDomain, const QCPRange &inKeyRange) const
22903{
22904 return mDataContainer->valueRange(foundRange, inSignDomain, inKeyRange);
22905}
22906
22907/* inherits documentation from base class */
22909{
22910 if (mDataContainer->isEmpty()) return;
22911
22912 // allocate line vector:
22913 QVector<QPointF> lines, scatters;
22914
22915 // loop over and draw segments of unselected/selected data:
22916 QList<QCPDataRange> selectedSegments, unselectedSegments, allSegments;
22917 getDataSegments(selectedSegments, unselectedSegments);
22918 allSegments << unselectedSegments << selectedSegments;
22919 for (int i=0; i<allSegments.size(); ++i)
22920 {
22921 bool isSelectedSegment = i >= unselectedSegments.size();
22922
22923 // fill with curve data:
22924 QPen finalCurvePen = mPen; // determine the final pen already here, because the line optimization depends on its stroke width
22925 if (isSelectedSegment && mSelectionDecorator)
22926 finalCurvePen = mSelectionDecorator->pen();
22927
22928 QCPDataRange lineDataRange = isSelectedSegment ? allSegments.at(i) : allSegments.at(i).adjusted(-1, 1); // unselected segments extend lines to bordering selected data point (safe to exceed total data bounds in first/last segment, getCurveLines takes care)
22929 getCurveLines(&lines, lineDataRange, finalCurvePen.widthF());
22930
22931 // check data validity if flag set:
22932 #ifdef QCUSTOMPLOT_CHECK_DATA
22933 for (QCPCurveDataContainer::const_iterator it = mDataContainer->constBegin(); it != mDataContainer->constEnd(); ++it)
22934 {
22935 if (QCP::isInvalidData(it->t) ||
22936 QCP::isInvalidData(it->key, it->value))
22937 qDebug() << Q_FUNC_INFO << "Data point at" << it->key << "invalid." << "Plottable name:" << name();
22938 }
22939 #endif
22940
22941 // draw curve fill:
22943 if (isSelectedSegment && mSelectionDecorator)
22944 mSelectionDecorator->applyBrush(painter);
22945 else
22946 painter->setBrush(mBrush);
22947 painter->setPen(Qt::NoPen);
22948 if (painter->brush().style() != Qt::NoBrush && painter->brush().color().alpha() != 0)
22949 painter->drawPolygon(QPolygonF(lines));
22950
22951 // draw curve line:
22952 if (mLineStyle != lsNone)
22953 {
22954 painter->setPen(finalCurvePen);
22955 painter->setBrush(Qt::NoBrush);
22956 drawCurveLine(painter, lines);
22957 }
22958
22959 // draw scatters:
22960 QCPScatterStyle finalScatterStyle = mScatterStyle;
22961 if (isSelectedSegment && mSelectionDecorator)
22962 finalScatterStyle = mSelectionDecorator->getFinalScatterStyle(mScatterStyle);
22963 if (!finalScatterStyle.isNone())
22964 {
22965 getScatters(&scatters, allSegments.at(i), finalScatterStyle.size());
22966 drawScatterPlot(painter, scatters, finalScatterStyle);
22967 }
22968 }
22969
22970 // draw other selection decoration that isn't just line/scatter pens and brushes:
22971 if (mSelectionDecorator)
22972 mSelectionDecorator->drawDecoration(painter, selection());
22973}
22974
22975/* inherits documentation from base class */
22976void QCPCurve::drawLegendIcon(QCPPainter *painter, const QRectF &rect) const
22977{
22978 // draw fill:
22979 if (mBrush.style() != Qt::NoBrush)
22980 {
22982 painter->fillRect(QRectF(rect.left(), rect.top()+rect.height()/2.0, rect.width(), rect.height()/3.0), mBrush);
22983 }
22984 // draw line vertically centered:
22985 if (mLineStyle != lsNone)
22986 {
22988 painter->setPen(mPen);
22989 painter->drawLine(QLineF(rect.left(), rect.top()+rect.height()/2.0, rect.right()+5, rect.top()+rect.height()/2.0)); // +5 on x2 else last segment is missing from dashed/dotted pens
22990 }
22991 // draw scatter symbol:
22992 if (!mScatterStyle.isNone())
22993 {
22995 // scale scatter pixmap if it's too large to fit in legend icon rect:
22996 if (mScatterStyle.shape() == QCPScatterStyle::ssPixmap && (mScatterStyle.pixmap().size().width() > rect.width() || mScatterStyle.pixmap().size().height() > rect.height()))
22997 {
22998 QCPScatterStyle scaledStyle(mScatterStyle);
22999 scaledStyle.setPixmap(scaledStyle.pixmap().scaled(rect.size().toSize(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
23000 scaledStyle.applyTo(painter, mPen);
23001 scaledStyle.drawShape(painter, QRectF(rect).center());
23002 } else
23003 {
23004 mScatterStyle.applyTo(painter, mPen);
23005 mScatterStyle.drawShape(painter, QRectF(rect).center());
23006 }
23007 }
23008}
23009
23010/*! \internal
23011
23012 Draws lines between the points in \a lines, given in pixel coordinates.
23013
23014 \see drawScatterPlot, getCurveLines
23015*/
23016void QCPCurve::drawCurveLine(QCPPainter *painter, const QVector<QPointF> &lines) const
23017{
23018 if (painter->pen().style() != Qt::NoPen && painter->pen().color().alpha() != 0)
23019 {
23021 drawPolyline(painter, lines);
23022 }
23023}
23024
23025/*! \internal
23026
23027 Draws scatter symbols at every point passed in \a points, given in pixel coordinates. The
23028 scatters will be drawn with \a painter and have the appearance as specified in \a style.
23029
23030 \see drawCurveLine, getCurveLines
23031*/
23032void QCPCurve::drawScatterPlot(QCPPainter *painter, const QVector<QPointF> &points, const QCPScatterStyle &style) const
23033{
23034 // draw scatter point symbols:
23036 style.applyTo(painter, mPen);
23037 foreach (const QPointF &point, points)
23038 if (!qIsNaN(point.x()) && !qIsNaN(point.y()))
23039 style.drawShape(painter, point);
23040}
23041
23042/*! \internal
23043
23044 Called by \ref draw to generate points in pixel coordinates which represent the line of the
23045 curve.
23046
23047 Line segments that aren't visible in the current axis rect are handled in an optimized way. They
23048 are projected onto a rectangle slightly larger than the visible axis rect and simplified
23049 regarding point count. The algorithm makes sure to preserve appearance of lines and fills inside
23050 the visible axis rect by generating new temporary points on the outer rect if necessary.
23051
23052 \a lines will be filled with points in pixel coordinates, that can be drawn with \ref
23053 drawCurveLine.
23054
23055 \a dataRange specifies the beginning and ending data indices that will be taken into account for
23056 conversion. In this function, the specified range may exceed the total data bounds without harm:
23057 a correspondingly trimmed data range will be used. This takes the burden off the user of this
23058 function to check for valid indices in \a dataRange, e.g. when extending ranges coming from \ref
23059 getDataSegments.
23060
23061 \a penWidth specifies the pen width that will be used to later draw the lines generated by this
23062 function. This is needed here to calculate an accordingly wider margin around the axis rect when
23063 performing the line optimization.
23064
23065 Methods that are also involved in the algorithm are: \ref getRegion, \ref getOptimizedPoint, \ref
23066 getOptimizedCornerPoints \ref mayTraverse, \ref getTraverse, \ref getTraverseCornerPoints.
23067
23068 \see drawCurveLine, drawScatterPlot
23069*/
23070void QCPCurve::getCurveLines(QVector<QPointF> *lines, const QCPDataRange &dataRange, double penWidth) const
23071{
23072 if (!lines) return;
23073 lines->clear();
23074 QCPAxis *keyAxis = mKeyAxis.data();
23075 QCPAxis *valueAxis = mValueAxis.data();
23076 if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
23077
23078 // add margins to rect to compensate for stroke width
23079 const double strokeMargin = qMax(qreal(1.0), qreal(penWidth*0.75)); // stroke radius + 50% safety
23080 const double keyMin = keyAxis->pixelToCoord(keyAxis->coordToPixel(keyAxis->range().lower)-strokeMargin*keyAxis->pixelOrientation());
23081 const double keyMax = keyAxis->pixelToCoord(keyAxis->coordToPixel(keyAxis->range().upper)+strokeMargin*keyAxis->pixelOrientation());
23082 const double valueMin = valueAxis->pixelToCoord(valueAxis->coordToPixel(valueAxis->range().lower)-strokeMargin*valueAxis->pixelOrientation());
23083 const double valueMax = valueAxis->pixelToCoord(valueAxis->coordToPixel(valueAxis->range().upper)+strokeMargin*valueAxis->pixelOrientation());
23084 QCPCurveDataContainer::const_iterator itBegin = mDataContainer->constBegin();
23085 QCPCurveDataContainer::const_iterator itEnd = mDataContainer->constEnd();
23086 mDataContainer->limitIteratorsToDataRange(itBegin, itEnd, dataRange);
23087 if (itBegin == itEnd)
23088 return;
23091 int prevRegion = getRegion(prevIt->key, prevIt->value, keyMin, valueMax, keyMax, valueMin);
23092 QVector<QPointF> trailingPoints; // points that must be applied after all other points (are generated only when handling first point to get virtual segment between last and first point right)
23093 while (it != itEnd)
23094 {
23095 const int currentRegion = getRegion(it->key, it->value, keyMin, valueMax, keyMax, valueMin);
23096 if (currentRegion != prevRegion) // changed region, possibly need to add some optimized edge points or original points if entering R
23097 {
23098 if (currentRegion != 5) // segment doesn't end in R, so it's a candidate for removal
23099 {
23100 QPointF crossA, crossB;
23101 if (prevRegion == 5) // we're coming from R, so add this point optimized
23102 {
23103 lines->append(getOptimizedPoint(currentRegion, it->key, it->value, prevIt->key, prevIt->value, keyMin, valueMax, keyMax, valueMin));
23104 // in the situations 5->1/7/9/3 the segment may leave R and directly cross through two outer regions. In these cases we need to add an additional corner point
23105 *lines << getOptimizedCornerPoints(prevRegion, currentRegion, prevIt->key, prevIt->value, it->key, it->value, keyMin, valueMax, keyMax, valueMin);
23106 } else if (mayTraverse(prevRegion, currentRegion) &&
23107 getTraverse(prevIt->key, prevIt->value, it->key, it->value, keyMin, valueMax, keyMax, valueMin, crossA, crossB))
23108 {
23109 // add the two cross points optimized if segment crosses R and if segment isn't virtual zeroth segment between last and first curve point:
23110 QVector<QPointF> beforeTraverseCornerPoints, afterTraverseCornerPoints;
23111 getTraverseCornerPoints(prevRegion, currentRegion, keyMin, valueMax, keyMax, valueMin, beforeTraverseCornerPoints, afterTraverseCornerPoints);
23112 if (it != itBegin)
23113 {
23114 *lines << beforeTraverseCornerPoints;
23115 lines->append(crossA);
23116 lines->append(crossB);
23117 *lines << afterTraverseCornerPoints;
23118 } else
23119 {
23120 lines->append(crossB);
23121 *lines << afterTraverseCornerPoints;
23122 trailingPoints << beforeTraverseCornerPoints << crossA ;
23123 }
23124 } else // doesn't cross R, line is just moving around in outside regions, so only need to add optimized point(s) at the boundary corner(s)
23125 {
23126 *lines << getOptimizedCornerPoints(prevRegion, currentRegion, prevIt->key, prevIt->value, it->key, it->value, keyMin, valueMax, keyMax, valueMin);
23127 }
23128 } else // segment does end in R, so we add previous point optimized and this point at original position
23129 {
23130 if (it == itBegin) // it is first point in curve and prevIt is last one. So save optimized point for adding it to the lineData in the end
23131 trailingPoints << getOptimizedPoint(prevRegion, prevIt->key, prevIt->value, it->key, it->value, keyMin, valueMax, keyMax, valueMin);
23132 else
23133 lines->append(getOptimizedPoint(prevRegion, prevIt->key, prevIt->value, it->key, it->value, keyMin, valueMax, keyMax, valueMin));
23134 lines->append(coordsToPixels(it->key, it->value));
23135 }
23136 } else // region didn't change
23137 {
23138 if (currentRegion == 5) // still in R, keep adding original points
23139 {
23140 lines->append(coordsToPixels(it->key, it->value));
23141 } else // still outside R, no need to add anything
23142 {
23143 // see how this is not doing anything? That's the main optimization...
23144 }
23145 }
23146 prevIt = it;
23147 prevRegion = currentRegion;
23148 ++it;
23149 }
23150 *lines << trailingPoints;
23151}
23152
23153/*! \internal
23154
23155 Called by \ref draw to generate points in pixel coordinates which represent the scatters of the
23156 curve. If a scatter skip is configured (\ref setScatterSkip), the returned points are accordingly
23157 sparser.
23158
23159 Scatters that aren't visible in the current axis rect are optimized away.
23160
23161 \a scatters will be filled with points in pixel coordinates, that can be drawn with \ref
23162 drawScatterPlot.
23163
23164 \a dataRange specifies the beginning and ending data indices that will be taken into account for
23165 conversion.
23166
23167 \a scatterWidth specifies the scatter width that will be used to later draw the scatters at pixel
23168 coordinates generated by this function. This is needed here to calculate an accordingly wider
23169 margin around the axis rect when performing the data point reduction.
23170
23171 \see draw, drawScatterPlot
23172*/
23173void QCPCurve::getScatters(QVector<QPointF> *scatters, const QCPDataRange &dataRange, double scatterWidth) const
23174{
23175 if (!scatters) return;
23176 scatters->clear();
23177 QCPAxis *keyAxis = mKeyAxis.data();
23178 QCPAxis *valueAxis = mValueAxis.data();
23179 if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
23180
23181 QCPCurveDataContainer::const_iterator begin = mDataContainer->constBegin();
23182 QCPCurveDataContainer::const_iterator end = mDataContainer->constEnd();
23183 mDataContainer->limitIteratorsToDataRange(begin, end, dataRange);
23184 if (begin == end)
23185 return;
23186 const int scatterModulo = mScatterSkip+1;
23187 const bool doScatterSkip = mScatterSkip > 0;
23188 int endIndex = int( end-mDataContainer->constBegin() );
23189
23190 QCPRange keyRange = keyAxis->range();
23191 QCPRange valueRange = valueAxis->range();
23192 // extend range to include width of scatter symbols:
23193 keyRange.lower = keyAxis->pixelToCoord(keyAxis->coordToPixel(keyRange.lower)-scatterWidth*keyAxis->pixelOrientation());
23194 keyRange.upper = keyAxis->pixelToCoord(keyAxis->coordToPixel(keyRange.upper)+scatterWidth*keyAxis->pixelOrientation());
23195 valueRange.lower = valueAxis->pixelToCoord(valueAxis->coordToPixel(valueRange.lower)-scatterWidth*valueAxis->pixelOrientation());
23196 valueRange.upper = valueAxis->pixelToCoord(valueAxis->coordToPixel(valueRange.upper)+scatterWidth*valueAxis->pixelOrientation());
23197
23199 int itIndex = int( begin-mDataContainer->constBegin() );
23200 while (doScatterSkip && it != end && itIndex % scatterModulo != 0) // advance begin iterator to first non-skipped scatter
23201 {
23202 ++itIndex;
23203 ++it;
23204 }
23205 if (keyAxis->orientation() == Qt::Vertical)
23206 {
23207 while (it != end)
23208 {
23209 if (!qIsNaN(it->value) && keyRange.contains(it->key) && valueRange.contains(it->value))
23210 scatters->append(QPointF(valueAxis->coordToPixel(it->value), keyAxis->coordToPixel(it->key)));
23211
23212 // advance iterator to next (non-skipped) data point:
23213 if (!doScatterSkip)
23214 ++it;
23215 else
23216 {
23217 itIndex += scatterModulo;
23218 if (itIndex < endIndex) // make sure we didn't jump over end
23219 it += scatterModulo;
23220 else
23221 {
23222 it = end;
23223 itIndex = endIndex;
23224 }
23225 }
23226 }
23227 } else
23228 {
23229 while (it != end)
23230 {
23231 if (!qIsNaN(it->value) && keyRange.contains(it->key) && valueRange.contains(it->value))
23232 scatters->append(QPointF(keyAxis->coordToPixel(it->key), valueAxis->coordToPixel(it->value)));
23233
23234 // advance iterator to next (non-skipped) data point:
23235 if (!doScatterSkip)
23236 ++it;
23237 else
23238 {
23239 itIndex += scatterModulo;
23240 if (itIndex < endIndex) // make sure we didn't jump over end
23241 it += scatterModulo;
23242 else
23243 {
23244 it = end;
23245 itIndex = endIndex;
23246 }
23247 }
23248 }
23249 }
23250}
23251
23252/*! \internal
23253
23254 This function is part of the curve optimization algorithm of \ref getCurveLines.
23255
23256 It returns the region of the given point (\a key, \a value) with respect to a rectangle defined
23257 by \a keyMin, \a keyMax, \a valueMin, and \a valueMax.
23258
23259 The regions are enumerated from top to bottom (\a valueMin to \a valueMax) and left to right (\a
23260 keyMin to \a keyMax):
23261
23262 <table style="width:10em; text-align:center">
23263 <tr><td>1</td><td>4</td><td>7</td></tr>
23264 <tr><td>2</td><td style="border:1px solid black">5</td><td>8</td></tr>
23265 <tr><td>3</td><td>6</td><td>9</td></tr>
23266 </table>
23267
23268 With the rectangle being region 5, and the outer regions extending infinitely outwards. In the
23269 curve optimization algorithm, region 5 is considered to be the visible portion of the plot.
23270*/
23271int QCPCurve::getRegion(double key, double value, double keyMin, double valueMax, double keyMax, double valueMin) const
23272{
23273 if (key < keyMin) // region 123
23274 {
23275 if (value > valueMax)
23276 return 1;
23277 else if (value < valueMin)
23278 return 3;
23279 else
23280 return 2;
23281 } else if (key > keyMax) // region 789
23282 {
23283 if (value > valueMax)
23284 return 7;
23285 else if (value < valueMin)
23286 return 9;
23287 else
23288 return 8;
23289 } else // region 456
23290 {
23291 if (value > valueMax)
23292 return 4;
23293 else if (value < valueMin)
23294 return 6;
23295 else
23296 return 5;
23297 }
23298}
23299
23300/*! \internal
23301
23302 This function is part of the curve optimization algorithm of \ref getCurveLines.
23303
23304 This method is used in case the current segment passes from inside the visible rect (region 5,
23305 see \ref getRegion) to any of the outer regions (\a otherRegion). The current segment is given by
23306 the line connecting (\a key, \a value) with (\a otherKey, \a otherValue).
23307
23308 It returns the intersection point of the segment with the border of region 5.
23309
23310 For this function it doesn't matter whether (\a key, \a value) is the point inside region 5 or
23311 whether it's (\a otherKey, \a otherValue), i.e. whether the segment is coming from region 5 or
23312 leaving it. It is important though that \a otherRegion correctly identifies the other region not
23313 equal to 5.
23314*/
23315QPointF QCPCurve::getOptimizedPoint(int otherRegion, double otherKey, double otherValue, double key, double value, double keyMin, double valueMax, double keyMax, double valueMin) const
23316{
23317 // The intersection point interpolation here is done in pixel coordinates, so we don't need to
23318 // differentiate between different axis scale types. Note that the nomenclature
23319 // top/left/bottom/right/min/max is with respect to the rect in plot coordinates, wich may be
23320 // different in pixel coordinates (horz/vert key axes, reversed ranges)
23321
23322 const double keyMinPx = mKeyAxis->coordToPixel(keyMin);
23323 const double keyMaxPx = mKeyAxis->coordToPixel(keyMax);
23324 const double valueMinPx = mValueAxis->coordToPixel(valueMin);
23325 const double valueMaxPx = mValueAxis->coordToPixel(valueMax);
23326 const double otherValuePx = mValueAxis->coordToPixel(otherValue);
23327 const double valuePx = mValueAxis->coordToPixel(value);
23328 const double otherKeyPx = mKeyAxis->coordToPixel(otherKey);
23329 const double keyPx = mKeyAxis->coordToPixel(key);
23330 double intersectKeyPx = keyMinPx; // initial key just a fail-safe
23331 double intersectValuePx = valueMinPx; // initial value just a fail-safe
23332 switch (otherRegion)
23333 {
23334 case 1: // top and left edge
23335 {
23336 intersectValuePx = valueMaxPx;
23337 intersectKeyPx = otherKeyPx + (keyPx-otherKeyPx)/(valuePx-otherValuePx)*(intersectValuePx-otherValuePx);
23338 if (intersectKeyPx < qMin(keyMinPx, keyMaxPx) || intersectKeyPx > qMax(keyMinPx, keyMaxPx)) // check whether top edge is not intersected, then it must be left edge (qMin/qMax necessary since axes may be reversed)
23339 {
23340 intersectKeyPx = keyMinPx;
23341 intersectValuePx = otherValuePx + (valuePx-otherValuePx)/(keyPx-otherKeyPx)*(intersectKeyPx-otherKeyPx);
23342 }
23343 break;
23344 }
23345 case 2: // left edge
23346 {
23347 intersectKeyPx = keyMinPx;
23348 intersectValuePx = otherValuePx + (valuePx-otherValuePx)/(keyPx-otherKeyPx)*(intersectKeyPx-otherKeyPx);
23349 break;
23350 }
23351 case 3: // bottom and left edge
23352 {
23353 intersectValuePx = valueMinPx;
23354 intersectKeyPx = otherKeyPx + (keyPx-otherKeyPx)/(valuePx-otherValuePx)*(intersectValuePx-otherValuePx);
23355 if (intersectKeyPx < qMin(keyMinPx, keyMaxPx) || intersectKeyPx > qMax(keyMinPx, keyMaxPx)) // check whether bottom edge is not intersected, then it must be left edge (qMin/qMax necessary since axes may be reversed)
23356 {
23357 intersectKeyPx = keyMinPx;
23358 intersectValuePx = otherValuePx + (valuePx-otherValuePx)/(keyPx-otherKeyPx)*(intersectKeyPx-otherKeyPx);
23359 }
23360 break;
23361 }
23362 case 4: // top edge
23363 {
23364 intersectValuePx = valueMaxPx;
23365 intersectKeyPx = otherKeyPx + (keyPx-otherKeyPx)/(valuePx-otherValuePx)*(intersectValuePx-otherValuePx);
23366 break;
23367 }
23368 case 5:
23369 {
23370 break; // case 5 shouldn't happen for this function but we add it anyway to prevent potential discontinuity in branch table
23371 }
23372 case 6: // bottom edge
23373 {
23374 intersectValuePx = valueMinPx;
23375 intersectKeyPx = otherKeyPx + (keyPx-otherKeyPx)/(valuePx-otherValuePx)*(intersectValuePx-otherValuePx);
23376 break;
23377 }
23378 case 7: // top and right edge
23379 {
23380 intersectValuePx = valueMaxPx;
23381 intersectKeyPx = otherKeyPx + (keyPx-otherKeyPx)/(valuePx-otherValuePx)*(intersectValuePx-otherValuePx);
23382 if (intersectKeyPx < qMin(keyMinPx, keyMaxPx) || intersectKeyPx > qMax(keyMinPx, keyMaxPx)) // check whether top edge is not intersected, then it must be right edge (qMin/qMax necessary since axes may be reversed)
23383 {
23384 intersectKeyPx = keyMaxPx;
23385 intersectValuePx = otherValuePx + (valuePx-otherValuePx)/(keyPx-otherKeyPx)*(intersectKeyPx-otherKeyPx);
23386 }
23387 break;
23388 }
23389 case 8: // right edge
23390 {
23391 intersectKeyPx = keyMaxPx;
23392 intersectValuePx = otherValuePx + (valuePx-otherValuePx)/(keyPx-otherKeyPx)*(intersectKeyPx-otherKeyPx);
23393 break;
23394 }
23395 case 9: // bottom and right edge
23396 {
23397 intersectValuePx = valueMinPx;
23398 intersectKeyPx = otherKeyPx + (keyPx-otherKeyPx)/(valuePx-otherValuePx)*(intersectValuePx-otherValuePx);
23399 if (intersectKeyPx < qMin(keyMinPx, keyMaxPx) || intersectKeyPx > qMax(keyMinPx, keyMaxPx)) // check whether bottom edge is not intersected, then it must be right edge (qMin/qMax necessary since axes may be reversed)
23400 {
23401 intersectKeyPx = keyMaxPx;
23402 intersectValuePx = otherValuePx + (valuePx-otherValuePx)/(keyPx-otherKeyPx)*(intersectKeyPx-otherKeyPx);
23403 }
23404 break;
23405 }
23406 }
23407 if (mKeyAxis->orientation() == Qt::Horizontal)
23408 return {intersectKeyPx, intersectValuePx};
23409 else
23410 return {intersectValuePx, intersectKeyPx};
23411}
23412
23413/*! \internal
23414
23415 This function is part of the curve optimization algorithm of \ref getCurveLines.
23416
23417 In situations where a single segment skips over multiple regions it might become necessary to add
23418 extra points at the corners of region 5 (see \ref getRegion) such that the optimized segment
23419 doesn't unintentionally cut through the visible area of the axis rect and create plot artifacts.
23420 This method provides these points that must be added, assuming the original segment doesn't
23421 start, end, or traverse region 5. (Corner points where region 5 is traversed are calculated by
23422 \ref getTraverseCornerPoints.)
23423
23424 For example, consider a segment which directly goes from region 4 to 2 but originally is far out
23425 to the top left such that it doesn't cross region 5. Naively optimizing these points by
23426 projecting them on the top and left borders of region 5 will create a segment that surely crosses
23427 5, creating a visual artifact in the plot. This method prevents this by providing extra points at
23428 the top left corner, making the optimized curve correctly pass from region 4 to 1 to 2 without
23429 traversing 5.
23430*/
23431QVector<QPointF> QCPCurve::getOptimizedCornerPoints(int prevRegion, int currentRegion, double prevKey, double prevValue, double key, double value, double keyMin, double valueMax, double keyMax, double valueMin) const
23432{
23433 QVector<QPointF> result;
23434 switch (prevRegion)
23435 {
23436 case 1:
23437 {
23438 switch (currentRegion)
23439 {
23440 case 2: { result << coordsToPixels(keyMin, valueMax); break; }
23441 case 4: { result << coordsToPixels(keyMin, valueMax); break; }
23442 case 3: { result << coordsToPixels(keyMin, valueMax) << coordsToPixels(keyMin, valueMin); break; }
23443 case 7: { result << coordsToPixels(keyMin, valueMax) << coordsToPixels(keyMax, valueMax); break; }
23444 case 6: { result << coordsToPixels(keyMin, valueMax) << coordsToPixels(keyMin, valueMin); result.append(result.last()); break; }
23445 case 8: { result << coordsToPixels(keyMin, valueMax) << coordsToPixels(keyMax, valueMax); result.append(result.last()); break; }
23446 case 9: { // in this case we need another distinction of cases: segment may pass below or above rect, requiring either bottom right or top left corner points
23447 if ((value-prevValue)/(key-prevKey)*(keyMin-key)+value < valueMin) // segment passes below R
23448 { result << coordsToPixels(keyMin, valueMax) << coordsToPixels(keyMin, valueMin); result.append(result.last()); result << coordsToPixels(keyMax, valueMin); }
23449 else
23450 { result << coordsToPixels(keyMin, valueMax) << coordsToPixels(keyMax, valueMax); result.append(result.last()); result << coordsToPixels(keyMax, valueMin); }
23451 break;
23452 }
23453 }
23454 break;
23455 }
23456 case 2:
23457 {
23458 switch (currentRegion)
23459 {
23460 case 1: { result << coordsToPixels(keyMin, valueMax); break; }
23461 case 3: { result << coordsToPixels(keyMin, valueMin); break; }
23462 case 4: { result << coordsToPixels(keyMin, valueMax); result.append(result.last()); break; }
23463 case 6: { result << coordsToPixels(keyMin, valueMin); result.append(result.last()); break; }
23464 case 7: { result << coordsToPixels(keyMin, valueMax); result.append(result.last()); result << coordsToPixels(keyMax, valueMax); break; }
23465 case 9: { result << coordsToPixels(keyMin, valueMin); result.append(result.last()); result << coordsToPixels(keyMax, valueMin); break; }
23466 }
23467 break;
23468 }
23469 case 3:
23470 {
23471 switch (currentRegion)
23472 {
23473 case 2: { result << coordsToPixels(keyMin, valueMin); break; }
23474 case 6: { result << coordsToPixels(keyMin, valueMin); break; }
23475 case 1: { result << coordsToPixels(keyMin, valueMin) << coordsToPixels(keyMin, valueMax); break; }
23476 case 9: { result << coordsToPixels(keyMin, valueMin) << coordsToPixels(keyMax, valueMin); break; }
23477 case 4: { result << coordsToPixels(keyMin, valueMin) << coordsToPixels(keyMin, valueMax); result.append(result.last()); break; }
23478 case 8: { result << coordsToPixels(keyMin, valueMin) << coordsToPixels(keyMax, valueMin); result.append(result.last()); break; }
23479 case 7: { // in this case we need another distinction of cases: segment may pass below or above rect, requiring either bottom right or top left corner points
23480 if ((value-prevValue)/(key-prevKey)*(keyMax-key)+value < valueMin) // segment passes below R
23481 { result << coordsToPixels(keyMin, valueMin) << coordsToPixels(keyMax, valueMin); result.append(result.last()); result << coordsToPixels(keyMax, valueMax); }
23482 else
23483 { result << coordsToPixels(keyMin, valueMin) << coordsToPixels(keyMin, valueMax); result.append(result.last()); result << coordsToPixels(keyMax, valueMax); }
23484 break;
23485 }
23486 }
23487 break;
23488 }
23489 case 4:
23490 {
23491 switch (currentRegion)
23492 {
23493 case 1: { result << coordsToPixels(keyMin, valueMax); break; }
23494 case 7: { result << coordsToPixels(keyMax, valueMax); break; }
23495 case 2: { result << coordsToPixels(keyMin, valueMax); result.append(result.last()); break; }
23496 case 8: { result << coordsToPixels(keyMax, valueMax); result.append(result.last()); break; }
23497 case 3: { result << coordsToPixels(keyMin, valueMax); result.append(result.last()); result << coordsToPixels(keyMin, valueMin); break; }
23498 case 9: { result << coordsToPixels(keyMax, valueMax); result.append(result.last()); result << coordsToPixels(keyMax, valueMin); break; }
23499 }
23500 break;
23501 }
23502 case 5:
23503 {
23504 switch (currentRegion)
23505 {
23506 case 1: { result << coordsToPixels(keyMin, valueMax); break; }
23507 case 7: { result << coordsToPixels(keyMax, valueMax); break; }
23508 case 9: { result << coordsToPixels(keyMax, valueMin); break; }
23509 case 3: { result << coordsToPixels(keyMin, valueMin); break; }
23510 }
23511 break;
23512 }
23513 case 6:
23514 {
23515 switch (currentRegion)
23516 {
23517 case 3: { result << coordsToPixels(keyMin, valueMin); break; }
23518 case 9: { result << coordsToPixels(keyMax, valueMin); break; }
23519 case 2: { result << coordsToPixels(keyMin, valueMin); result.append(result.last()); break; }
23520 case 8: { result << coordsToPixels(keyMax, valueMin); result.append(result.last()); break; }
23521 case 1: { result << coordsToPixels(keyMin, valueMin); result.append(result.last()); result << coordsToPixels(keyMin, valueMax); break; }
23522 case 7: { result << coordsToPixels(keyMax, valueMin); result.append(result.last()); result << coordsToPixels(keyMax, valueMax); break; }
23523 }
23524 break;
23525 }
23526 case 7:
23527 {
23528 switch (currentRegion)
23529 {
23530 case 4: { result << coordsToPixels(keyMax, valueMax); break; }
23531 case 8: { result << coordsToPixels(keyMax, valueMax); break; }
23532 case 1: { result << coordsToPixels(keyMax, valueMax) << coordsToPixels(keyMin, valueMax); break; }
23533 case 9: { result << coordsToPixels(keyMax, valueMax) << coordsToPixels(keyMax, valueMin); break; }
23534 case 2: { result << coordsToPixels(keyMax, valueMax) << coordsToPixels(keyMin, valueMax); result.append(result.last()); break; }
23535 case 6: { result << coordsToPixels(keyMax, valueMax) << coordsToPixels(keyMax, valueMin); result.append(result.last()); break; }
23536 case 3: { // in this case we need another distinction of cases: segment may pass below or above rect, requiring either bottom right or top left corner points
23537 if ((value-prevValue)/(key-prevKey)*(keyMax-key)+value < valueMin) // segment passes below R
23538 { result << coordsToPixels(keyMax, valueMax) << coordsToPixels(keyMax, valueMin); result.append(result.last()); result << coordsToPixels(keyMin, valueMin); }
23539 else
23540 { result << coordsToPixels(keyMax, valueMax) << coordsToPixels(keyMin, valueMax); result.append(result.last()); result << coordsToPixels(keyMin, valueMin); }
23541 break;
23542 }
23543 }
23544 break;
23545 }
23546 case 8:
23547 {
23548 switch (currentRegion)
23549 {
23550 case 7: { result << coordsToPixels(keyMax, valueMax); break; }
23551 case 9: { result << coordsToPixels(keyMax, valueMin); break; }
23552 case 4: { result << coordsToPixels(keyMax, valueMax); result.append(result.last()); break; }
23553 case 6: { result << coordsToPixels(keyMax, valueMin); result.append(result.last()); break; }
23554 case 1: { result << coordsToPixels(keyMax, valueMax); result.append(result.last()); result << coordsToPixels(keyMin, valueMax); break; }
23555 case 3: { result << coordsToPixels(keyMax, valueMin); result.append(result.last()); result << coordsToPixels(keyMin, valueMin); break; }
23556 }
23557 break;
23558 }
23559 case 9:
23560 {
23561 switch (currentRegion)
23562 {
23563 case 6: { result << coordsToPixels(keyMax, valueMin); break; }
23564 case 8: { result << coordsToPixels(keyMax, valueMin); break; }
23565 case 3: { result << coordsToPixels(keyMax, valueMin) << coordsToPixels(keyMin, valueMin); break; }
23566 case 7: { result << coordsToPixels(keyMax, valueMin) << coordsToPixels(keyMax, valueMax); break; }
23567 case 2: { result << coordsToPixels(keyMax, valueMin) << coordsToPixels(keyMin, valueMin); result.append(result.last()); break; }
23568 case 4: { result << coordsToPixels(keyMax, valueMin) << coordsToPixels(keyMax, valueMax); result.append(result.last()); break; }
23569 case 1: { // in this case we need another distinction of cases: segment may pass below or above rect, requiring either bottom right or top left corner points
23570 if ((value-prevValue)/(key-prevKey)*(keyMin-key)+value < valueMin) // segment passes below R
23571 { result << coordsToPixels(keyMax, valueMin) << coordsToPixels(keyMin, valueMin); result.append(result.last()); result << coordsToPixels(keyMin, valueMax); }
23572 else
23573 { result << coordsToPixels(keyMax, valueMin) << coordsToPixels(keyMax, valueMax); result.append(result.last()); result << coordsToPixels(keyMin, valueMax); }
23574 break;
23575 }
23576 }
23577 break;
23578 }
23579 }
23580 return result;
23581}
23582
23583/*! \internal
23584
23585 This function is part of the curve optimization algorithm of \ref getCurveLines.
23586
23587 This method returns whether a segment going from \a prevRegion to \a currentRegion (see \ref
23588 getRegion) may traverse the visible region 5. This function assumes that neither \a prevRegion
23589 nor \a currentRegion is 5 itself.
23590
23591 If this method returns false, the segment for sure doesn't pass region 5. If it returns true, the
23592 segment may or may not pass region 5 and a more fine-grained calculation must be used (\ref
23593 getTraverse).
23594*/
23595bool QCPCurve::mayTraverse(int prevRegion, int currentRegion) const
23596{
23597 switch (prevRegion)
23598 {
23599 case 1:
23600 {
23601 switch (currentRegion)
23602 {
23603 case 4:
23604 case 7:
23605 case 2:
23606 case 3: return false;
23607 default: return true;
23608 }
23609 }
23610 case 2:
23611 {
23612 switch (currentRegion)
23613 {
23614 case 1:
23615 case 3: return false;
23616 default: return true;
23617 }
23618 }
23619 case 3:
23620 {
23621 switch (currentRegion)
23622 {
23623 case 1:
23624 case 2:
23625 case 6:
23626 case 9: return false;
23627 default: return true;
23628 }
23629 }
23630 case 4:
23631 {
23632 switch (currentRegion)
23633 {
23634 case 1:
23635 case 7: return false;
23636 default: return true;
23637 }
23638 }
23639 case 5: return false; // should never occur
23640 case 6:
23641 {
23642 switch (currentRegion)
23643 {
23644 case 3:
23645 case 9: return false;
23646 default: return true;
23647 }
23648 }
23649 case 7:
23650 {
23651 switch (currentRegion)
23652 {
23653 case 1:
23654 case 4:
23655 case 8:
23656 case 9: return false;
23657 default: return true;
23658 }
23659 }
23660 case 8:
23661 {
23662 switch (currentRegion)
23663 {
23664 case 7:
23665 case 9: return false;
23666 default: return true;
23667 }
23668 }
23669 case 9:
23670 {
23671 switch (currentRegion)
23672 {
23673 case 3:
23674 case 6:
23675 case 8:
23676 case 7: return false;
23677 default: return true;
23678 }
23679 }
23680 default: return true;
23681 }
23682}
23683
23684
23685/*! \internal
23686
23687 This function is part of the curve optimization algorithm of \ref getCurveLines.
23688
23689 This method assumes that the \ref mayTraverse test has returned true, so there is a chance the
23690 segment defined by (\a prevKey, \a prevValue) and (\a key, \a value) goes through the visible
23691 region 5.
23692
23693 The return value of this method indicates whether the segment actually traverses region 5 or not.
23694
23695 If the segment traverses 5, the output parameters \a crossA and \a crossB indicate the entry and
23696 exit points of region 5. They will become the optimized points for that segment.
23697*/
23698bool QCPCurve::getTraverse(double prevKey, double prevValue, double key, double value, double keyMin, double valueMax, double keyMax, double valueMin, QPointF &crossA, QPointF &crossB) const
23699{
23700 // The intersection point interpolation here is done in pixel coordinates, so we don't need to
23701 // differentiate between different axis scale types. Note that the nomenclature
23702 // top/left/bottom/right/min/max is with respect to the rect in plot coordinates, wich may be
23703 // different in pixel coordinates (horz/vert key axes, reversed ranges)
23704
23705 QList<QPointF> intersections;
23706 const double valueMinPx = mValueAxis->coordToPixel(valueMin);
23707 const double valueMaxPx = mValueAxis->coordToPixel(valueMax);
23708 const double keyMinPx = mKeyAxis->coordToPixel(keyMin);
23709 const double keyMaxPx = mKeyAxis->coordToPixel(keyMax);
23710 const double keyPx = mKeyAxis->coordToPixel(key);
23711 const double valuePx = mValueAxis->coordToPixel(value);
23712 const double prevKeyPx = mKeyAxis->coordToPixel(prevKey);
23713 const double prevValuePx = mValueAxis->coordToPixel(prevValue);
23714 if (qFuzzyIsNull(keyPx-prevKeyPx)) // line is parallel to value axis
23715 {
23716 // due to region filter in mayTraverse(), if line is parallel to value or key axis, region 5 is traversed here
23717 intersections.append(mKeyAxis->orientation() == Qt::Horizontal ? QPointF(keyPx, valueMinPx) : QPointF(valueMinPx, keyPx)); // direction will be taken care of at end of method
23718 intersections.append(mKeyAxis->orientation() == Qt::Horizontal ? QPointF(keyPx, valueMaxPx) : QPointF(valueMaxPx, keyPx));
23719 } else if (qFuzzyIsNull(valuePx-prevValuePx)) // line is parallel to key axis
23720 {
23721 // due to region filter in mayTraverse(), if line is parallel to value or key axis, region 5 is traversed here
23722 intersections.append(mKeyAxis->orientation() == Qt::Horizontal ? QPointF(keyMinPx, valuePx) : QPointF(valuePx, keyMinPx)); // direction will be taken care of at end of method
23723 intersections.append(mKeyAxis->orientation() == Qt::Horizontal ? QPointF(keyMaxPx, valuePx) : QPointF(valuePx, keyMaxPx));
23724 } else // line is skewed
23725 {
23726 double gamma;
23727 double keyPerValuePx = (keyPx-prevKeyPx)/(valuePx-prevValuePx);
23728 // check top of rect:
23729 gamma = prevKeyPx + (valueMaxPx-prevValuePx)*keyPerValuePx;
23730 if (gamma >= qMin(keyMinPx, keyMaxPx) && gamma <= qMax(keyMinPx, keyMaxPx)) // qMin/qMax necessary since axes may be reversed
23731 intersections.append(mKeyAxis->orientation() == Qt::Horizontal ? QPointF(gamma, valueMaxPx) : QPointF(valueMaxPx, gamma));
23732 // check bottom of rect:
23733 gamma = prevKeyPx + (valueMinPx-prevValuePx)*keyPerValuePx;
23734 if (gamma >= qMin(keyMinPx, keyMaxPx) && gamma <= qMax(keyMinPx, keyMaxPx)) // qMin/qMax necessary since axes may be reversed
23735 intersections.append(mKeyAxis->orientation() == Qt::Horizontal ? QPointF(gamma, valueMinPx) : QPointF(valueMinPx, gamma));
23736 const double valuePerKeyPx = 1.0/keyPerValuePx;
23737 // check left of rect:
23738 gamma = prevValuePx + (keyMinPx-prevKeyPx)*valuePerKeyPx;
23739 if (gamma >= qMin(valueMinPx, valueMaxPx) && gamma <= qMax(valueMinPx, valueMaxPx)) // qMin/qMax necessary since axes may be reversed
23740 intersections.append(mKeyAxis->orientation() == Qt::Horizontal ? QPointF(keyMinPx, gamma) : QPointF(gamma, keyMinPx));
23741 // check right of rect:
23742 gamma = prevValuePx + (keyMaxPx-prevKeyPx)*valuePerKeyPx;
23743 if (gamma >= qMin(valueMinPx, valueMaxPx) && gamma <= qMax(valueMinPx, valueMaxPx)) // qMin/qMax necessary since axes may be reversed
23744 intersections.append(mKeyAxis->orientation() == Qt::Horizontal ? QPointF(keyMaxPx, gamma) : QPointF(gamma, keyMaxPx));
23745 }
23746
23747 // handle cases where found points isn't exactly 2:
23748 if (intersections.size() > 2)
23749 {
23750 // line probably goes through corner of rect, and we got duplicate points there. single out the point pair with greatest distance in between:
23751 double distSqrMax = 0;
23752 QPointF pv1, pv2;
23753 for (int i=0; i<intersections.size()-1; ++i)
23754 {
23755 for (int k=i+1; k<intersections.size(); ++k)
23756 {
23757 QPointF distPoint = intersections.at(i)-intersections.at(k);
23758 double distSqr = distPoint.x()*distPoint.x()+distPoint.y()+distPoint.y();
23759 if (distSqr > distSqrMax)
23760 {
23761 pv1 = intersections.at(i);
23762 pv2 = intersections.at(k);
23763 distSqrMax = distSqr;
23764 }
23765 }
23766 }
23767 intersections = QList<QPointF>() << pv1 << pv2;
23768 } else if (intersections.size() != 2)
23769 {
23770 // one or even zero points found (shouldn't happen unless line perfectly tangent to corner), no need to draw segment
23771 return false;
23772 }
23773
23774 // possibly re-sort points so optimized point segment has same direction as original segment:
23775 double xDelta = keyPx-prevKeyPx;
23776 double yDelta = valuePx-prevValuePx;
23777 if (mKeyAxis->orientation() != Qt::Horizontal)
23778 qSwap(xDelta, yDelta);
23779 if (xDelta*(intersections.at(1).x()-intersections.at(0).x()) + yDelta*(intersections.at(1).y()-intersections.at(0).y()) < 0) // scalar product of both segments < 0 -> opposite direction
23780 intersections.move(0, 1);
23781 crossA = intersections.at(0);
23782 crossB = intersections.at(1);
23783 return true;
23784}
23785
23786/*! \internal
23787
23788 This function is part of the curve optimization algorithm of \ref getCurveLines.
23789
23790 This method assumes that the \ref getTraverse test has returned true, so the segment definitely
23791 traverses the visible region 5 when going from \a prevRegion to \a currentRegion.
23792
23793 In certain situations it is not sufficient to merely generate the entry and exit points of the
23794 segment into/out of region 5, as \ref getTraverse provides. It may happen that a single segment, in
23795 addition to traversing region 5, skips another region outside of region 5, which makes it
23796 necessary to add an optimized corner point there (very similar to the job \ref
23797 getOptimizedCornerPoints does for segments that are completely in outside regions and don't
23798 traverse 5).
23799
23800 As an example, consider a segment going from region 1 to region 6, traversing the lower left
23801 corner of region 5. In this configuration, the segment additionally crosses the border between
23802 region 1 and 2 before entering region 5. This makes it necessary to add an additional point in
23803 the top left corner, before adding the optimized traverse points. So in this case, the output
23804 parameter \a beforeTraverse will contain the top left corner point, and \a afterTraverse will be
23805 empty.
23806
23807 In some cases, such as when going from region 1 to 9, it may even be necessary to add additional
23808 corner points before and after the traverse. Then both \a beforeTraverse and \a afterTraverse
23809 return the respective corner points.
23810*/
23811void QCPCurve::getTraverseCornerPoints(int prevRegion, int currentRegion, double keyMin, double valueMax, double keyMax, double valueMin, QVector<QPointF> &beforeTraverse, QVector<QPointF> &afterTraverse) const
23812{
23813 switch (prevRegion)
23814 {
23815 case 1:
23816 {
23817 switch (currentRegion)
23818 {
23819 case 6: { beforeTraverse << coordsToPixels(keyMin, valueMax); break; }
23820 case 9: { beforeTraverse << coordsToPixels(keyMin, valueMax); afterTraverse << coordsToPixels(keyMax, valueMin); break; }
23821 case 8: { beforeTraverse << coordsToPixels(keyMin, valueMax); break; }
23822 }
23823 break;
23824 }
23825 case 2:
23826 {
23827 switch (currentRegion)
23828 {
23829 case 7: { afterTraverse << coordsToPixels(keyMax, valueMax); break; }
23830 case 9: { afterTraverse << coordsToPixels(keyMax, valueMin); break; }
23831 }
23832 break;
23833 }
23834 case 3:
23835 {
23836 switch (currentRegion)
23837 {
23838 case 4: { beforeTraverse << coordsToPixels(keyMin, valueMin); break; }
23839 case 7: { beforeTraverse << coordsToPixels(keyMin, valueMin); afterTraverse << coordsToPixels(keyMax, valueMax); break; }
23840 case 8: { beforeTraverse << coordsToPixels(keyMin, valueMin); break; }
23841 }
23842 break;
23843 }
23844 case 4:
23845 {
23846 switch (currentRegion)
23847 {
23848 case 3: { afterTraverse << coordsToPixels(keyMin, valueMin); break; }
23849 case 9: { afterTraverse << coordsToPixels(keyMax, valueMin); break; }
23850 }
23851 break;
23852 }
23853 case 5: { break; } // shouldn't happen because this method only handles full traverses
23854 case 6:
23855 {
23856 switch (currentRegion)
23857 {
23858 case 1: { afterTraverse << coordsToPixels(keyMin, valueMax); break; }
23859 case 7: { afterTraverse << coordsToPixels(keyMax, valueMax); break; }
23860 }
23861 break;
23862 }
23863 case 7:
23864 {
23865 switch (currentRegion)
23866 {
23867 case 2: { beforeTraverse << coordsToPixels(keyMax, valueMax); break; }
23868 case 3: { beforeTraverse << coordsToPixels(keyMax, valueMax); afterTraverse << coordsToPixels(keyMin, valueMin); break; }
23869 case 6: { beforeTraverse << coordsToPixels(keyMax, valueMax); break; }
23870 }
23871 break;
23872 }
23873 case 8:
23874 {
23875 switch (currentRegion)
23876 {
23877 case 1: { afterTraverse << coordsToPixels(keyMin, valueMax); break; }
23878 case 3: { afterTraverse << coordsToPixels(keyMin, valueMin); break; }
23879 }
23880 break;
23881 }
23882 case 9:
23883 {
23884 switch (currentRegion)
23885 {
23886 case 2: { beforeTraverse << coordsToPixels(keyMax, valueMin); break; }
23887 case 1: { beforeTraverse << coordsToPixels(keyMax, valueMin); afterTraverse << coordsToPixels(keyMin, valueMax); break; }
23888 case 4: { beforeTraverse << coordsToPixels(keyMax, valueMin); break; }
23889 }
23890 break;
23891 }
23892 }
23893}
23894
23895/*! \internal
23896
23897 Calculates the (minimum) distance (in pixels) the curve's representation has from the given \a
23898 pixelPoint in pixels. This is used to determine whether the curve was clicked or not, e.g. in
23899 \ref selectTest. The closest data point to \a pixelPoint is returned in \a closestData. Note that
23900 if the curve has a line representation, the returned distance may be smaller than the distance to
23901 the \a closestData point, since the distance to the curve line is also taken into account.
23902
23903 If either the curve has no data or if the line style is \ref lsNone and the scatter style's shape
23904 is \ref QCPScatterStyle::ssNone (i.e. there is no visual representation of the curve), returns
23905 -1.0.
23906*/
23907double QCPCurve::pointDistance(const QPointF &pixelPoint, QCPCurveDataContainer::const_iterator &closestData) const
23908{
23909 closestData = mDataContainer->constEnd();
23910 if (mDataContainer->isEmpty())
23911 return -1.0;
23912 if (mLineStyle == lsNone && mScatterStyle.isNone())
23913 return -1.0;
23914
23915 if (mDataContainer->size() == 1)
23916 {
23917 QPointF dataPoint = coordsToPixels(mDataContainer->constBegin()->key, mDataContainer->constBegin()->value);
23918 closestData = mDataContainer->constBegin();
23919 return QCPVector2D(dataPoint-pixelPoint).length();
23920 }
23921
23922 // calculate minimum distances to curve data points and find closestData iterator:
23923 double minDistSqr = (std::numeric_limits<double>::max)();
23924 // iterate over found data points and then choose the one with the shortest distance to pos:
23925 QCPCurveDataContainer::const_iterator begin = mDataContainer->constBegin();
23926 QCPCurveDataContainer::const_iterator end = mDataContainer->constEnd();
23927 for (QCPCurveDataContainer::const_iterator it=begin; it!=end; ++it)
23928 {
23929 const double currentDistSqr = QCPVector2D(coordsToPixels(it->key, it->value)-pixelPoint).lengthSquared();
23930 if (currentDistSqr < minDistSqr)
23931 {
23932 minDistSqr = currentDistSqr;
23933 closestData = it;
23934 }
23935 }
23936
23937 // calculate distance to line if there is one (if so, will probably be smaller than distance to closest data point):
23938 if (mLineStyle != lsNone)
23939 {
23940 QVector<QPointF> lines;
23941 getCurveLines(&lines, QCPDataRange(0, dataCount()), mParentPlot->selectionTolerance()*1.2); // optimized lines outside axis rect shouldn't respond to clicks at the edge, so use 1.2*tolerance as pen width
23942 for (int i=0; i<lines.size()-1; ++i)
23943 {
23944 double currentDistSqr = QCPVector2D(pixelPoint).distanceSquaredToLine(lines.at(i), lines.at(i+1));
23945 if (currentDistSqr < minDistSqr)
23946 minDistSqr = currentDistSqr;
23947 }
23948 }
23949
23950 return qSqrt(minDistSqr);
23951}
23952/* end of 'src/plottables/plottable-curve.cpp' */
23953
23954
23955/* including file 'src/plottables/plottable-bars.cpp' */
23956/* modified 2022-11-06T12:45:56, size 43907 */
23957
23958
23959////////////////////////////////////////////////////////////////////////////////////////////////////
23960//////////////////// QCPBarsGroup
23961////////////////////////////////////////////////////////////////////////////////////////////////////
23962
23963/*! \class QCPBarsGroup
23964 \brief Groups multiple QCPBars together so they appear side by side
23965
23966 \image html QCPBarsGroup.png
23967
23968 When showing multiple QCPBars in one plot which have bars at identical keys, it may be desirable
23969 to have them appearing next to each other at each key. This is what adding the respective QCPBars
23970 plottables to a QCPBarsGroup achieves. (An alternative approach is to stack them on top of each
23971 other, see \ref QCPBars::moveAbove.)
23972
23973 \section qcpbarsgroup-usage Usage
23974
23975 To add a QCPBars plottable to the group, create a new group and then add the respective bars
23976 intances:
23977 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpbarsgroup-creation
23978 Alternatively to appending to the group like shown above, you can also set the group on the
23979 QCPBars plottable via \ref QCPBars::setBarsGroup.
23980
23981 The spacing between the bars can be configured via \ref setSpacingType and \ref setSpacing. The
23982 bars in this group appear in the plot in the order they were appended. To insert a bars plottable
23983 at a certain index position, or to reposition a bars plottable which is already in the group, use
23984 \ref insert.
23985
23986 To remove specific bars from the group, use either \ref remove or call \ref
23987 QCPBars::setBarsGroup "QCPBars::setBarsGroup(0)" on the respective bars plottable.
23988
23989 To clear the entire group, call \ref clear, or simply delete the group.
23990
23991 \section qcpbarsgroup-example Example
23992
23993 The image above is generated with the following code:
23994 \snippet documentation/doc-image-generator/mainwindow.cpp qcpbarsgroup-example
23995*/
23996
23997/* start of documentation of inline functions */
23998
23999/*! \fn QList<QCPBars*> QCPBarsGroup::bars() const
24000
24001 Returns all bars currently in this group.
24002
24003 \see bars(int index)
24004*/
24005
24006/*! \fn int QCPBarsGroup::size() const
24007
24008 Returns the number of QCPBars plottables that are part of this group.
24009
24010*/
24011
24012/*! \fn bool QCPBarsGroup::isEmpty() const
24013
24014 Returns whether this bars group is empty.
24015
24016 \see size
24017*/
24018
24019/*! \fn bool QCPBarsGroup::contains(QCPBars *bars)
24020
24021 Returns whether the specified \a bars plottable is part of this group.
24022
24023*/
24024
24025/* end of documentation of inline functions */
24026
24027/*!
24028 Constructs a new bars group for the specified QCustomPlot instance.
24029*/
24031 QObject(parentPlot),
24032 mParentPlot(parentPlot),
24033 mSpacingType(stAbsolute),
24034 mSpacing(4)
24035{
24036}
24037
24038QCPBarsGroup::~QCPBarsGroup()
24039{
24040 clear();
24041}
24042
24043/*!
24044 Sets how the spacing between adjacent bars is interpreted. See \ref SpacingType.
24045
24046 The actual spacing can then be specified with \ref setSpacing.
24047
24048 \see setSpacing
24049*/
24051{
24052 mSpacingType = spacingType;
24053}
24054
24055/*!
24056 Sets the spacing between adjacent bars. What the number passed as \a spacing actually means, is
24057 defined by the current \ref SpacingType, which can be set with \ref setSpacingType.
24058
24059 \see setSpacingType
24060*/
24061void QCPBarsGroup::setSpacing(double spacing)
24062{
24063 mSpacing = spacing;
24064}
24065
24066/*!
24067 Returns the QCPBars instance with the specified \a index in this group. If no such QCPBars
24068 exists, returns \c nullptr.
24069
24070 \see bars(), size
24071*/
24073{
24074 if (index >= 0 && index < mBars.size())
24075 {
24076 return mBars.at(index);
24077 } else
24078 {
24079 qDebug() << Q_FUNC_INFO << "index out of bounds:" << index;
24080 return nullptr;
24081 }
24082}
24083
24084/*!
24085 Removes all QCPBars plottables from this group.
24086
24087 \see isEmpty
24088*/
24090{
24091 const QList<QCPBars*> oldBars = mBars;
24092 foreach (QCPBars *bars, oldBars)
24093 bars->setBarsGroup(nullptr); // removes itself from mBars via removeBars
24094}
24095
24096/*!
24097 Adds the specified \a bars plottable to this group. Alternatively, you can also use \ref
24098 QCPBars::setBarsGroup on the \a bars instance.
24099
24100 \see insert, remove
24101*/
24103{
24104 if (!bars)
24105 {
24106 qDebug() << Q_FUNC_INFO << "bars is 0";
24107 return;
24108 }
24109
24110 if (!mBars.contains(bars))
24111 bars->setBarsGroup(this);
24112 else
24113 qDebug() << Q_FUNC_INFO << "bars plottable is already in this bars group:" << reinterpret_cast<quintptr>(bars);
24114}
24115
24116/*!
24117 Inserts the specified \a bars plottable into this group at the specified index position \a i.
24118 This gives you full control over the ordering of the bars.
24119
24120 \a bars may already be part of this group. In that case, \a bars is just moved to the new index
24121 position.
24122
24123 \see append, remove
24124*/
24126{
24127 if (!bars)
24128 {
24129 qDebug() << Q_FUNC_INFO << "bars is 0";
24130 return;
24131 }
24132
24133 // first append to bars list normally:
24134 if (!mBars.contains(bars))
24135 bars->setBarsGroup(this);
24136 // then move to according position:
24137 mBars.move(mBars.indexOf(bars), qBound(0, i, mBars.size()-1));
24138}
24139
24140/*!
24141 Removes the specified \a bars plottable from this group.
24142
24143 \see contains, clear
24144*/
24146{
24147 if (!bars)
24148 {
24149 qDebug() << Q_FUNC_INFO << "bars is 0";
24150 return;
24151 }
24152
24153 if (mBars.contains(bars))
24154 bars->setBarsGroup(nullptr);
24155 else
24156 qDebug() << Q_FUNC_INFO << "bars plottable is not in this bars group:" << reinterpret_cast<quintptr>(bars);
24157}
24158
24159/*! \internal
24160
24161 Adds the specified \a bars to the internal mBars list of bars. This method does not change the
24162 barsGroup property on \a bars.
24163
24164 \see unregisterBars
24165*/
24167{
24168 if (!mBars.contains(bars))
24169 mBars.append(bars);
24170}
24171
24172/*! \internal
24173
24174 Removes the specified \a bars from the internal mBars list of bars. This method does not change
24175 the barsGroup property on \a bars.
24176
24177 \see registerBars
24178*/
24180{
24181 mBars.removeOne(bars);
24182}
24183
24184/*! \internal
24185
24186 Returns the pixel offset in the key dimension the specified \a bars plottable should have at the
24187 given key coordinate \a keyCoord. The offset is relative to the pixel position of the key
24188 coordinate \a keyCoord.
24189*/
24190double QCPBarsGroup::keyPixelOffset(const QCPBars *bars, double keyCoord)
24191{
24192 // find list of all base bars in case some mBars are stacked:
24193 QList<const QCPBars*> baseBars;
24194 foreach (const QCPBars *b, mBars)
24195 {
24196 while (b->barBelow())
24197 b = b->barBelow();
24198 if (!baseBars.contains(b))
24199 baseBars.append(b);
24200 }
24201 // find base bar this "bars" is stacked on:
24202 const QCPBars *thisBase = bars;
24203 while (thisBase->barBelow())
24204 thisBase = thisBase->barBelow();
24205
24206 // determine key pixel offset of this base bars considering all other base bars in this barsgroup:
24207 double result = 0;
24208 int index = baseBars.indexOf(thisBase);
24209 if (index >= 0)
24210 {
24211 if (baseBars.size() % 2 == 1 && index == (baseBars.size()-1)/2) // is center bar (int division on purpose)
24212 {
24213 return result;
24214 } else
24215 {
24216 double lowerPixelWidth, upperPixelWidth;
24217 int startIndex;
24218 int dir = (index <= (baseBars.size()-1)/2) ? -1 : 1; // if bar is to lower keys of center, dir is negative
24219 if (baseBars.size() % 2 == 0) // even number of bars
24220 {
24221 startIndex = baseBars.size()/2 + (dir < 0 ? -1 : 0);
24222 result += getPixelSpacing(baseBars.at(startIndex), keyCoord)*0.5; // half of middle spacing
24223 } else // uneven number of bars
24224 {
24225 startIndex = (baseBars.size()-1)/2+dir;
24226 baseBars.at((baseBars.size()-1)/2)->getPixelWidth(keyCoord, lowerPixelWidth, upperPixelWidth);
24227 result += qAbs(upperPixelWidth-lowerPixelWidth)*0.5; // half of center bar
24228 result += getPixelSpacing(baseBars.at((baseBars.size()-1)/2), keyCoord); // center bar spacing
24229 }
24230 for (int i = startIndex; i != index; i += dir) // add widths and spacings of bars in between center and our bars
24231 {
24232 baseBars.at(i)->getPixelWidth(keyCoord, lowerPixelWidth, upperPixelWidth);
24233 result += qAbs(upperPixelWidth-lowerPixelWidth);
24234 result += getPixelSpacing(baseBars.at(i), keyCoord);
24235 }
24236 // finally half of our bars width:
24237 baseBars.at(index)->getPixelWidth(keyCoord, lowerPixelWidth, upperPixelWidth);
24238 result += qAbs(upperPixelWidth-lowerPixelWidth)*0.5;
24239 // correct sign of result depending on orientation and direction of key axis:
24240 result *= dir*thisBase->keyAxis()->pixelOrientation();
24241 }
24242 }
24243 return result;
24244}
24245
24246/*! \internal
24247
24248 Returns the spacing in pixels which is between this \a bars and the following one, both at the
24249 key coordinate \a keyCoord.
24250
24251 \note Typically the returned value doesn't depend on \a bars or \a keyCoord. \a bars is only
24252 needed to get access to the key axis transformation and axis rect for the modes \ref
24253 stAxisRectRatio and \ref stPlotCoords. The \a keyCoord is only relevant for spacings given in
24254 \ref stPlotCoords on a logarithmic axis.
24255*/
24256double QCPBarsGroup::getPixelSpacing(const QCPBars *bars, double keyCoord)
24257{
24258 switch (mSpacingType)
24259 {
24260 case stAbsolute:
24261 {
24262 return mSpacing;
24263 }
24264 case stAxisRectRatio:
24265 {
24266 if (bars->keyAxis()->orientation() == Qt::Horizontal)
24267 return bars->keyAxis()->axisRect()->width()*mSpacing;
24268 else
24269 return bars->keyAxis()->axisRect()->height()*mSpacing;
24270 }
24271 case stPlotCoords:
24272 {
24273 double keyPixel = bars->keyAxis()->coordToPixel(keyCoord);
24274 return qAbs(bars->keyAxis()->coordToPixel(keyCoord+mSpacing)-keyPixel);
24275 }
24276 }
24277 return 0;
24278}
24279
24280
24281////////////////////////////////////////////////////////////////////////////////////////////////////
24282//////////////////// QCPBarsData
24283////////////////////////////////////////////////////////////////////////////////////////////////////
24284
24285/*! \class QCPBarsData
24286 \brief Holds the data of one single data point (one bar) for QCPBars.
24287
24288 The stored data is:
24289 \li \a key: coordinate on the key axis of this bar (this is the \a mainKey and the \a sortKey)
24290 \li \a value: height coordinate on the value axis of this bar (this is the \a mainValue)
24291
24292 The container for storing multiple data points is \ref QCPBarsDataContainer. It is a typedef for
24293 \ref QCPDataContainer with \ref QCPBarsData as the DataType template parameter. See the
24294 documentation there for an explanation regarding the data type's generic methods.
24295
24296 \see QCPBarsDataContainer
24297*/
24298
24299/* start documentation of inline functions */
24300
24301/*! \fn double QCPBarsData::sortKey() const
24302
24303 Returns the \a key member of this data point.
24304
24305 For a general explanation of what this method is good for in the context of the data container,
24306 see the documentation of \ref QCPDataContainer.
24307*/
24308
24309/*! \fn static QCPBarsData QCPBarsData::fromSortKey(double sortKey)
24310
24311 Returns a data point with the specified \a sortKey. All other members are set to zero.
24312
24313 For a general explanation of what this method is good for in the context of the data container,
24314 see the documentation of \ref QCPDataContainer.
24315*/
24316
24317/*! \fn static static bool QCPBarsData::sortKeyIsMainKey()
24318
24319 Since the member \a key is both the data point key coordinate and the data ordering parameter,
24320 this method returns true.
24321
24322 For a general explanation of what this method is good for in the context of the data container,
24323 see the documentation of \ref QCPDataContainer.
24324*/
24325
24326/*! \fn double QCPBarsData::mainKey() const
24327
24328 Returns the \a key member of this data point.
24329
24330 For a general explanation of what this method is good for in the context of the data container,
24331 see the documentation of \ref QCPDataContainer.
24332*/
24333
24334/*! \fn double QCPBarsData::mainValue() const
24335
24336 Returns the \a value member of this data point.
24337
24338 For a general explanation of what this method is good for in the context of the data container,
24339 see the documentation of \ref QCPDataContainer.
24340*/
24341
24342/*! \fn QCPRange QCPBarsData::valueRange() const
24343
24344 Returns a QCPRange with both lower and upper boundary set to \a value of this data point.
24345
24346 For a general explanation of what this method is good for in the context of the data container,
24347 see the documentation of \ref QCPDataContainer.
24348*/
24349
24350/* end documentation of inline functions */
24351
24352/*!
24353 Constructs a bar data point with key and value set to zero.
24354*/
24356 key(0),
24357 value(0)
24358{
24359}
24360
24361/*!
24362 Constructs a bar data point with the specified \a key and \a value.
24363*/
24364QCPBarsData::QCPBarsData(double key, double value) :
24365 key(key),
24366 value(value)
24367{
24368}
24369
24370
24371////////////////////////////////////////////////////////////////////////////////////////////////////
24372//////////////////// QCPBars
24373////////////////////////////////////////////////////////////////////////////////////////////////////
24374
24375/*! \class QCPBars
24376 \brief A plottable representing a bar chart in a plot.
24377
24378 \image html QCPBars.png
24379
24380 To plot data, assign it with the \ref setData or \ref addData functions.
24381
24382 \section qcpbars-appearance Changing the appearance
24383
24384 The appearance of the bars is determined by the pen and the brush (\ref setPen, \ref setBrush).
24385 The width of the individual bars can be controlled with \ref setWidthType and \ref setWidth.
24386
24387 Bar charts are stackable. This means, two QCPBars plottables can be placed on top of each other
24388 (see \ref QCPBars::moveAbove). So when two bars are at the same key position, they will appear
24389 stacked.
24390
24391 If you would like to group multiple QCPBars plottables together so they appear side by side as
24392 shown below, use QCPBarsGroup.
24393
24394 \image html QCPBarsGroup.png
24395
24396 \section qcpbars-usage Usage
24397
24398 Like all data representing objects in QCustomPlot, the QCPBars is a plottable
24399 (QCPAbstractPlottable). So the plottable-interface of QCustomPlot applies
24400 (QCustomPlot::plottable, QCustomPlot::removePlottable, etc.)
24401
24402 Usually, you first create an instance:
24403 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpbars-creation-1
24404 which registers it with the QCustomPlot instance of the passed axes. Note that this QCustomPlot instance takes
24405 ownership of the plottable, so do not delete it manually but use QCustomPlot::removePlottable() instead.
24406 The newly created plottable can be modified, e.g.:
24407 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpbars-creation-2
24408*/
24409
24410/* start of documentation of inline functions */
24411
24412/*! \fn QSharedPointer<QCPBarsDataContainer> QCPBars::data() const
24413
24414 Returns a shared pointer to the internal data storage of type \ref QCPBarsDataContainer. You may
24415 use it to directly manipulate the data, which may be more convenient and faster than using the
24416 regular \ref setData or \ref addData methods.
24417*/
24418
24419/*! \fn QCPBars *QCPBars::barBelow() const
24420 Returns the bars plottable that is directly below this bars plottable.
24421 If there is no such plottable, returns \c nullptr.
24422
24423 \see barAbove, moveBelow, moveAbove
24424*/
24425
24426/*! \fn QCPBars *QCPBars::barAbove() const
24427 Returns the bars plottable that is directly above this bars plottable.
24428 If there is no such plottable, returns \c nullptr.
24429
24430 \see barBelow, moveBelow, moveAbove
24431*/
24432
24433/* end of documentation of inline functions */
24434
24435/*!
24436 Constructs a bar chart which uses \a keyAxis as its key axis ("x") and \a valueAxis as its value
24437 axis ("y"). \a keyAxis and \a valueAxis must reside in the same QCustomPlot instance and not have
24438 the same orientation. If either of these restrictions is violated, a corresponding message is
24439 printed to the debug output (qDebug), the construction is not aborted, though.
24440
24441 The created QCPBars is automatically registered with the QCustomPlot instance inferred from \a
24442 keyAxis. This QCustomPlot instance takes ownership of the QCPBars, so do not delete it manually
24443 but use QCustomPlot::removePlottable() instead.
24444*/
24445QCPBars::QCPBars(QCPAxis *keyAxis, QCPAxis *valueAxis) :
24446 QCPAbstractPlottable1D<QCPBarsData>(keyAxis, valueAxis),
24447 mWidth(0.75),
24448 mWidthType(wtPlotCoords),
24449 mBarsGroup(nullptr),
24450 mBaseValue(0),
24451 mStackingGap(1)
24452{
24453 // modify inherited properties from abstract plottable:
24454 mPen.setColor(Qt::blue);
24455 mPen.setStyle(Qt::SolidLine);
24456 mBrush.setColor(QColor(40, 50, 255, 30));
24457 mBrush.setStyle(Qt::SolidPattern);
24458 mSelectionDecorator->setBrush(QBrush(QColor(160, 160, 255)));
24459}
24460
24461QCPBars::~QCPBars()
24462{
24463 setBarsGroup(nullptr);
24464 if (mBarBelow || mBarAbove)
24465 connectBars(mBarBelow.data(), mBarAbove.data()); // take this bar out of any stacking
24466}
24467
24468/*! \overload
24469
24470 Replaces the current data container with the provided \a data container.
24471
24472 Since a QSharedPointer is used, multiple QCPBars may share the same data container safely.
24473 Modifying the data in the container will then affect all bars that share the container. Sharing
24474 can be achieved by simply exchanging the data containers wrapped in shared pointers:
24475 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpbars-datasharing-1
24476
24477 If you do not wish to share containers, but create a copy from an existing container, rather use
24478 the \ref QCPDataContainer<DataType>::set method on the bar's data container directly:
24479 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpbars-datasharing-2
24480
24481 \see addData
24482*/
24484{
24485 mDataContainer = data;
24486}
24487
24488/*! \overload
24489
24490 Replaces the current data with the provided points in \a keys and \a values. The provided
24491 vectors should have equal length. Else, the number of added points will be the size of the
24492 smallest vector.
24493
24494 If you can guarantee that the passed data points are sorted by \a keys in ascending order, you
24495 can set \a alreadySorted to true, to improve performance by saving a sorting run.
24496
24497 \see addData
24498*/
24499void QCPBars::setData(const QVector<double> &keys, const QVector<double> &values, bool alreadySorted)
24500{
24501 mDataContainer->clear();
24502 addData(keys, values, alreadySorted);
24503}
24504
24505/*!
24506 Sets the width of the bars.
24507
24508 How the number passed as \a width is interpreted (e.g. screen pixels, plot coordinates,...),
24509 depends on the currently set width type, see \ref setWidthType and \ref WidthType.
24510*/
24511void QCPBars::setWidth(double width)
24512{
24513 mWidth = width;
24514}
24515
24516/*!
24517 Sets how the width of the bars is defined. See the documentation of \ref WidthType for an
24518 explanation of the possible values for \a widthType.
24519
24520 The default value is \ref wtPlotCoords.
24521
24522 \see setWidth
24523*/
24525{
24526 mWidthType = widthType;
24527}
24528
24529/*!
24530 Sets to which QCPBarsGroup this QCPBars instance belongs to. Alternatively, you can also use \ref
24531 QCPBarsGroup::append.
24532
24533 To remove this QCPBars from any group, set \a barsGroup to \c nullptr.
24534*/
24536{
24537 // deregister at old group:
24538 if (mBarsGroup)
24539 mBarsGroup->unregisterBars(this);
24540 mBarsGroup = barsGroup;
24541 // register at new group:
24542 if (mBarsGroup)
24543 mBarsGroup->registerBars(this);
24544}
24545
24546/*!
24547 Sets the base value of this bars plottable.
24548
24549 The base value defines where on the value coordinate the bars start. How far the bars extend from
24550 the base value is given by their individual value data. For example, if the base value is set to
24551 1, a bar with data value 2 will have its lowest point at value coordinate 1 and highest point at
24552 3.
24553
24554 For stacked bars, only the base value of the bottom-most QCPBars has meaning.
24555
24556 The default base value is 0.
24557*/
24558void QCPBars::setBaseValue(double baseValue)
24559{
24560 mBaseValue = baseValue;
24561}
24562
24563/*!
24564 If this bars plottable is stacked on top of another bars plottable (\ref moveAbove), this method
24565 allows specifying a distance in \a pixels, by which the drawn bar rectangles will be separated by
24566 the bars below it.
24567*/
24568void QCPBars::setStackingGap(double pixels)
24569{
24570 mStackingGap = pixels;
24571}
24572
24573/*! \overload
24574
24575 Adds the provided points in \a keys and \a values to the current data. The provided vectors
24576 should have equal length. Else, the number of added points will be the size of the smallest
24577 vector.
24578
24579 If you can guarantee that the passed data points are sorted by \a keys in ascending order, you
24580 can set \a alreadySorted to true, to improve performance by saving a sorting run.
24581
24582 Alternatively, you can also access and modify the data directly via the \ref data method, which
24583 returns a pointer to the internal data container.
24584*/
24585void QCPBars::addData(const QVector<double> &keys, const QVector<double> &values, bool alreadySorted)
24586{
24587 if (keys.size() != values.size())
24588 qDebug() << Q_FUNC_INFO << "keys and values have different sizes:" << keys.size() << values.size();
24589 const int n = qMin(keys.size(), values.size());
24590 QVector<QCPBarsData> tempData(n);
24591 QVector<QCPBarsData>::iterator it = tempData.begin();
24592 const QVector<QCPBarsData>::iterator itEnd = tempData.end();
24593 int i = 0;
24594 while (it != itEnd)
24595 {
24596 it->key = keys[i];
24597 it->value = values[i];
24598 ++it;
24599 ++i;
24600 }
24601 mDataContainer->add(tempData, alreadySorted); // don't modify tempData beyond this to prevent copy on write
24602}
24603
24604/*! \overload
24605 Adds the provided data point as \a key and \a value to the current data.
24606
24607 Alternatively, you can also access and modify the data directly via the \ref data method, which
24608 returns a pointer to the internal data container.
24609*/
24610void QCPBars::addData(double key, double value)
24611{
24612 mDataContainer->add(QCPBarsData(key, value));
24613}
24614
24615/*!
24616 Moves this bars plottable below \a bars. In other words, the bars of this plottable will appear
24617 below the bars of \a bars. The move target \a bars must use the same key and value axis as this
24618 plottable.
24619
24620 Inserting into and removing from existing bar stacking is handled gracefully. If \a bars already
24621 has a bars object below itself, this bars object is inserted between the two. If this bars object
24622 is already between two other bars, the two other bars will be stacked on top of each other after
24623 the operation.
24624
24625 To remove this bars plottable from any stacking, set \a bars to \c nullptr.
24626
24627 \see moveBelow, barAbove, barBelow
24628*/
24630{
24631 if (bars == this) return;
24632 if (bars && (bars->keyAxis() != mKeyAxis.data() || bars->valueAxis() != mValueAxis.data()))
24633 {
24634 qDebug() << Q_FUNC_INFO << "passed QCPBars* doesn't have same key and value axis as this QCPBars";
24635 return;
24636 }
24637 // remove from stacking:
24638 connectBars(mBarBelow.data(), mBarAbove.data()); // Note: also works if one (or both) of them is 0
24639 // if new bar given, insert this bar below it:
24640 if (bars)
24641 {
24642 if (bars->mBarBelow)
24643 connectBars(bars->mBarBelow.data(), this);
24644 connectBars(this, bars);
24645 }
24646}
24647
24648/*!
24649 Moves this bars plottable above \a bars. In other words, the bars of this plottable will appear
24650 above the bars of \a bars. The move target \a bars must use the same key and value axis as this
24651 plottable.
24652
24653 Inserting into and removing from existing bar stacking is handled gracefully. If \a bars already
24654 has a bars object above itself, this bars object is inserted between the two. If this bars object
24655 is already between two other bars, the two other bars will be stacked on top of each other after
24656 the operation.
24657
24658 To remove this bars plottable from any stacking, set \a bars to \c nullptr.
24659
24660 \see moveBelow, barBelow, barAbove
24661*/
24663{
24664 if (bars == this) return;
24665 if (bars && (bars->keyAxis() != mKeyAxis.data() || bars->valueAxis() != mValueAxis.data()))
24666 {
24667 qDebug() << Q_FUNC_INFO << "passed QCPBars* doesn't have same key and value axis as this QCPBars";
24668 return;
24669 }
24670 // remove from stacking:
24671 connectBars(mBarBelow.data(), mBarAbove.data()); // Note: also works if one (or both) of them is 0
24672 // if new bar given, insert this bar above it:
24673 if (bars)
24674 {
24675 if (bars->mBarAbove)
24676 connectBars(this, bars->mBarAbove.data());
24677 connectBars(bars, this);
24678 }
24679}
24680
24681/*!
24682 \copydoc QCPPlottableInterface1D::selectTestRect
24683*/
24684QCPDataSelection QCPBars::selectTestRect(const QRectF &rect, bool onlySelectable) const
24685{
24686 QCPDataSelection result;
24687 if ((onlySelectable && mSelectable == QCP::stNone) || mDataContainer->isEmpty())
24688 return result;
24689 if (!mKeyAxis || !mValueAxis)
24690 return result;
24691
24692 QCPBarsDataContainer::const_iterator visibleBegin, visibleEnd;
24693 getVisibleDataBounds(visibleBegin, visibleEnd);
24694
24695 for (QCPBarsDataContainer::const_iterator it=visibleBegin; it!=visibleEnd; ++it)
24696 {
24697 if (rect.intersects(getBarRect(it->key, it->value)))
24698 result.addDataRange(QCPDataRange(int(it-mDataContainer->constBegin()), int(it-mDataContainer->constBegin()+1)), false);
24699 }
24700 result.simplify();
24701 return result;
24702}
24703
24704/*!
24705 Implements a selectTest specific to this plottable's point geometry.
24706
24707 If \a details is not 0, it will be set to a \ref QCPDataSelection, describing the closest data
24708 point to \a pos.
24709
24710 \seebaseclassmethod \ref QCPAbstractPlottable::selectTest
24711*/
24712double QCPBars::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
24713{
24714 Q_UNUSED(details)
24715 if ((onlySelectable && mSelectable == QCP::stNone) || mDataContainer->isEmpty())
24716 return -1;
24717 if (!mKeyAxis || !mValueAxis)
24718 return -1;
24719
24720 if (mKeyAxis.data()->axisRect()->rect().contains(pos.toPoint()) || mParentPlot->interactions().testFlag(QCP::iSelectPlottablesBeyondAxisRect))
24721 {
24722 // get visible data range:
24723 QCPBarsDataContainer::const_iterator visibleBegin, visibleEnd;
24724 getVisibleDataBounds(visibleBegin, visibleEnd);
24725 for (QCPBarsDataContainer::const_iterator it=visibleBegin; it!=visibleEnd; ++it)
24726 {
24727 if (getBarRect(it->key, it->value).contains(pos))
24728 {
24729 if (details)
24730 {
24731 int pointIndex = int(it-mDataContainer->constBegin());
24732 details->setValue(QCPDataSelection(QCPDataRange(pointIndex, pointIndex+1)));
24733 }
24734 return mParentPlot->selectionTolerance()*0.99;
24735 }
24736 }
24737 }
24738 return -1;
24739}
24740
24741/* inherits documentation from base class */
24742QCPRange QCPBars::getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain) const
24743{
24744 /* Note: If this QCPBars uses absolute pixels as width (or is in a QCPBarsGroup with spacing in
24745 absolute pixels), using this method to adapt the key axis range to fit the bars into the
24746 currently visible axis range will not work perfectly. Because in the moment the axis range is
24747 changed to the new range, the fixed pixel widths/spacings will represent different coordinate
24748 spans than before, which in turn would require a different key range to perfectly fit, and so on.
24749 The only solution would be to iteratively approach the perfect fitting axis range, but the
24750 mismatch isn't large enough in most applications, to warrant this here. If a user does need a
24751 better fit, he should call the corresponding axis rescale multiple times in a row.
24752 */
24753 QCPRange range;
24754 range = mDataContainer->keyRange(foundRange, inSignDomain);
24755
24756 // determine exact range of bars by including bar width and barsgroup offset:
24757 if (foundRange && mKeyAxis)
24758 {
24759 double lowerPixelWidth, upperPixelWidth, keyPixel;
24760 // lower range bound:
24761 getPixelWidth(range.lower, lowerPixelWidth, upperPixelWidth);
24762 keyPixel = mKeyAxis.data()->coordToPixel(range.lower) + lowerPixelWidth;
24763 if (mBarsGroup)
24764 keyPixel += mBarsGroup->keyPixelOffset(this, range.lower);
24765 const double lowerCorrected = mKeyAxis.data()->pixelToCoord(keyPixel);
24766 if (!qIsNaN(lowerCorrected) && qIsFinite(lowerCorrected) && range.lower > lowerCorrected)
24767 range.lower = lowerCorrected;
24768 // upper range bound:
24769 getPixelWidth(range.upper, lowerPixelWidth, upperPixelWidth);
24770 keyPixel = mKeyAxis.data()->coordToPixel(range.upper) + upperPixelWidth;
24771 if (mBarsGroup)
24772 keyPixel += mBarsGroup->keyPixelOffset(this, range.upper);
24773 const double upperCorrected = mKeyAxis.data()->pixelToCoord(keyPixel);
24774 if (!qIsNaN(upperCorrected) && qIsFinite(upperCorrected) && range.upper < upperCorrected)
24775 range.upper = upperCorrected;
24776 }
24777 return range;
24778}
24779
24780/* inherits documentation from base class */
24781QCPRange QCPBars::getValueRange(bool &foundRange, QCP::SignDomain inSignDomain, const QCPRange &inKeyRange) const
24782{
24783 // Note: can't simply use mDataContainer->valueRange here because we need to
24784 // take into account bar base value and possible stacking of multiple bars
24785 QCPRange range;
24786 range.lower = mBaseValue;
24787 range.upper = mBaseValue;
24788 bool haveLower = true; // set to true, because baseValue should always be visible in bar charts
24789 bool haveUpper = true; // set to true, because baseValue should always be visible in bar charts
24790 QCPBarsDataContainer::const_iterator itBegin = mDataContainer->constBegin();
24791 QCPBarsDataContainer::const_iterator itEnd = mDataContainer->constEnd();
24792 if (inKeyRange != QCPRange())
24793 {
24794 itBegin = mDataContainer->findBegin(inKeyRange.lower, false);
24795 itEnd = mDataContainer->findEnd(inKeyRange.upper, false);
24796 }
24797 for (QCPBarsDataContainer::const_iterator it = itBegin; it != itEnd; ++it)
24798 {
24799 const double current = it->value + getStackedBaseValue(it->key, it->value >= 0);
24800 if (qIsNaN(current)) continue;
24801 if (inSignDomain == QCP::sdBoth || (inSignDomain == QCP::sdNegative && current < 0) || (inSignDomain == QCP::sdPositive && current > 0))
24802 {
24803 if (current < range.lower || !haveLower)
24804 {
24805 range.lower = current;
24806 haveLower = true;
24807 }
24808 if (current > range.upper || !haveUpper)
24809 {
24810 range.upper = current;
24811 haveUpper = true;
24812 }
24813 }
24814 }
24815
24816 foundRange = true; // return true because bar charts always have the 0-line visible
24817 return range;
24818}
24819
24820/* inherits documentation from base class */
24822{
24823 if (index >= 0 && index < mDataContainer->size())
24824 {
24825 QCPAxis *keyAxis = mKeyAxis.data();
24826 QCPAxis *valueAxis = mValueAxis.data();
24827 if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return {}; }
24828
24829 const QCPDataContainer<QCPBarsData>::const_iterator it = mDataContainer->constBegin()+index;
24830 const double valuePixel = valueAxis->coordToPixel(getStackedBaseValue(it->key, it->value >= 0) + it->value);
24831 const double keyPixel = keyAxis->coordToPixel(it->key) + (mBarsGroup ? mBarsGroup->keyPixelOffset(this, it->key) : 0);
24832 if (keyAxis->orientation() == Qt::Horizontal)
24833 return {keyPixel, valuePixel};
24834 else
24835 return {valuePixel, keyPixel};
24836 } else
24837 {
24838 qDebug() << Q_FUNC_INFO << "Index out of bounds" << index;
24839 return {};
24840 }
24841}
24842
24843/* inherits documentation from base class */
24845{
24846 if (!mKeyAxis || !mValueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
24847 if (mDataContainer->isEmpty()) return;
24848
24849 QCPBarsDataContainer::const_iterator visibleBegin, visibleEnd;
24850 getVisibleDataBounds(visibleBegin, visibleEnd);
24851
24852 // loop over and draw segments of unselected/selected data:
24853 QList<QCPDataRange> selectedSegments, unselectedSegments, allSegments;
24854 getDataSegments(selectedSegments, unselectedSegments);
24855 allSegments << unselectedSegments << selectedSegments;
24856 for (int i=0; i<allSegments.size(); ++i)
24857 {
24858 bool isSelectedSegment = i >= unselectedSegments.size();
24859 QCPBarsDataContainer::const_iterator begin = visibleBegin;
24860 QCPBarsDataContainer::const_iterator end = visibleEnd;
24861 mDataContainer->limitIteratorsToDataRange(begin, end, allSegments.at(i));
24862 if (begin == end)
24863 continue;
24864
24865 for (QCPBarsDataContainer::const_iterator it=begin; it!=end; ++it)
24866 {
24867 // check data validity if flag set:
24868#ifdef QCUSTOMPLOT_CHECK_DATA
24869 if (QCP::isInvalidData(it->key, it->value))
24870 qDebug() << Q_FUNC_INFO << "Data point at" << it->key << "of drawn range invalid." << "Plottable name:" << name();
24871#endif
24872 // draw bar:
24873 if (isSelectedSegment && mSelectionDecorator)
24874 {
24875 mSelectionDecorator->applyBrush(painter);
24876 mSelectionDecorator->applyPen(painter);
24877 } else
24878 {
24879 painter->setBrush(mBrush);
24880 painter->setPen(mPen);
24881 }
24883 painter->drawPolygon(getBarRect(it->key, it->value));
24884 }
24885 }
24886
24887 // draw other selection decoration that isn't just line/scatter pens and brushes:
24888 if (mSelectionDecorator)
24889 mSelectionDecorator->drawDecoration(painter, selection());
24890}
24891
24892/* inherits documentation from base class */
24893void QCPBars::drawLegendIcon(QCPPainter *painter, const QRectF &rect) const
24894{
24895 // draw filled rect:
24897 painter->setBrush(mBrush);
24898 painter->setPen(mPen);
24899 QRectF r = QRectF(0, 0, rect.width()*0.67, rect.height()*0.67);
24900 r.moveCenter(rect.center());
24901 painter->drawRect(r);
24902}
24903
24904/*! \internal
24905
24906 called by \ref draw to determine which data (key) range is visible at the current key axis range
24907 setting, so only that needs to be processed. It also takes into account the bar width.
24908
24909 \a begin returns an iterator to the lowest data point that needs to be taken into account when
24910 plotting. Note that in order to get a clean plot all the way to the edge of the axis rect, \a
24911 lower may still be just outside the visible range.
24912
24913 \a end returns an iterator one higher than the highest visible data point. Same as before, \a end
24914 may also lie just outside of the visible range.
24915
24916 if the plottable contains no data, both \a begin and \a end point to constEnd.
24917*/
24919{
24920 if (!mKeyAxis)
24921 {
24922 qDebug() << Q_FUNC_INFO << "invalid key axis";
24923 begin = mDataContainer->constEnd();
24924 end = mDataContainer->constEnd();
24925 return;
24926 }
24927 if (mDataContainer->isEmpty())
24928 {
24929 begin = mDataContainer->constEnd();
24930 end = mDataContainer->constEnd();
24931 return;
24932 }
24933
24934 // get visible data range as QMap iterators
24935 begin = mDataContainer->findBegin(mKeyAxis.data()->range().lower);
24936 end = mDataContainer->findEnd(mKeyAxis.data()->range().upper);
24937 double lowerPixelBound = mKeyAxis.data()->coordToPixel(mKeyAxis.data()->range().lower);
24938 double upperPixelBound = mKeyAxis.data()->coordToPixel(mKeyAxis.data()->range().upper);
24939 bool isVisible = false;
24940 // walk left from begin to find lower bar that actually is completely outside visible pixel range:
24942 while (it != mDataContainer->constBegin())
24943 {
24944 --it;
24945 const QRectF barRect = getBarRect(it->key, it->value);
24946 if (mKeyAxis.data()->orientation() == Qt::Horizontal)
24947 isVisible = ((!mKeyAxis.data()->rangeReversed() && barRect.right() >= lowerPixelBound) || (mKeyAxis.data()->rangeReversed() && barRect.left() <= lowerPixelBound));
24948 else // keyaxis is vertical
24949 isVisible = ((!mKeyAxis.data()->rangeReversed() && barRect.top() <= lowerPixelBound) || (mKeyAxis.data()->rangeReversed() && barRect.bottom() >= lowerPixelBound));
24950 if (isVisible)
24951 begin = it;
24952 else
24953 break;
24954 }
24955 // walk right from ubound to find upper bar that actually is completely outside visible pixel range:
24956 it = end;
24957 while (it != mDataContainer->constEnd())
24958 {
24959 const QRectF barRect = getBarRect(it->key, it->value);
24960 if (mKeyAxis.data()->orientation() == Qt::Horizontal)
24961 isVisible = ((!mKeyAxis.data()->rangeReversed() && barRect.left() <= upperPixelBound) || (mKeyAxis.data()->rangeReversed() && barRect.right() >= upperPixelBound));
24962 else // keyaxis is vertical
24963 isVisible = ((!mKeyAxis.data()->rangeReversed() && barRect.bottom() >= upperPixelBound) || (mKeyAxis.data()->rangeReversed() && barRect.top() <= upperPixelBound));
24964 if (isVisible)
24965 end = it+1;
24966 else
24967 break;
24968 ++it;
24969 }
24970}
24971
24972/*! \internal
24973
24974 Returns the rect in pixel coordinates of a single bar with the specified \a key and \a value. The
24975 rect is shifted according to the bar stacking (see \ref moveAbove) and base value (see \ref
24976 setBaseValue), and to have non-overlapping border lines with the bars stacked below.
24977*/
24978QRectF QCPBars::getBarRect(double key, double value) const
24979{
24980 QCPAxis *keyAxis = mKeyAxis.data();
24981 QCPAxis *valueAxis = mValueAxis.data();
24982 if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return {}; }
24983
24984 double lowerPixelWidth, upperPixelWidth;
24985 getPixelWidth(key, lowerPixelWidth, upperPixelWidth);
24986 double base = getStackedBaseValue(key, value >= 0);
24987 double basePixel = valueAxis->coordToPixel(base);
24988 double valuePixel = valueAxis->coordToPixel(base+value);
24989 double keyPixel = keyAxis->coordToPixel(key);
24990 if (mBarsGroup)
24991 keyPixel += mBarsGroup->keyPixelOffset(this, key);
24992 double bottomOffset = (mBarBelow && mPen != Qt::NoPen ? 1 : 0)*(mPen.isCosmetic() ? 1 : mPen.widthF());
24993 bottomOffset += mBarBelow ? mStackingGap : 0;
24994 bottomOffset *= (value<0 ? -1 : 1)*valueAxis->pixelOrientation();
24995 if (qAbs(valuePixel-basePixel) <= qAbs(bottomOffset))
24996 bottomOffset = valuePixel-basePixel;
24997 if (keyAxis->orientation() == Qt::Horizontal)
24998 {
24999 return QRectF(QPointF(keyPixel+lowerPixelWidth, valuePixel), QPointF(keyPixel+upperPixelWidth, basePixel+bottomOffset)).normalized();
25000 } else
25001 {
25002 return QRectF(QPointF(basePixel+bottomOffset, keyPixel+lowerPixelWidth), QPointF(valuePixel, keyPixel+upperPixelWidth)).normalized();
25003 }
25004}
25005
25006/*! \internal
25007
25008 This function is used to determine the width of the bar at coordinate \a key, according to the
25009 specified width (\ref setWidth) and width type (\ref setWidthType).
25010
25011 The output parameters \a lower and \a upper return the number of pixels the bar extends to lower
25012 and higher keys, relative to the \a key coordinate (so with a non-reversed horizontal axis, \a
25013 lower is negative and \a upper positive).
25014*/
25015void QCPBars::getPixelWidth(double key, double &lower, double &upper) const
25016{
25017 lower = 0;
25018 upper = 0;
25019 switch (mWidthType)
25020 {
25021 case wtAbsolute:
25022 {
25023 upper = mWidth*0.5*mKeyAxis.data()->pixelOrientation();
25024 lower = -upper;
25025 break;
25026 }
25027 case wtAxisRectRatio:
25028 {
25029 if (mKeyAxis && mKeyAxis.data()->axisRect())
25030 {
25031 if (mKeyAxis.data()->orientation() == Qt::Horizontal)
25032 upper = mKeyAxis.data()->axisRect()->width()*mWidth*0.5*mKeyAxis.data()->pixelOrientation();
25033 else
25034 upper = mKeyAxis.data()->axisRect()->height()*mWidth*0.5*mKeyAxis.data()->pixelOrientation();
25035 lower = -upper;
25036 } else
25037 qDebug() << Q_FUNC_INFO << "No key axis or axis rect defined";
25038 break;
25039 }
25040 case wtPlotCoords:
25041 {
25042 if (mKeyAxis)
25043 {
25044 double keyPixel = mKeyAxis.data()->coordToPixel(key);
25045 upper = mKeyAxis.data()->coordToPixel(key+mWidth*0.5)-keyPixel;
25046 lower = mKeyAxis.data()->coordToPixel(key-mWidth*0.5)-keyPixel;
25047 // no need to qSwap(lower, higher) when range reversed, because higher/lower are gained by
25048 // coordinate transform which includes range direction
25049 } else
25050 qDebug() << Q_FUNC_INFO << "No key axis defined";
25051 break;
25052 }
25053 }
25054}
25055
25056/*! \internal
25057
25058 This function is called to find at which value to start drawing the base of a bar at \a key, when
25059 it is stacked on top of another QCPBars (e.g. with \ref moveAbove).
25060
25061 positive and negative bars are separated per stack (positive are stacked above baseValue upwards,
25062 negative are stacked below baseValue downwards). This can be indicated with \a positive. So if the
25063 bar for which we need the base value is negative, set \a positive to false.
25064*/
25065double QCPBars::getStackedBaseValue(double key, bool positive) const
25066{
25067 if (mBarBelow)
25068 {
25069 double max = 0; // don't initialize with mBaseValue here because only base value of bottom-most bar has meaning in a bar stack
25070 // find bars of mBarBelow that are approximately at key and find largest one:
25071 double epsilon = qAbs(key)*(sizeof(key)==4 ? 1e-6 : 1e-14); // should be safe even when changed to use float at some point
25072 if (key == 0)
25073 epsilon = (sizeof(key)==4 ? 1e-6 : 1e-14);
25074 QCPBarsDataContainer::const_iterator it = mBarBelow.data()->mDataContainer->findBegin(key-epsilon);
25075 QCPBarsDataContainer::const_iterator itEnd = mBarBelow.data()->mDataContainer->findEnd(key+epsilon);
25076 while (it != itEnd)
25077 {
25078 if (it->key > key-epsilon && it->key < key+epsilon)
25079 {
25080 if ((positive && it->value > max) ||
25081 (!positive && it->value < max))
25082 max = it->value;
25083 }
25084 ++it;
25085 }
25086 // recurse down the bar-stack to find the total height:
25087 return max + mBarBelow.data()->getStackedBaseValue(key, positive);
25088 } else
25089 return mBaseValue;
25090}
25091
25092/*! \internal
25093
25094 Connects \a below and \a above to each other via their mBarAbove/mBarBelow properties. The bar(s)
25095 currently above lower and below upper will become disconnected to lower/upper.
25096
25097 If lower is zero, upper will be disconnected at the bottom.
25098 If upper is zero, lower will be disconnected at the top.
25099*/
25101{
25102 if (!lower && !upper) return;
25103
25104 if (!lower) // disconnect upper at bottom
25105 {
25106 // disconnect old bar below upper:
25107 if (upper->mBarBelow && upper->mBarBelow.data()->mBarAbove.data() == upper)
25108 upper->mBarBelow.data()->mBarAbove = nullptr;
25109 upper->mBarBelow = nullptr;
25110 } else if (!upper) // disconnect lower at top
25111 {
25112 // disconnect old bar above lower:
25113 if (lower->mBarAbove && lower->mBarAbove.data()->mBarBelow.data() == lower)
25114 lower->mBarAbove.data()->mBarBelow = nullptr;
25115 lower->mBarAbove = nullptr;
25116 } else // connect lower and upper
25117 {
25118 // disconnect old bar above lower:
25119 if (lower->mBarAbove && lower->mBarAbove.data()->mBarBelow.data() == lower)
25120 lower->mBarAbove.data()->mBarBelow = nullptr;
25121 // disconnect old bar below upper:
25122 if (upper->mBarBelow && upper->mBarBelow.data()->mBarAbove.data() == upper)
25123 upper->mBarBelow.data()->mBarAbove = nullptr;
25124 lower->mBarAbove = upper;
25125 upper->mBarBelow = lower;
25126 }
25127}
25128/* end of 'src/plottables/plottable-bars.cpp' */
25129
25130
25131/* including file 'src/plottables/plottable-statisticalbox.cpp' */
25132/* modified 2022-11-06T12:45:57, size 28951 */
25133
25134////////////////////////////////////////////////////////////////////////////////////////////////////
25135//////////////////// QCPStatisticalBoxData
25136////////////////////////////////////////////////////////////////////////////////////////////////////
25137
25138/*! \class QCPStatisticalBoxData
25139 \brief Holds the data of one single data point for QCPStatisticalBox.
25140
25141 The stored data is:
25142
25143 \li \a key: coordinate on the key axis of this data point (this is the \a mainKey and the \a sortKey)
25144
25145 \li \a minimum: the position of the lower whisker, typically the minimum measurement of the
25146 sample that's not considered an outlier.
25147
25148 \li \a lowerQuartile: the lower end of the box. The lower and the upper quartiles are the two
25149 statistical quartiles around the median of the sample, they should contain 50% of the sample
25150 data.
25151
25152 \li \a median: the value of the median mark inside the quartile box. The median separates the
25153 sample data in half (50% of the sample data is below/above the median). (This is the \a mainValue)
25154
25155 \li \a upperQuartile: the upper end of the box. The lower and the upper quartiles are the two
25156 statistical quartiles around the median of the sample, they should contain 50% of the sample
25157 data.
25158
25159 \li \a maximum: the position of the upper whisker, typically the maximum measurement of the
25160 sample that's not considered an outlier.
25161
25162 \li \a outliers: a QVector of outlier values that will be drawn as scatter points at the \a key
25163 coordinate of this data point (see \ref QCPStatisticalBox::setOutlierStyle)
25164
25165 The container for storing multiple data points is \ref QCPStatisticalBoxDataContainer. It is a
25166 typedef for \ref QCPDataContainer with \ref QCPStatisticalBoxData as the DataType template
25167 parameter. See the documentation there for an explanation regarding the data type's generic
25168 methods.
25169
25170 \see QCPStatisticalBoxDataContainer
25171*/
25172
25173/* start documentation of inline functions */
25174
25175/*! \fn double QCPStatisticalBoxData::sortKey() const
25176
25177 Returns the \a key member of this data point.
25178
25179 For a general explanation of what this method is good for in the context of the data container,
25180 see the documentation of \ref QCPDataContainer.
25181*/
25182
25183/*! \fn static QCPStatisticalBoxData QCPStatisticalBoxData::fromSortKey(double sortKey)
25184
25185 Returns a data point with the specified \a sortKey. All other members are set to zero.
25186
25187 For a general explanation of what this method is good for in the context of the data container,
25188 see the documentation of \ref QCPDataContainer.
25189*/
25190
25191/*! \fn static static bool QCPStatisticalBoxData::sortKeyIsMainKey()
25192
25193 Since the member \a key is both the data point key coordinate and the data ordering parameter,
25194 this method returns true.
25195
25196 For a general explanation of what this method is good for in the context of the data container,
25197 see the documentation of \ref QCPDataContainer.
25198*/
25199
25200/*! \fn double QCPStatisticalBoxData::mainKey() const
25201
25202 Returns the \a key member of this data point.
25203
25204 For a general explanation of what this method is good for in the context of the data container,
25205 see the documentation of \ref QCPDataContainer.
25206*/
25207
25208/*! \fn double QCPStatisticalBoxData::mainValue() const
25209
25210 Returns the \a median member of this data point.
25211
25212 For a general explanation of what this method is good for in the context of the data container,
25213 see the documentation of \ref QCPDataContainer.
25214*/
25215
25216/*! \fn QCPRange QCPStatisticalBoxData::valueRange() const
25217
25218 Returns a QCPRange spanning from the \a minimum to the \a maximum member of this statistical box
25219 data point, possibly further expanded by outliers.
25220
25221 For a general explanation of what this method is good for in the context of the data container,
25222 see the documentation of \ref QCPDataContainer.
25223*/
25224
25225/* end documentation of inline functions */
25226
25227/*!
25228 Constructs a data point with key and all values set to zero.
25229*/
25231 key(0),
25232 minimum(0),
25233 lowerQuartile(0),
25234 median(0),
25235 upperQuartile(0),
25236 maximum(0)
25237{
25238}
25239
25240/*!
25241 Constructs a data point with the specified \a key, \a minimum, \a lowerQuartile, \a median, \a
25242 upperQuartile, \a maximum and optionally a number of \a outliers.
25243*/
25244QCPStatisticalBoxData::QCPStatisticalBoxData(double key, double minimum, double lowerQuartile, double median, double upperQuartile, double maximum, const QVector<double> &outliers) :
25245 key(key),
25246 minimum(minimum),
25247 lowerQuartile(lowerQuartile),
25248 median(median),
25249 upperQuartile(upperQuartile),
25250 maximum(maximum),
25251 outliers(outliers)
25252{
25253}
25254
25255
25256////////////////////////////////////////////////////////////////////////////////////////////////////
25257//////////////////// QCPStatisticalBox
25258////////////////////////////////////////////////////////////////////////////////////////////////////
25259
25260/*! \class QCPStatisticalBox
25261 \brief A plottable representing a single statistical box in a plot.
25262
25263 \image html QCPStatisticalBox.png
25264
25265 To plot data, assign it with the \ref setData or \ref addData functions. Alternatively, you can
25266 also access and modify the data via the \ref data method, which returns a pointer to the internal
25267 \ref QCPStatisticalBoxDataContainer.
25268
25269 Additionally each data point can itself have a list of outliers, drawn as scatter points at the
25270 key coordinate of the respective statistical box data point. They can either be set by using the
25271 respective \ref addData(double,double,double,double,double,double,const QVector<double>&)
25272 "addData" method or accessing the individual data points through \ref data, and setting the
25273 <tt>QVector<double> outliers</tt> of the data points directly.
25274
25275 \section qcpstatisticalbox-appearance Changing the appearance
25276
25277 The appearance of each data point box, ranging from the lower to the upper quartile, is
25278 controlled via \ref setPen and \ref setBrush. You may change the width of the boxes with \ref
25279 setWidth in plot coordinates.
25280
25281 Each data point's visual representation also consists of two whiskers. Whiskers are the lines
25282 which reach from the upper quartile to the maximum, and from the lower quartile to the minimum.
25283 The appearance of the whiskers can be modified with: \ref setWhiskerPen, \ref setWhiskerBarPen,
25284 \ref setWhiskerWidth. The whisker width is the width of the bar perpendicular to the whisker at
25285 the top (for maximum) and bottom (for minimum). If the whisker pen is changed, make sure to set
25286 the \c capStyle to \c Qt::FlatCap. Otherwise the backbone line might exceed the whisker bars by a
25287 few pixels due to the pen cap being not perfectly flat.
25288
25289 The median indicator line inside the box has its own pen, \ref setMedianPen.
25290
25291 The outlier data points are drawn as normal scatter points. Their look can be controlled with
25292 \ref setOutlierStyle
25293
25294 \section qcpstatisticalbox-usage Usage
25295
25296 Like all data representing objects in QCustomPlot, the QCPStatisticalBox is a plottable
25297 (QCPAbstractPlottable). So the plottable-interface of QCustomPlot applies
25298 (QCustomPlot::plottable, QCustomPlot::removePlottable, etc.)
25299
25300 Usually, you first create an instance:
25301 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpstatisticalbox-creation-1
25302 which registers it with the QCustomPlot instance of the passed axes. Note that this QCustomPlot instance takes
25303 ownership of the plottable, so do not delete it manually but use QCustomPlot::removePlottable() instead.
25304 The newly created plottable can be modified, e.g.:
25305 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpstatisticalbox-creation-2
25306*/
25307
25308/* start documentation of inline functions */
25309
25310/*! \fn QSharedPointer<QCPStatisticalBoxDataContainer> QCPStatisticalBox::data() const
25311
25312 Returns a shared pointer to the internal data storage of type \ref
25313 QCPStatisticalBoxDataContainer. You may use it to directly manipulate the data, which may be more
25314 convenient and faster than using the regular \ref setData or \ref addData methods.
25315*/
25316
25317/* end documentation of inline functions */
25318
25319/*!
25320 Constructs a statistical box which uses \a keyAxis as its key axis ("x") and \a valueAxis as its
25321 value axis ("y"). \a keyAxis and \a valueAxis must reside in the same QCustomPlot instance and
25322 not have the same orientation. If either of these restrictions is violated, a corresponding
25323 message is printed to the debug output (qDebug), the construction is not aborted, though.
25324
25325 The created QCPStatisticalBox is automatically registered with the QCustomPlot instance inferred
25326 from \a keyAxis. This QCustomPlot instance takes ownership of the QCPStatisticalBox, so do not
25327 delete it manually but use QCustomPlot::removePlottable() instead.
25328*/
25330 QCPAbstractPlottable1D<QCPStatisticalBoxData>(keyAxis, valueAxis),
25331 mWidth(0.5),
25332 mWhiskerWidth(0.2),
25333 mWhiskerPen(Qt::black, 0, Qt::DashLine, Qt::FlatCap),
25334 mWhiskerBarPen(Qt::black),
25335 mWhiskerAntialiased(false),
25336 mMedianPen(Qt::black, 3, Qt::SolidLine, Qt::FlatCap),
25337 mOutlierStyle(QCPScatterStyle::ssCircle, Qt::blue, 6)
25338{
25341}
25342
25343/*! \overload
25344
25345 Replaces the current data container with the provided \a data container.
25346
25347 Since a QSharedPointer is used, multiple QCPStatisticalBoxes may share the same data container
25348 safely. Modifying the data in the container will then affect all statistical boxes that share the
25349 container. Sharing can be achieved by simply exchanging the data containers wrapped in shared
25350 pointers:
25351 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpstatisticalbox-datasharing-1
25352
25353 If you do not wish to share containers, but create a copy from an existing container, rather use
25354 the \ref QCPDataContainer<DataType>::set method on the statistical box data container directly:
25355 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpstatisticalbox-datasharing-2
25356
25357 \see addData
25358*/
25363/*! \overload
25364
25365 Replaces the current data with the provided points in \a keys, \a minimum, \a lowerQuartile, \a
25366 median, \a upperQuartile and \a maximum. The provided vectors should have equal length. Else, the
25367 number of added points will be the size of the smallest vector.
25368
25369 If you can guarantee that the passed data points are sorted by \a keys in ascending order, you
25370 can set \a alreadySorted to true, to improve performance by saving a sorting run.
25371
25372 \see addData
25373*/
25374void QCPStatisticalBox::setData(const QVector<double> &keys, const QVector<double> &minimum, const QVector<double> &lowerQuartile, const QVector<double> &median, const QVector<double> &upperQuartile, const QVector<double> &maximum, bool alreadySorted)
25375{
25376 mDataContainer->clear();
25377 addData(keys, minimum, lowerQuartile, median, upperQuartile, maximum, alreadySorted);
25378}
25379
25380/*!
25381 Sets the width of the boxes in key coordinates.
25382
25383 \see setWhiskerWidth
25384*/
25386{
25387 mWidth = width;
25388}
25389
25390/*!
25391 Sets the width of the whiskers in key coordinates.
25392
25393 Whiskers are the lines which reach from the upper quartile to the maximum, and from the lower
25394 quartile to the minimum.
25395
25396 \see setWidth
25397*/
25399{
25400 mWhiskerWidth = width;
25401}
25402
25403/*!
25404 Sets the pen used for drawing the whisker backbone.
25405
25406 Whiskers are the lines which reach from the upper quartile to the maximum, and from the lower
25407 quartile to the minimum.
25408
25409 Make sure to set the \c capStyle of the passed \a pen to \c Qt::FlatCap. Otherwise the backbone
25410 line might exceed the whisker bars by a few pixels due to the pen cap being not perfectly flat.
25411
25412 \see setWhiskerBarPen
25413*/
25415{
25416 mWhiskerPen = pen;
25417}
25418
25419/*!
25420 Sets the pen used for drawing the whisker bars. Those are the lines parallel to the key axis at
25421 each end of the whisker backbone.
25422
25423 Whiskers are the lines which reach from the upper quartile to the maximum, and from the lower
25424 quartile to the minimum.
25425
25426 \see setWhiskerPen
25427*/
25429{
25430 mWhiskerBarPen = pen;
25431}
25432
25433/*!
25434 Sets whether the statistical boxes whiskers are drawn with antialiasing or not.
25435
25436 Note that antialiasing settings may be overridden by QCustomPlot::setAntialiasedElements and
25437 QCustomPlot::setNotAntialiasedElements.
25438*/
25440{
25441 mWhiskerAntialiased = enabled;
25442}
25443
25444/*!
25445 Sets the pen used for drawing the median indicator line inside the statistical boxes.
25446*/
25448{
25449 mMedianPen = pen;
25450}
25451
25452/*!
25453 Sets the appearance of the outlier data points.
25454
25455 Outliers can be specified with the method
25456 \ref addData(double key, double minimum, double lowerQuartile, double median, double upperQuartile, double maximum, const QVector<double> &outliers)
25457*/
25459{
25460 mOutlierStyle = style;
25461}
25462
25463/*! \overload
25464
25465 Adds the provided points in \a keys, \a minimum, \a lowerQuartile, \a median, \a upperQuartile and
25466 \a maximum to the current data. The provided vectors should have equal length. Else, the number
25467 of added points will be the size of the smallest vector.
25468
25469 If you can guarantee that the passed data points are sorted by \a keys in ascending order, you
25470 can set \a alreadySorted to true, to improve performance by saving a sorting run.
25471
25472 Alternatively, you can also access and modify the data directly via the \ref data method, which
25473 returns a pointer to the internal data container.
25474*/
25475void QCPStatisticalBox::addData(const QVector<double> &keys, const QVector<double> &minimum, const QVector<double> &lowerQuartile, const QVector<double> &median, const QVector<double> &upperQuartile, const QVector<double> &maximum, bool alreadySorted)
25476{
25477 if (keys.size() != minimum.size() || minimum.size() != lowerQuartile.size() || lowerQuartile.size() != median.size() ||
25478 median.size() != upperQuartile.size() || upperQuartile.size() != maximum.size() || maximum.size() != keys.size())
25479 qDebug() << Q_FUNC_INFO << "keys, minimum, lowerQuartile, median, upperQuartile, maximum have different sizes:"
25480 << keys.size() << minimum.size() << lowerQuartile.size() << median.size() << upperQuartile.size() << maximum.size();
25481 const int n = qMin(keys.size(), qMin(minimum.size(), qMin(lowerQuartile.size(), qMin(median.size(), qMin(upperQuartile.size(), maximum.size())))));
25484 const QVector<QCPStatisticalBoxData>::iterator itEnd = tempData.end();
25485 int i = 0;
25486 while (it != itEnd)
25487 {
25488 it->key = keys[i];
25489 it->minimum = minimum[i];
25490 it->lowerQuartile = lowerQuartile[i];
25491 it->median = median[i];
25492 it->upperQuartile = upperQuartile[i];
25493 it->maximum = maximum[i];
25494 ++it;
25495 ++i;
25496 }
25497 mDataContainer->add(tempData, alreadySorted); // don't modify tempData beyond this to prevent copy on write
25498}
25499
25500/*! \overload
25501
25502 Adds the provided data point as \a key, \a minimum, \a lowerQuartile, \a median, \a upperQuartile
25503 and \a maximum to the current data.
25504
25505 Alternatively, you can also access and modify the data directly via the \ref data method, which
25506 returns a pointer to the internal data container.
25507*/
25508void QCPStatisticalBox::addData(double key, double minimum, double lowerQuartile, double median, double upperQuartile, double maximum, const QVector<double> &outliers)
25509{
25510 mDataContainer->add(QCPStatisticalBoxData(key, minimum, lowerQuartile, median, upperQuartile, maximum, outliers));
25511}
25512
25513/*!
25514 \copydoc QCPPlottableInterface1D::selectTestRect
25515*/
25516QCPDataSelection QCPStatisticalBox::selectTestRect(const QRectF &rect, bool onlySelectable) const
25517{
25518 QCPDataSelection result;
25519 if ((onlySelectable && mSelectable == QCP::stNone) || mDataContainer->isEmpty())
25520 return result;
25521 if (!mKeyAxis || !mValueAxis)
25522 return result;
25523
25524 QCPStatisticalBoxDataContainer::const_iterator visibleBegin, visibleEnd;
25525 getVisibleDataBounds(visibleBegin, visibleEnd);
25526
25527 for (QCPStatisticalBoxDataContainer::const_iterator it=visibleBegin; it!=visibleEnd; ++it)
25528 {
25529 if (rect.intersects(getQuartileBox(it)))
25530 result.addDataRange(QCPDataRange(int(it-mDataContainer->constBegin()), int(it-mDataContainer->constBegin()+1)), false);
25531 }
25532 result.simplify();
25533 return result;
25534}
25535
25536/*!
25537 Implements a selectTest specific to this plottable's point geometry.
25538
25539 If \a details is not 0, it will be set to a \ref QCPDataSelection, describing the closest data
25540 point to \a pos.
25541
25542 \seebaseclassmethod \ref QCPAbstractPlottable::selectTest
25543*/
25544double QCPStatisticalBox::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
25545{
25546 Q_UNUSED(details)
25547 if ((onlySelectable && mSelectable == QCP::stNone) || mDataContainer->isEmpty())
25548 return -1;
25549 if (!mKeyAxis || !mValueAxis)
25550 return -1;
25551
25552 if (mKeyAxis->axisRect()->rect().contains(pos.toPoint()) || mParentPlot->interactions().testFlag(QCP::iSelectPlottablesBeyondAxisRect))
25553 {
25554 // get visible data range:
25555 QCPStatisticalBoxDataContainer::const_iterator visibleBegin, visibleEnd;
25556 QCPStatisticalBoxDataContainer::const_iterator closestDataPoint = mDataContainer->constEnd();
25557 getVisibleDataBounds(visibleBegin, visibleEnd);
25558 double minDistSqr = (std::numeric_limits<double>::max)();
25559 for (QCPStatisticalBoxDataContainer::const_iterator it=visibleBegin; it!=visibleEnd; ++it)
25560 {
25561 if (getQuartileBox(it).contains(pos)) // quartile box
25562 {
25563 double currentDistSqr = mParentPlot->selectionTolerance()*0.99 * mParentPlot->selectionTolerance()*0.99;
25564 if (currentDistSqr < minDistSqr)
25565 {
25566 minDistSqr = currentDistSqr;
25567 closestDataPoint = it;
25568 }
25569 } else // whiskers
25570 {
25571 const QVector<QLineF> whiskerBackbones = getWhiskerBackboneLines(it);
25572 const QCPVector2D posVec(pos);
25573 foreach (const QLineF &backbone, whiskerBackbones)
25574 {
25575 double currentDistSqr = posVec.distanceSquaredToLine(backbone);
25576 if (currentDistSqr < minDistSqr)
25577 {
25578 minDistSqr = currentDistSqr;
25579 closestDataPoint = it;
25580 }
25581 }
25582 }
25583 }
25584 if (details)
25585 {
25586 int pointIndex = int(closestDataPoint-mDataContainer->constBegin());
25587 details->setValue(QCPDataSelection(QCPDataRange(pointIndex, pointIndex+1)));
25588 }
25589 return qSqrt(minDistSqr);
25590 }
25591 return -1;
25592}
25593
25594/* inherits documentation from base class */
25595QCPRange QCPStatisticalBox::getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain) const
25596{
25597 QCPRange range = mDataContainer->keyRange(foundRange, inSignDomain);
25598 // determine exact range by including width of bars/flags:
25599 if (foundRange)
25600 {
25601 if (inSignDomain != QCP::sdPositive || range.lower-mWidth*0.5 > 0)
25602 range.lower -= mWidth*0.5;
25603 if (inSignDomain != QCP::sdNegative || range.upper+mWidth*0.5 < 0)
25604 range.upper += mWidth*0.5;
25605 }
25606 return range;
25607}
25608
25609/* inherits documentation from base class */
25610QCPRange QCPStatisticalBox::getValueRange(bool &foundRange, QCP::SignDomain inSignDomain, const QCPRange &inKeyRange) const
25611{
25612 return mDataContainer->valueRange(foundRange, inSignDomain, inKeyRange);
25613}
25614
25615/* inherits documentation from base class */
25617{
25618 if (mDataContainer->isEmpty()) return;
25619 QCPAxis *keyAxis = mKeyAxis.data();
25620 QCPAxis *valueAxis = mValueAxis.data();
25621 if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
25622
25623 QCPStatisticalBoxDataContainer::const_iterator visibleBegin, visibleEnd;
25624 getVisibleDataBounds(visibleBegin, visibleEnd);
25625
25626 // loop over and draw segments of unselected/selected data:
25627 QList<QCPDataRange> selectedSegments, unselectedSegments, allSegments;
25628 getDataSegments(selectedSegments, unselectedSegments);
25629 allSegments << unselectedSegments << selectedSegments;
25630 for (int i=0; i<allSegments.size(); ++i)
25631 {
25632 bool isSelectedSegment = i >= unselectedSegments.size();
25635 mDataContainer->limitIteratorsToDataRange(begin, end, allSegments.at(i));
25636 if (begin == end)
25637 continue;
25638
25639 for (QCPStatisticalBoxDataContainer::const_iterator it=begin; it!=end; ++it)
25640 {
25641 // check data validity if flag set:
25642# ifdef QCUSTOMPLOT_CHECK_DATA
25643 if (QCP::isInvalidData(it->key, it->minimum) ||
25644 QCP::isInvalidData(it->lowerQuartile, it->median) ||
25645 QCP::isInvalidData(it->upperQuartile, it->maximum))
25646 qDebug() << Q_FUNC_INFO << "Data point at" << it->key << "of drawn range has invalid data." << "Plottable name:" << name();
25647 for (int i=0; i<it->outliers.size(); ++i)
25648 if (QCP::isInvalidData(it->outliers.at(i)))
25649 qDebug() << Q_FUNC_INFO << "Data point outlier at" << it->key << "of drawn range invalid." << "Plottable name:" << name();
25650# endif
25651
25652 if (isSelectedSegment && mSelectionDecorator)
25653 {
25654 mSelectionDecorator->applyPen(painter);
25655 mSelectionDecorator->applyBrush(painter);
25656 } else
25657 {
25658 painter->setPen(mPen);
25659 painter->setBrush(mBrush);
25660 }
25661 QCPScatterStyle finalOutlierStyle = mOutlierStyle;
25662 if (isSelectedSegment && mSelectionDecorator)
25663 finalOutlierStyle = mSelectionDecorator->getFinalScatterStyle(mOutlierStyle);
25664 drawStatisticalBox(painter, it, finalOutlierStyle);
25665 }
25666 }
25667
25668 // draw other selection decoration that isn't just line/scatter pens and brushes:
25669 if (mSelectionDecorator)
25670 mSelectionDecorator->drawDecoration(painter, selection());
25671}
25672
25673/* inherits documentation from base class */
25674void QCPStatisticalBox::drawLegendIcon(QCPPainter *painter, const QRectF &rect) const
25675{
25676 // draw filled rect:
25678 painter->setPen(mPen);
25679 painter->setBrush(mBrush);
25680 QRectF r = QRectF(0, 0, rect.width()*0.67, rect.height()*0.67);
25681 r.moveCenter(rect.center());
25682 painter->drawRect(r);
25683}
25684
25685/*!
25686 Draws the graphical representation of a single statistical box with the data given by the
25687 iterator \a it with the provided \a painter.
25688
25689 If the statistical box has a set of outlier data points, they are drawn with \a outlierStyle.
25690
25691 \see getQuartileBox, getWhiskerBackboneLines, getWhiskerBarLines
25692*/
25694{
25695 // draw quartile box:
25697 const QRectF quartileBox = getQuartileBox(it);
25698 painter->drawRect(quartileBox);
25699 // draw median line with cliprect set to quartile box:
25700 painter->save();
25701 painter->setClipRect(quartileBox, Qt::IntersectClip);
25702 painter->setPen(mMedianPen);
25703 painter->drawLine(QLineF(coordsToPixels(it->key-mWidth*0.5, it->median), coordsToPixels(it->key+mWidth*0.5, it->median)));
25704 painter->restore();
25705 // draw whisker lines:
25706 applyAntialiasingHint(painter, mWhiskerAntialiased, QCP::aePlottables);
25707 painter->setPen(mWhiskerPen);
25708 painter->drawLines(getWhiskerBackboneLines(it));
25709 painter->setPen(mWhiskerBarPen);
25710 painter->drawLines(getWhiskerBarLines(it));
25711 // draw outliers:
25713 outlierStyle.applyTo(painter, mPen);
25714 for (int i=0; i<it->outliers.size(); ++i)
25715 outlierStyle.drawShape(painter, coordsToPixels(it->key, it->outliers.at(i)));
25716}
25717
25718/*! \internal
25719
25720 called by \ref draw to determine which data (key) range is visible at the current key axis range
25721 setting, so only that needs to be processed. It also takes into account the bar width.
25722
25723 \a begin returns an iterator to the lowest data point that needs to be taken into account when
25724 plotting. Note that in order to get a clean plot all the way to the edge of the axis rect, \a
25725 lower may still be just outside the visible range.
25726
25727 \a end returns an iterator one higher than the highest visible data point. Same as before, \a end
25728 may also lie just outside of the visible range.
25729
25730 if the plottable contains no data, both \a begin and \a end point to constEnd.
25731*/
25733{
25734 if (!mKeyAxis)
25735 {
25736 qDebug() << Q_FUNC_INFO << "invalid key axis";
25737 begin = mDataContainer->constEnd();
25738 end = mDataContainer->constEnd();
25739 return;
25740 }
25741 begin = mDataContainer->findBegin(mKeyAxis.data()->range().lower-mWidth*0.5); // subtract half width of box to include partially visible data points
25742 end = mDataContainer->findEnd(mKeyAxis.data()->range().upper+mWidth*0.5); // add half width of box to include partially visible data points
25743}
25744
25745/*! \internal
25746
25747 Returns the box in plot coordinates (keys in x, values in y of the returned rect) that covers the
25748 value range from the lower to the upper quartile, of the data given by \a it.
25749
25750 \see drawStatisticalBox, getWhiskerBackboneLines, getWhiskerBarLines
25751*/
25753{
25754 QRectF result;
25755 result.setTopLeft(coordsToPixels(it->key-mWidth*0.5, it->upperQuartile));
25756 result.setBottomRight(coordsToPixels(it->key+mWidth*0.5, it->lowerQuartile));
25757 return result;
25758}
25759
25760/*! \internal
25761
25762 Returns the whisker backbones (keys in x, values in y of the returned lines) that cover the value
25763 range from the minimum to the lower quartile, and from the upper quartile to the maximum of the
25764 data given by \a it.
25765
25766 \see drawStatisticalBox, getQuartileBox, getWhiskerBarLines
25767*/
25769{
25770 QVector<QLineF> result(2);
25771 result[0].setPoints(coordsToPixels(it->key, it->lowerQuartile), coordsToPixels(it->key, it->minimum)); // min backbone
25772 result[1].setPoints(coordsToPixels(it->key, it->upperQuartile), coordsToPixels(it->key, it->maximum)); // max backbone
25773 return result;
25774}
25775
25776/*! \internal
25777
25778 Returns the whisker bars (keys in x, values in y of the returned lines) that are placed at the
25779 end of the whisker backbones, at the minimum and maximum of the data given by \a it.
25780
25781 \see drawStatisticalBox, getQuartileBox, getWhiskerBackboneLines
25782*/
25784{
25785 QVector<QLineF> result(2);
25786 result[0].setPoints(coordsToPixels(it->key-mWhiskerWidth*0.5, it->minimum), coordsToPixels(it->key+mWhiskerWidth*0.5, it->minimum)); // min bar
25787 result[1].setPoints(coordsToPixels(it->key-mWhiskerWidth*0.5, it->maximum), coordsToPixels(it->key+mWhiskerWidth*0.5, it->maximum)); // max bar
25788 return result;
25789}
25790/* end of 'src/plottables/plottable-statisticalbox.cpp' */
25791
25792
25793/* including file 'src/plottables/plottable-colormap.cpp' */
25794/* modified 2022-11-06T12:45:56, size 48189 */
25795
25796////////////////////////////////////////////////////////////////////////////////////////////////////
25797//////////////////// QCPColorMapData
25798////////////////////////////////////////////////////////////////////////////////////////////////////
25799
25800/*! \class QCPColorMapData
25801 \brief Holds the two-dimensional data of a QCPColorMap plottable.
25802
25803 This class is a data storage for \ref QCPColorMap. It holds a two-dimensional array, which \ref
25804 QCPColorMap then displays as a 2D image in the plot, where the array values are represented by a
25805 color, depending on the value.
25806
25807 The size of the array can be controlled via \ref setSize (or \ref setKeySize, \ref setValueSize).
25808 Which plot coordinates these cells correspond to can be configured with \ref setRange (or \ref
25809 setKeyRange, \ref setValueRange).
25810
25811 The data cells can be accessed in two ways: They can be directly addressed by an integer index
25812 with \ref setCell. This is the fastest method. Alternatively, they can be addressed by their plot
25813 coordinate with \ref setData. plot coordinate to cell index transformations and vice versa are
25814 provided by the functions \ref coordToCell and \ref cellToCoord.
25815
25816 A \ref QCPColorMapData also holds an on-demand two-dimensional array of alpha values which (if
25817 allocated) has the same size as the data map. It can be accessed via \ref setAlpha, \ref
25818 fillAlpha and \ref clearAlpha. The memory for the alpha map is only allocated if needed, i.e. on
25819 the first call of \ref setAlpha. \ref clearAlpha restores full opacity and frees the alpha map.
25820
25821 This class also buffers the minimum and maximum values that are in the data set, to provide
25822 QCPColorMap::rescaleDataRange with the necessary information quickly. Setting a cell to a value
25823 that is greater than the current maximum increases this maximum to the new value. However,
25824 setting the cell that currently holds the maximum value to a smaller value doesn't decrease the
25825 maximum again, because finding the true new maximum would require going through the entire data
25826 array, which might be time consuming. The same holds for the data minimum. This functionality is
25827 given by \ref recalculateDataBounds, such that you can decide when it is sensible to find the
25828 true current minimum and maximum. The method QCPColorMap::rescaleDataRange offers a convenience
25829 parameter \a recalculateDataBounds which may be set to true to automatically call \ref
25830 recalculateDataBounds internally.
25831*/
25832
25833/* start of documentation of inline functions */
25834
25835/*! \fn bool QCPColorMapData::isEmpty() const
25836
25837 Returns whether this instance carries no data. This is equivalent to having a size where at least
25838 one of the dimensions is 0 (see \ref setSize).
25839*/
25840
25841/* end of documentation of inline functions */
25842
25843/*!
25844 Constructs a new QCPColorMapData instance. The instance has \a keySize cells in the key direction
25845 and \a valueSize cells in the value direction. These cells will be displayed by the \ref QCPColorMap
25846 at the coordinates \a keyRange and \a valueRange.
25847
25848 \see setSize, setKeySize, setValueSize, setRange, setKeyRange, setValueRange
25849*/
25850QCPColorMapData::QCPColorMapData(int keySize, int valueSize, const QCPRange &keyRange, const QCPRange &valueRange) :
25851 mKeySize(0),
25852 mValueSize(0),
25853 mKeyRange(keyRange),
25854 mValueRange(valueRange),
25855 mIsEmpty(true),
25856 mData(nullptr),
25857 mAlpha(nullptr),
25858 mDataModified(true)
25859{
25860 setSize(keySize, valueSize);
25861 fill(0);
25862}
25863
25864QCPColorMapData::~QCPColorMapData()
25865{
25866 delete[] mData;
25867 delete[] mAlpha;
25868}
25869
25870/*!
25871 Constructs a new QCPColorMapData instance copying the data and range of \a other.
25872*/
25874 mKeySize(0),
25875 mValueSize(0),
25876 mIsEmpty(true),
25877 mData(nullptr),
25878 mAlpha(nullptr),
25879 mDataModified(true)
25880{
25881 *this = other;
25882}
25883
25884/*!
25885 Overwrites this color map data instance with the data stored in \a other. The alpha map state is
25886 transferred, too.
25887*/
25889{
25890 if (&other != this)
25891 {
25892 const int keySize = other.keySize();
25893 const int valueSize = other.valueSize();
25894 if (!other.mAlpha && mAlpha)
25895 clearAlpha();
25896 setSize(keySize, valueSize);
25897 if (other.mAlpha && !mAlpha)
25898 createAlpha(false);
25899 setRange(other.keyRange(), other.valueRange());
25900 if (!isEmpty())
25901 {
25902 memcpy(mData, other.mData, sizeof(mData[0])*size_t(keySize*valueSize));
25903 if (mAlpha)
25904 memcpy(mAlpha, other.mAlpha, sizeof(mAlpha[0])*size_t(keySize*valueSize));
25905 }
25906 mDataBounds = other.mDataBounds;
25907 mDataModified = true;
25908 }
25909 return *this;
25910}
25911
25912/* undocumented getter */
25913double QCPColorMapData::data(double key, double value)
25914{
25915 int keyCell = int( (key-mKeyRange.lower)/(mKeyRange.upper-mKeyRange.lower)*(mKeySize-1)+0.5 );
25916 int valueCell = int( (value-mValueRange.lower)/(mValueRange.upper-mValueRange.lower)*(mValueSize-1)+0.5 );
25917 if (keyCell >= 0 && keyCell < mKeySize && valueCell >= 0 && valueCell < mValueSize)
25918 return mData[valueCell*mKeySize + keyCell];
25919 else
25920 return 0;
25921}
25922
25923/* undocumented getter */
25924double QCPColorMapData::cell(int keyIndex, int valueIndex)
25925{
25926 if (keyIndex >= 0 && keyIndex < mKeySize && valueIndex >= 0 && valueIndex < mValueSize)
25927 return mData[valueIndex*mKeySize + keyIndex];
25928 else
25929 return 0;
25930}
25931
25932/*!
25933 Returns the alpha map value of the cell with the indices \a keyIndex and \a valueIndex.
25934
25935 If this color map data doesn't have an alpha map (because \ref setAlpha was never called after
25936 creation or after a call to \ref clearAlpha), returns 255, which corresponds to full opacity.
25937
25938 \see setAlpha
25939*/
25940unsigned char QCPColorMapData::alpha(int keyIndex, int valueIndex)
25941{
25942 if (mAlpha && keyIndex >= 0 && keyIndex < mKeySize && valueIndex >= 0 && valueIndex < mValueSize)
25943 return mAlpha[valueIndex*mKeySize + keyIndex];
25944 else
25945 return 255;
25946}
25947
25948/*!
25949 Resizes the data array to have \a keySize cells in the key dimension and \a valueSize cells in
25950 the value dimension.
25951
25952 The current data is discarded and the map cells are set to 0, unless the map had already the
25953 requested size.
25954
25955 Setting at least one of \a keySize or \a valueSize to zero frees the internal data array and \ref
25956 isEmpty returns true.
25957
25958 \see setRange, setKeySize, setValueSize
25959*/
25960void QCPColorMapData::setSize(int keySize, int valueSize)
25961{
25962 if (keySize != mKeySize || valueSize != mValueSize)
25963 {
25964 mKeySize = keySize;
25965 mValueSize = valueSize;
25966 delete[] mData;
25967 mIsEmpty = mKeySize == 0 || mValueSize == 0;
25968 if (!mIsEmpty)
25969 {
25970#ifdef __EXCEPTIONS
25971 try { // 2D arrays get memory intensive fast. So if the allocation fails, at least output debug message
25972#endif
25973 mData = new double[size_t(mKeySize*mValueSize)];
25974#ifdef __EXCEPTIONS
25975 } catch (...) { mData = nullptr; }
25976#endif
25977 if (mData)
25978 fill(0);
25979 else
25980 qDebug() << Q_FUNC_INFO << "out of memory for data dimensions "<< mKeySize << "*" << mValueSize;
25981 } else
25982 mData = nullptr;
25983
25984 if (mAlpha) // if we had an alpha map, recreate it with new size
25985 createAlpha();
25986
25987 mDataModified = true;
25988 }
25989}
25990
25991/*!
25992 Resizes the data array to have \a keySize cells in the key dimension.
25993
25994 The current data is discarded and the map cells are set to 0, unless the map had already the
25995 requested size.
25996
25997 Setting \a keySize to zero frees the internal data array and \ref isEmpty returns true.
25998
25999 \see setKeyRange, setSize, setValueSize
26000*/
26002{
26003 setSize(keySize, mValueSize);
26004}
26005
26006/*!
26007 Resizes the data array to have \a valueSize cells in the value dimension.
26008
26009 The current data is discarded and the map cells are set to 0, unless the map had already the
26010 requested size.
26011
26012 Setting \a valueSize to zero frees the internal data array and \ref isEmpty returns true.
26013
26014 \see setValueRange, setSize, setKeySize
26015*/
26017{
26018 setSize(mKeySize, valueSize);
26019}
26020
26021/*!
26022 Sets the coordinate ranges the data shall be distributed over. This defines the rectangular area
26023 covered by the color map in plot coordinates.
26024
26025 The outer cells will be centered on the range boundaries given to this function. For example, if
26026 the key size (\ref setKeySize) is 3 and \a keyRange is set to <tt>QCPRange(2, 3)</tt> there will
26027 be cells centered on the key coordinates 2, 2.5 and 3.
26028
26029 \see setSize
26030*/
26031void QCPColorMapData::setRange(const QCPRange &keyRange, const QCPRange &valueRange)
26032{
26033 setKeyRange(keyRange);
26034 setValueRange(valueRange);
26035}
26036
26037/*!
26038 Sets the coordinate range the data shall be distributed over in the key dimension. Together with
26039 the value range, This defines the rectangular area covered by the color map in plot coordinates.
26040
26041 The outer cells will be centered on the range boundaries given to this function. For example, if
26042 the key size (\ref setKeySize) is 3 and \a keyRange is set to <tt>QCPRange(2, 3)</tt> there will
26043 be cells centered on the key coordinates 2, 2.5 and 3.
26044
26045 \see setRange, setValueRange, setSize
26046*/
26048{
26049 mKeyRange = keyRange;
26050}
26051
26052/*!
26053 Sets the coordinate range the data shall be distributed over in the value dimension. Together with
26054 the key range, This defines the rectangular area covered by the color map in plot coordinates.
26055
26056 The outer cells will be centered on the range boundaries given to this function. For example, if
26057 the value size (\ref setValueSize) is 3 and \a valueRange is set to <tt>QCPRange(2, 3)</tt> there
26058 will be cells centered on the value coordinates 2, 2.5 and 3.
26059
26060 \see setRange, setKeyRange, setSize
26061*/
26063{
26064 mValueRange = valueRange;
26065}
26066
26067/*!
26068 Sets the data of the cell, which lies at the plot coordinates given by \a key and \a value, to \a
26069 z.
26070
26071 \note The QCPColorMap always displays the data at equal key/value intervals, even if the key or
26072 value axis is set to a logarithmic scaling. If you want to use QCPColorMap with logarithmic axes,
26073 you shouldn't use the \ref QCPColorMapData::setData method as it uses a linear transformation to
26074 determine the cell index. Rather directly access the cell index with \ref
26075 QCPColorMapData::setCell.
26076
26077 \see setCell, setRange
26078*/
26079void QCPColorMapData::setData(double key, double value, double z)
26080{
26081 int keyCell = int( (key-mKeyRange.lower)/(mKeyRange.upper-mKeyRange.lower)*(mKeySize-1)+0.5 );
26082 int valueCell = int( (value-mValueRange.lower)/(mValueRange.upper-mValueRange.lower)*(mValueSize-1)+0.5 );
26083 if (keyCell >= 0 && keyCell < mKeySize && valueCell >= 0 && valueCell < mValueSize)
26084 {
26085 mData[valueCell*mKeySize + keyCell] = z;
26086 if (z < mDataBounds.lower)
26087 mDataBounds.lower = z;
26088 if (z > mDataBounds.upper)
26089 mDataBounds.upper = z;
26090 mDataModified = true;
26091 }
26092}
26093
26094/*!
26095 Sets the data of the cell with indices \a keyIndex and \a valueIndex to \a z. The indices
26096 enumerate the cells starting from zero, up to the map's size-1 in the respective dimension (see
26097 \ref setSize).
26098
26099 In the standard plot configuration (horizontal key axis and vertical value axis, both not
26100 range-reversed), the cell with indices (0, 0) is in the bottom left corner and the cell with
26101 indices (keySize-1, valueSize-1) is in the top right corner of the color map.
26102
26103 \see setData, setSize
26104*/
26105void QCPColorMapData::setCell(int keyIndex, int valueIndex, double z)
26106{
26107 if (keyIndex >= 0 && keyIndex < mKeySize && valueIndex >= 0 && valueIndex < mValueSize)
26108 {
26109 mData[valueIndex*mKeySize + keyIndex] = z;
26110 if (z < mDataBounds.lower)
26111 mDataBounds.lower = z;
26112 if (z > mDataBounds.upper)
26113 mDataBounds.upper = z;
26114 mDataModified = true;
26115 } else
26116 qDebug() << Q_FUNC_INFO << "index out of bounds:" << keyIndex << valueIndex;
26117}
26118
26119/*!
26120 Sets the alpha of the color map cell given by \a keyIndex and \a valueIndex to \a alpha. A value
26121 of 0 for \a alpha results in a fully transparent cell, and a value of 255 results in a fully
26122 opaque cell.
26123
26124 If an alpha map doesn't exist yet for this color map data, it will be created here. If you wish
26125 to restore full opacity and free any allocated memory of the alpha map, call \ref clearAlpha.
26126
26127 Note that the cell-wise alpha which can be configured here is independent of any alpha configured
26128 in the color map's gradient (\ref QCPColorGradient). If a cell is affected both by the cell-wise
26129 and gradient alpha, the alpha values will be blended accordingly during rendering of the color
26130 map.
26131
26132 \see fillAlpha, clearAlpha
26133*/
26134void QCPColorMapData::setAlpha(int keyIndex, int valueIndex, unsigned char alpha)
26135{
26136 if (keyIndex >= 0 && keyIndex < mKeySize && valueIndex >= 0 && valueIndex < mValueSize)
26137 {
26138 if (mAlpha || createAlpha())
26139 {
26140 mAlpha[valueIndex*mKeySize + keyIndex] = alpha;
26141 mDataModified = true;
26142 }
26143 } else
26144 qDebug() << Q_FUNC_INFO << "index out of bounds:" << keyIndex << valueIndex;
26145}
26146
26147/*!
26148 Goes through the data and updates the buffered minimum and maximum data values.
26149
26150 Calling this method is only advised if you are about to call \ref QCPColorMap::rescaleDataRange
26151 and can not guarantee that the cells holding the maximum or minimum data haven't been overwritten
26152 with a smaller or larger value respectively, since the buffered maximum/minimum values have been
26153 updated the last time. Why this is the case is explained in the class description (\ref
26154 QCPColorMapData).
26155
26156 Note that the method \ref QCPColorMap::rescaleDataRange provides a parameter \a
26157 recalculateDataBounds for convenience. Setting this to true will call this method for you, before
26158 doing the rescale.
26159*/
26161{
26162 if (mKeySize > 0 && mValueSize > 0)
26163 {
26164 double minHeight = std::numeric_limits<double>::max();
26165 double maxHeight = -std::numeric_limits<double>::max();
26166 const int dataCount = mValueSize*mKeySize;
26167 for (int i=0; i<dataCount; ++i)
26168 {
26169 if (mData[i] > maxHeight)
26170 maxHeight = mData[i];
26171 if (mData[i] < minHeight)
26172 minHeight = mData[i];
26173 }
26174 mDataBounds.lower = minHeight;
26175 mDataBounds.upper = maxHeight;
26176 }
26177}
26178
26179/*!
26180 Frees the internal data memory.
26181
26182 This is equivalent to calling \ref setSize "setSize(0, 0)".
26183*/
26185{
26186 setSize(0, 0);
26187}
26188
26189/*!
26190 Frees the internal alpha map. The color map will have full opacity again.
26191*/
26193{
26194 if (mAlpha)
26195 {
26196 delete[] mAlpha;
26197 mAlpha = nullptr;
26198 mDataModified = true;
26199 }
26200}
26201
26202/*!
26203 Sets all cells to the value \a z.
26204*/
26206{
26207 const int dataCount = mValueSize*mKeySize;
26208 memset(mData, z, dataCount*sizeof(*mData));
26209 mDataBounds = QCPRange(z, z);
26210 mDataModified = true;
26211}
26212
26213/*!
26214 Sets the opacity of all color map cells to \a alpha. A value of 0 for \a alpha results in a fully
26215 transparent color map, and a value of 255 results in a fully opaque color map.
26216
26217 If you wish to restore opacity to 100% and free any used memory for the alpha map, rather use
26218 \ref clearAlpha.
26219
26220 \see setAlpha
26221*/
26222void QCPColorMapData::fillAlpha(unsigned char alpha)
26223{
26224 if (mAlpha || createAlpha(false))
26225 {
26226 const int dataCount = mValueSize*mKeySize;
26227 memset(mAlpha, alpha, dataCount*sizeof(*mAlpha));
26228 mDataModified = true;
26229 }
26230}
26231
26232/*!
26233 Transforms plot coordinates given by \a key and \a value to cell indices of this QCPColorMapData
26234 instance. The resulting cell indices are returned via the output parameters \a keyIndex and \a
26235 valueIndex.
26236
26237 The retrieved key/value cell indices can then be used for example with \ref setCell.
26238
26239 If you are only interested in a key or value index, you may pass \c nullptr as \a valueIndex or
26240 \a keyIndex.
26241
26242 \note The QCPColorMap always displays the data at equal key/value intervals, even if the key or
26243 value axis is set to a logarithmic scaling. If you want to use QCPColorMap with logarithmic axes,
26244 you shouldn't use the \ref QCPColorMapData::coordToCell method as it uses a linear transformation to
26245 determine the cell index.
26246
26247 \see cellToCoord, QCPAxis::coordToPixel
26248*/
26249void QCPColorMapData::coordToCell(double key, double value, int *keyIndex, int *valueIndex) const
26250{
26251 if (keyIndex)
26252 *keyIndex = int( (key-mKeyRange.lower)/(mKeyRange.upper-mKeyRange.lower)*(mKeySize-1)+0.5 );
26253 if (valueIndex)
26254 *valueIndex = int( (value-mValueRange.lower)/(mValueRange.upper-mValueRange.lower)*(mValueSize-1)+0.5 );
26255}
26256
26257/*!
26258 Transforms cell indices given by \a keyIndex and \a valueIndex to cell indices of this QCPColorMapData
26259 instance. The resulting coordinates are returned via the output parameters \a key and \a
26260 value.
26261
26262 If you are only interested in a key or value coordinate, you may pass \c nullptr as \a key or \a
26263 value.
26264
26265 \note The QCPColorMap always displays the data at equal key/value intervals, even if the key or
26266 value axis is set to a logarithmic scaling. If you want to use QCPColorMap with logarithmic axes,
26267 you shouldn't use the \ref QCPColorMapData::cellToCoord method as it uses a linear transformation to
26268 determine the cell index.
26269
26270 \see coordToCell, QCPAxis::pixelToCoord
26271*/
26272void QCPColorMapData::cellToCoord(int keyIndex, int valueIndex, double *key, double *value) const
26273{
26274 if (key)
26275 *key = keyIndex/double(mKeySize-1)*(mKeyRange.upper-mKeyRange.lower)+mKeyRange.lower;
26276 if (value)
26277 *value = valueIndex/double(mValueSize-1)*(mValueRange.upper-mValueRange.lower)+mValueRange.lower;
26278}
26279
26280/*! \internal
26281
26282 Allocates the internal alpha map with the current data map key/value size and, if \a
26283 initializeOpaque is true, initializes all values to 255. If \a initializeOpaque is false, the
26284 values are not initialized at all. In this case, the alpha map should be initialized manually,
26285 e.g. with \ref fillAlpha.
26286
26287 If an alpha map exists already, it is deleted first. If this color map is empty (has either key
26288 or value size zero, see \ref isEmpty), the alpha map is cleared.
26289
26290 The return value indicates the existence of the alpha map after the call. So this method returns
26291 true if the data map isn't empty and an alpha map was successfully allocated.
26292*/
26293bool QCPColorMapData::createAlpha(bool initializeOpaque)
26294{
26295 clearAlpha();
26296 if (isEmpty())
26297 return false;
26298
26299#ifdef __EXCEPTIONS
26300 try { // 2D arrays get memory intensive fast. So if the allocation fails, at least output debug message
26301#endif
26302 mAlpha = new unsigned char[size_t(mKeySize*mValueSize)];
26303#ifdef __EXCEPTIONS
26304 } catch (...) { mAlpha = nullptr; }
26305#endif
26306 if (mAlpha)
26307 {
26308 if (initializeOpaque)
26309 fillAlpha(255);
26310 return true;
26311 } else
26312 {
26313 qDebug() << Q_FUNC_INFO << "out of memory for data dimensions "<< mKeySize << "*" << mValueSize;
26314 return false;
26315 }
26316}
26317
26318
26319////////////////////////////////////////////////////////////////////////////////////////////////////
26320//////////////////// QCPColorMap
26321////////////////////////////////////////////////////////////////////////////////////////////////////
26322
26323/*! \class QCPColorMap
26324 \brief A plottable representing a two-dimensional color map in a plot.
26325
26326 \image html QCPColorMap.png
26327
26328 The data is stored in the class \ref QCPColorMapData, which can be accessed via the data()
26329 method.
26330
26331 A color map has three dimensions to represent a data point: The \a key dimension, the \a value
26332 dimension and the \a data dimension. As with other plottables such as graphs, \a key and \a value
26333 correspond to two orthogonal axes on the QCustomPlot surface that you specify in the QCPColorMap
26334 constructor. The \a data dimension however is encoded as the color of the point at (\a key, \a
26335 value).
26336
26337 Set the number of points (or \a cells) in the key/value dimension via \ref
26338 QCPColorMapData::setSize. The plot coordinate range over which these points will be displayed is
26339 specified via \ref QCPColorMapData::setRange. The first cell will be centered on the lower range
26340 boundary and the last cell will be centered on the upper range boundary. The data can be set by
26341 either accessing the cells directly with QCPColorMapData::setCell or by addressing the cells via
26342 their plot coordinates with \ref QCPColorMapData::setData. If possible, you should prefer
26343 setCell, since it doesn't need to do any coordinate transformation and thus performs a bit
26344 better.
26345
26346 The cell with index (0, 0) is at the bottom left, if the color map uses normal (i.e. not reversed)
26347 key and value axes.
26348
26349 To show the user which colors correspond to which \a data values, a \ref QCPColorScale is
26350 typically placed to the right of the axis rect. See the documentation there for details on how to
26351 add and use a color scale.
26352
26353 \section qcpcolormap-appearance Changing the appearance
26354
26355 Most important to the appearance is the color gradient, which can be specified via \ref
26356 setGradient. See the documentation of \ref QCPColorGradient for details on configuring a color
26357 gradient.
26358
26359 The \a data range that is mapped to the colors of the gradient can be specified with \ref
26360 setDataRange. To make the data range encompass the whole data set minimum to maximum, call \ref
26361 rescaleDataRange. If your data may contain NaN values, use \ref QCPColorGradient::setNanHandling
26362 to define how they are displayed.
26363
26364 \section qcpcolormap-transparency Transparency
26365
26366 Transparency in color maps can be achieved by two mechanisms. On one hand, you can specify alpha
26367 values for color stops of the \ref QCPColorGradient, via the regular QColor interface. This will
26368 cause the color map data which gets mapped to colors around those color stops to appear with the
26369 accordingly interpolated transparency.
26370
26371 On the other hand you can also directly apply an alpha value to each cell independent of its
26372 data, by using the alpha map feature of \ref QCPColorMapData. The relevant methods are \ref
26373 QCPColorMapData::setAlpha, QCPColorMapData::fillAlpha and \ref QCPColorMapData::clearAlpha().
26374
26375 The two transparencies will be joined together in the plot and otherwise not interfere with each
26376 other. They are mixed in a multiplicative matter, so an alpha of e.g. 50% (128/255) in both modes
26377 simultaneously, will result in a total transparency of 25% (64/255).
26378
26379 \section qcpcolormap-usage Usage
26380
26381 Like all data representing objects in QCustomPlot, the QCPColorMap is a plottable
26382 (QCPAbstractPlottable). So the plottable-interface of QCustomPlot applies
26383 (QCustomPlot::plottable, QCustomPlot::removePlottable, etc.)
26384
26385 Usually, you first create an instance:
26386 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcolormap-creation-1
26387 which registers it with the QCustomPlot instance of the passed axes. Note that this QCustomPlot instance takes
26388 ownership of the plottable, so do not delete it manually but use QCustomPlot::removePlottable() instead.
26389 The newly created plottable can be modified, e.g.:
26390 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcolormap-creation-2
26391
26392 \note The QCPColorMap always displays the data at equal key/value intervals, even if the key or
26393 value axis is set to a logarithmic scaling. If you want to use QCPColorMap with logarithmic axes,
26394 you shouldn't use the \ref QCPColorMapData::setData method as it uses a linear transformation to
26395 determine the cell index. Rather directly access the cell index with \ref
26396 QCPColorMapData::setCell.
26397*/
26398
26399/* start documentation of inline functions */
26400
26401/*! \fn QCPColorMapData *QCPColorMap::data() const
26402
26403 Returns a pointer to the internal data storage of type \ref QCPColorMapData. Access this to
26404 modify data points (cells) and the color map key/value range.
26405
26406 \see setData
26407*/
26408
26409/* end documentation of inline functions */
26410
26411/* start documentation of signals */
26412
26413/*! \fn void QCPColorMap::dataRangeChanged(const QCPRange &newRange);
26414
26415 This signal is emitted when the data range changes.
26416
26417 \see setDataRange
26418*/
26419
26420/*! \fn void QCPColorMap::dataScaleTypeChanged(QCPAxis::ScaleType scaleType);
26421
26422 This signal is emitted when the data scale type changes.
26423
26424 \see setDataScaleType
26425*/
26426
26427/*! \fn void QCPColorMap::gradientChanged(const QCPColorGradient &newGradient);
26428
26429 This signal is emitted when the gradient changes.
26430
26431 \see setGradient
26432*/
26433
26434/* end documentation of signals */
26435
26436/*!
26437 Constructs a color map with the specified \a keyAxis and \a valueAxis.
26438
26439 The created QCPColorMap is automatically registered with the QCustomPlot instance inferred from
26440 \a keyAxis. This QCustomPlot instance takes ownership of the QCPColorMap, so do not delete it
26441 manually but use QCustomPlot::removePlottable() instead.
26442*/
26444 QCPAbstractPlottable(keyAxis, valueAxis),
26445 mDataScaleType(QCPAxis::stLinear),
26446 mMapData(new QCPColorMapData(10, 10, QCPRange(0, 5), QCPRange(0, 5))),
26447 mGradient(QCPColorGradient::gpCold),
26448 mInterpolate(true),
26449 mTightBoundary(false),
26450 mMapImageInvalidated(true)
26451{
26452}
26453
26454QCPColorMap::~QCPColorMap()
26455{
26456 delete mMapData;
26457}
26458
26459/*!
26460 Replaces the current \ref data with the provided \a data.
26461
26462 If \a copy is set to true, the \a data object will only be copied. if false, the color map
26463 takes ownership of the passed data and replaces the internal data pointer with it. This is
26464 significantly faster than copying for large datasets.
26465*/
26467{
26468 if (mMapData == data)
26469 {
26470 qDebug() << Q_FUNC_INFO << "The data pointer is already in (and owned by) this plottable" << reinterpret_cast<quintptr>(data);
26471 return;
26472 }
26473 if (copy)
26474 {
26475 *mMapData = *data;
26476 } else
26477 {
26478 delete mMapData;
26479 mMapData = data;
26480 }
26481 mMapImageInvalidated = true;
26482}
26483
26484/*!
26485 Sets the data range of this color map to \a dataRange. The data range defines which data values
26486 are mapped to the color gradient.
26487
26488 To make the data range span the full range of the data set, use \ref rescaleDataRange.
26489
26490 \see QCPColorScale::setDataRange
26491*/
26493{
26494 if (!QCPRange::validRange(dataRange)) return;
26495 if (mDataRange.lower != dataRange.lower || mDataRange.upper != dataRange.upper)
26496 {
26497 if (mDataScaleType == QCPAxis::stLogarithmic)
26498 mDataRange = dataRange.sanitizedForLogScale();
26499 else
26500 mDataRange = dataRange.sanitizedForLinScale();
26501 mMapImageInvalidated = true;
26502 emit dataRangeChanged(mDataRange);
26503 }
26504}
26505
26506/*!
26507 Sets whether the data is correlated with the color gradient linearly or logarithmically.
26508
26509 \see QCPColorScale::setDataScaleType
26510*/
26512{
26513 if (mDataScaleType != scaleType)
26514 {
26515 mDataScaleType = scaleType;
26516 mMapImageInvalidated = true;
26517 emit dataScaleTypeChanged(mDataScaleType);
26518 if (mDataScaleType == QCPAxis::stLogarithmic)
26519 setDataRange(mDataRange.sanitizedForLogScale());
26520 }
26521}
26522
26523/*!
26524 Sets the color gradient that is used to represent the data. For more details on how to create an
26525 own gradient or use one of the preset gradients, see \ref QCPColorGradient.
26526
26527 The colors defined by the gradient will be used to represent data values in the currently set
26528 data range, see \ref setDataRange. Data points that are outside this data range will either be
26529 colored uniformly with the respective gradient boundary color, or the gradient will repeat,
26530 depending on \ref QCPColorGradient::setPeriodic.
26531
26532 \see QCPColorScale::setGradient
26533*/
26535{
26536 if (mGradient != gradient)
26537 {
26538 mGradient = gradient;
26539 mMapImageInvalidated = true;
26540 emit gradientChanged(mGradient);
26541 }
26542}
26543
26544/*!
26545 Sets whether the color map image shall use bicubic interpolation when displaying the color map
26546 shrinked or expanded, and not at a 1:1 pixel-to-data scale.
26547
26548 \image html QCPColorMap-interpolate.png "A 10*10 color map, with interpolation and without interpolation enabled"
26549*/
26551{
26552 mInterpolate = enabled;
26553 mMapImageInvalidated = true; // because oversampling factors might need to change
26554}
26555
26556/*!
26557 Sets whether the outer most data rows and columns are clipped to the specified key and value
26558 range (see \ref QCPColorMapData::setKeyRange, \ref QCPColorMapData::setValueRange).
26559
26560 if \a enabled is set to false, the data points at the border of the color map are drawn with the
26561 same width and height as all other data points. Since the data points are represented by
26562 rectangles of one color centered on the data coordinate, this means that the shown color map
26563 extends by half a data point over the specified key/value range in each direction.
26564
26565 \image html QCPColorMap-tightboundary.png "A color map, with tight boundary enabled and disabled"
26566*/
26568{
26569 mTightBoundary = enabled;
26570}
26571
26572/*!
26573 Associates the color scale \a colorScale with this color map.
26574
26575 This means that both the color scale and the color map synchronize their gradient, data range and
26576 data scale type (\ref setGradient, \ref setDataRange, \ref setDataScaleType). Multiple color maps
26577 can be associated with one single color scale. This causes the color maps to also synchronize
26578 those properties, via the mutual color scale.
26579
26580 This function causes the color map to adopt the current color gradient, data range and data scale
26581 type of \a colorScale. After this call, you may change these properties at either the color map
26582 or the color scale, and the setting will be applied to both.
26583
26584 Pass \c nullptr as \a colorScale to disconnect the color scale from this color map again.
26585*/
26587{
26588 if (mColorScale) // unconnect signals from old color scale
26589 {
26590 disconnect(this, SIGNAL(dataRangeChanged(QCPRange)), mColorScale.data(), SLOT(setDataRange(QCPRange)));
26592 disconnect(this, SIGNAL(gradientChanged(QCPColorGradient)), mColorScale.data(), SLOT(setGradient(QCPColorGradient)));
26593 disconnect(mColorScale.data(), SIGNAL(dataRangeChanged(QCPRange)), this, SLOT(setDataRange(QCPRange)));
26594 disconnect(mColorScale.data(), SIGNAL(gradientChanged(QCPColorGradient)), this, SLOT(setGradient(QCPColorGradient)));
26596 }
26597 mColorScale = colorScale;
26598 if (mColorScale) // connect signals to new color scale
26599 {
26600 setGradient(mColorScale.data()->gradient());
26601 setDataRange(mColorScale.data()->dataRange());
26602 setDataScaleType(mColorScale.data()->dataScaleType());
26603 connect(this, SIGNAL(dataRangeChanged(QCPRange)), mColorScale.data(), SLOT(setDataRange(QCPRange)));
26605 connect(this, SIGNAL(gradientChanged(QCPColorGradient)), mColorScale.data(), SLOT(setGradient(QCPColorGradient)));
26606 connect(mColorScale.data(), SIGNAL(dataRangeChanged(QCPRange)), this, SLOT(setDataRange(QCPRange)));
26607 connect(mColorScale.data(), SIGNAL(gradientChanged(QCPColorGradient)), this, SLOT(setGradient(QCPColorGradient)));
26609 }
26610}
26611
26612/*!
26613 Sets the data range (\ref setDataRange) to span the minimum and maximum values that occur in the
26614 current data set. This corresponds to the \ref rescaleKeyAxis or \ref rescaleValueAxis methods,
26615 only for the third data dimension of the color map.
26616
26617 The minimum and maximum values of the data set are buffered in the internal QCPColorMapData
26618 instance (\ref data). As data is updated via its \ref QCPColorMapData::setCell or \ref
26619 QCPColorMapData::setData, the buffered minimum and maximum values are updated, too. For
26620 performance reasons, however, they are only updated in an expanding fashion. So the buffered
26621 maximum can only increase and the buffered minimum can only decrease. In consequence, changes to
26622 the data that actually lower the maximum of the data set (by overwriting the cell holding the
26623 current maximum with a smaller value), aren't recognized and the buffered maximum overestimates
26624 the true maximum of the data set. The same happens for the buffered minimum. To recalculate the
26625 true minimum and maximum by explicitly looking at each cell, the method
26626 QCPColorMapData::recalculateDataBounds can be used. For convenience, setting the parameter \a
26627 recalculateDataBounds calls this method before setting the data range to the buffered minimum and
26628 maximum.
26629
26630 \see setDataRange
26631*/
26632void QCPColorMap::rescaleDataRange(bool recalculateDataBounds)
26633{
26634 if (recalculateDataBounds)
26635 mMapData->recalculateDataBounds();
26636 setDataRange(mMapData->dataBounds());
26637}
26638
26639/*!
26640 Takes the current appearance of the color map and updates the legend icon, which is used to
26641 represent this color map in the legend (see \ref QCPLegend).
26642
26643 The \a transformMode specifies whether the rescaling is done by a faster, low quality image
26644 scaling algorithm (Qt::FastTransformation) or by a slower, higher quality algorithm
26645 (Qt::SmoothTransformation).
26646
26647 The current color map appearance is scaled down to \a thumbSize. Ideally, this should be equal to
26648 the size of the legend icon (see \ref QCPLegend::setIconSize). If it isn't exactly the configured
26649 legend icon size, the thumb will be rescaled during drawing of the legend item.
26650
26651 \see setDataRange
26652*/
26654{
26655 if (mMapImage.isNull() && !data()->isEmpty())
26656 updateMapImage(); // try to update map image if it's null (happens if no draw has happened yet)
26657
26658 if (!mMapImage.isNull()) // might still be null, e.g. if data is empty, so check here again
26659 {
26660 bool mirrorX = (keyAxis()->orientation() == Qt::Horizontal ? keyAxis() : valueAxis())->rangeReversed();
26661 bool mirrorY = (valueAxis()->orientation() == Qt::Vertical ? valueAxis() : keyAxis())->rangeReversed();
26662 mLegendIcon = QPixmap::fromImage(mMapImage.mirrored(mirrorX, mirrorY)).scaled(thumbSize, Qt::KeepAspectRatio, transformMode);
26663 }
26664}
26665
26666/* inherits documentation from base class */
26667double QCPColorMap::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
26668{
26669 Q_UNUSED(details)
26670 if ((onlySelectable && mSelectable == QCP::stNone) || mMapData->isEmpty())
26671 return -1;
26672 if (!mKeyAxis || !mValueAxis)
26673 return -1;
26674
26675 if (mKeyAxis.data()->axisRect()->rect().contains(pos.toPoint()) || mParentPlot->interactions().testFlag(QCP::iSelectPlottablesBeyondAxisRect))
26676 {
26677 double posKey, posValue;
26678 pixelsToCoords(pos, posKey, posValue);
26679 if (mMapData->keyRange().contains(posKey) && mMapData->valueRange().contains(posValue))
26680 {
26681 if (details)
26682 details->setValue(QCPDataSelection(QCPDataRange(0, 1))); // temporary solution, to facilitate whole-plottable selection. Replace in future version with segmented 2D selection.
26683 return mParentPlot->selectionTolerance()*0.99;
26684 }
26685 }
26686 return -1;
26687}
26688
26689/* inherits documentation from base class */
26690QCPRange QCPColorMap::getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain) const
26691{
26692 foundRange = true;
26693 QCPRange result = mMapData->keyRange();
26694 result.normalize();
26695 if (inSignDomain == QCP::sdPositive)
26696 {
26697 if (result.lower <= 0 && result.upper > 0)
26698 result.lower = result.upper*1e-3;
26699 else if (result.lower <= 0 && result.upper <= 0)
26700 foundRange = false;
26701 } else if (inSignDomain == QCP::sdNegative)
26702 {
26703 if (result.upper >= 0 && result.lower < 0)
26704 result.upper = result.lower*1e-3;
26705 else if (result.upper >= 0 && result.lower >= 0)
26706 foundRange = false;
26707 }
26708 return result;
26709}
26710
26711/* inherits documentation from base class */
26712QCPRange QCPColorMap::getValueRange(bool &foundRange, QCP::SignDomain inSignDomain, const QCPRange &inKeyRange) const
26713{
26714 if (inKeyRange != QCPRange())
26715 {
26716 if (mMapData->keyRange().upper < inKeyRange.lower || mMapData->keyRange().lower > inKeyRange.upper)
26717 {
26718 foundRange = false;
26719 return {};
26720 }
26721 }
26722
26723 foundRange = true;
26724 QCPRange result = mMapData->valueRange();
26725 result.normalize();
26726 if (inSignDomain == QCP::sdPositive)
26727 {
26728 if (result.lower <= 0 && result.upper > 0)
26729 result.lower = result.upper*1e-3;
26730 else if (result.lower <= 0 && result.upper <= 0)
26731 foundRange = false;
26732 } else if (inSignDomain == QCP::sdNegative)
26733 {
26734 if (result.upper >= 0 && result.lower < 0)
26735 result.upper = result.lower*1e-3;
26736 else if (result.upper >= 0 && result.lower >= 0)
26737 foundRange = false;
26738 }
26739 return result;
26740}
26741
26742/*! \internal
26743
26744 Updates the internal map image buffer by going through the internal \ref QCPColorMapData and
26745 turning the data values into color pixels with \ref QCPColorGradient::colorize.
26746
26747 This method is called by \ref QCPColorMap::draw if either the data has been modified or the map image
26748 has been invalidated for a different reason (e.g. a change of the data range with \ref
26749 setDataRange).
26750
26751 If the map cell count is low, the image created will be oversampled in order to avoid a
26752 QPainter::drawImage bug which makes inner pixel boundaries jitter when stretch-drawing images
26753 without smooth transform enabled. Accordingly, oversampling isn't performed if \ref
26754 setInterpolate is true.
26755*/
26757{
26758 QCPAxis *keyAxis = mKeyAxis.data();
26759 if (!keyAxis) return;
26760 if (mMapData->isEmpty()) return;
26761
26763 const int keySize = mMapData->keySize();
26764 const int valueSize = mMapData->valueSize();
26765 int keyOversamplingFactor = mInterpolate ? 1 : int(1.0+100.0/double(keySize)); // make mMapImage have at least size 100, factor becomes 1 if size > 200 or interpolation is on
26766 int valueOversamplingFactor = mInterpolate ? 1 : int(1.0+100.0/double(valueSize)); // make mMapImage have at least size 100, factor becomes 1 if size > 200 or interpolation is on
26767
26768 // resize mMapImage to correct dimensions including possible oversampling factors, according to key/value axes orientation:
26769 if (keyAxis->orientation() == Qt::Horizontal && (mMapImage.width() != keySize*keyOversamplingFactor || mMapImage.height() != valueSize*valueOversamplingFactor))
26770 mMapImage = QImage(QSize(keySize*keyOversamplingFactor, valueSize*valueOversamplingFactor), format);
26771 else if (keyAxis->orientation() == Qt::Vertical && (mMapImage.width() != valueSize*valueOversamplingFactor || mMapImage.height() != keySize*keyOversamplingFactor))
26772 mMapImage = QImage(QSize(valueSize*valueOversamplingFactor, keySize*keyOversamplingFactor), format);
26773
26774 if (mMapImage.isNull())
26775 {
26776 qDebug() << Q_FUNC_INFO << "Couldn't create map image (possibly too large for memory)";
26777 mMapImage = QImage(QSize(10, 10), format);
26778 mMapImage.fill(Qt::black);
26779 } else
26780 {
26781 QImage *localMapImage = &mMapImage; // this is the image on which the colorization operates. Either the final mMapImage, or if we need oversampling, mUndersampledMapImage
26782 if (keyOversamplingFactor > 1 || valueOversamplingFactor > 1)
26783 {
26784 // resize undersampled map image to actual key/value cell sizes:
26785 if (keyAxis->orientation() == Qt::Horizontal && (mUndersampledMapImage.width() != keySize || mUndersampledMapImage.height() != valueSize))
26786 mUndersampledMapImage = QImage(QSize(keySize, valueSize), format);
26787 else if (keyAxis->orientation() == Qt::Vertical && (mUndersampledMapImage.width() != valueSize || mUndersampledMapImage.height() != keySize))
26788 mUndersampledMapImage = QImage(QSize(valueSize, keySize), format);
26789 localMapImage = &mUndersampledMapImage; // make the colorization run on the undersampled image
26790 } else if (!mUndersampledMapImage.isNull())
26791 mUndersampledMapImage = QImage(); // don't need oversampling mechanism anymore (map size has changed) but mUndersampledMapImage still has nonzero size, free it
26792
26793 const double *rawData = mMapData->mData;
26794 const unsigned char *rawAlpha = mMapData->mAlpha;
26795 if (keyAxis->orientation() == Qt::Horizontal)
26796 {
26797 const int lineCount = valueSize;
26798 const int rowCount = keySize;
26799 for (int line=0; line<lineCount; ++line)
26800 {
26801 QRgb* pixels = reinterpret_cast<QRgb*>(localMapImage->scanLine(lineCount-1-line)); // invert scanline index because QImage counts scanlines from top, but our vertical index counts from bottom (mathematical coordinate system)
26802 if (rawAlpha)
26803 mGradient.colorize(rawData+line*rowCount, rawAlpha+line*rowCount, mDataRange, pixels, rowCount, 1, mDataScaleType==QCPAxis::stLogarithmic);
26804 else
26805 mGradient.colorize(rawData+line*rowCount, mDataRange, pixels, rowCount, 1, mDataScaleType==QCPAxis::stLogarithmic);
26806 }
26807 } else // keyAxis->orientation() == Qt::Vertical
26808 {
26809 const int lineCount = keySize;
26810 const int rowCount = valueSize;
26811 for (int line=0; line<lineCount; ++line)
26812 {
26813 QRgb* pixels = reinterpret_cast<QRgb*>(localMapImage->scanLine(lineCount-1-line)); // invert scanline index because QImage counts scanlines from top, but our vertical index counts from bottom (mathematical coordinate system)
26814 if (rawAlpha)
26815 mGradient.colorize(rawData+line, rawAlpha+line, mDataRange, pixels, rowCount, lineCount, mDataScaleType==QCPAxis::stLogarithmic);
26816 else
26817 mGradient.colorize(rawData+line, mDataRange, pixels, rowCount, lineCount, mDataScaleType==QCPAxis::stLogarithmic);
26818 }
26819 }
26820
26821 if (keyOversamplingFactor > 1 || valueOversamplingFactor > 1)
26822 {
26823 if (keyAxis->orientation() == Qt::Horizontal)
26824 mMapImage = mUndersampledMapImage.scaled(keySize*keyOversamplingFactor, valueSize*valueOversamplingFactor, Qt::IgnoreAspectRatio, Qt::FastTransformation);
26825 else
26826 mMapImage = mUndersampledMapImage.scaled(valueSize*valueOversamplingFactor, keySize*keyOversamplingFactor, Qt::IgnoreAspectRatio, Qt::FastTransformation);
26827 }
26828 }
26829 mMapData->mDataModified = false;
26830 mMapImageInvalidated = false;
26831}
26832
26833/* inherits documentation from base class */
26835{
26836 if (mMapData->isEmpty()) return;
26837 if (!mKeyAxis || !mValueAxis) return;
26839
26840 if (mMapData->mDataModified || mMapImageInvalidated)
26842
26843 // use buffer if painting vectorized (PDF):
26844 const bool useBuffer = painter->modes().testFlag(QCPPainter::pmVectorized);
26845 QCPPainter *localPainter = painter; // will be redirected to paint on mapBuffer if painting vectorized
26846 QRectF mapBufferTarget; // the rect in absolute widget coordinates where the visible map portion/buffer will end up in
26847 QPixmap mapBuffer;
26848 if (useBuffer)
26849 {
26850 const double mapBufferPixelRatio = 3; // factor by which DPI is increased in embedded bitmaps
26851 mapBufferTarget = painter->clipRegion().boundingRect();
26852 mapBuffer = QPixmap((mapBufferTarget.size()*mapBufferPixelRatio).toSize());
26853 mapBuffer.fill(Qt::transparent);
26854 localPainter = new QCPPainter(&mapBuffer);
26855 localPainter->scale(mapBufferPixelRatio, mapBufferPixelRatio);
26856 localPainter->translate(-mapBufferTarget.topLeft());
26857 }
26858
26859 QRectF imageRect = QRectF(coordsToPixels(mMapData->keyRange().lower, mMapData->valueRange().lower),
26860 coordsToPixels(mMapData->keyRange().upper, mMapData->valueRange().upper)).normalized();
26861 // extend imageRect to contain outer halves/quarters of bordering/cornering pixels (cells are centered on map range boundary):
26862 double halfCellWidth = 0; // in pixels
26863 double halfCellHeight = 0; // in pixels
26864 if (keyAxis()->orientation() == Qt::Horizontal)
26865 {
26866 if (mMapData->keySize() > 1)
26867 halfCellWidth = 0.5*imageRect.width()/double(mMapData->keySize()-1);
26868 if (mMapData->valueSize() > 1)
26869 halfCellHeight = 0.5*imageRect.height()/double(mMapData->valueSize()-1);
26870 } else // keyAxis orientation is Qt::Vertical
26871 {
26872 if (mMapData->keySize() > 1)
26873 halfCellHeight = 0.5*imageRect.height()/double(mMapData->keySize()-1);
26874 if (mMapData->valueSize() > 1)
26875 halfCellWidth = 0.5*imageRect.width()/double(mMapData->valueSize()-1);
26876 }
26877 imageRect.adjust(-halfCellWidth, -halfCellHeight, halfCellWidth, halfCellHeight);
26878 const bool mirrorX = (keyAxis()->orientation() == Qt::Horizontal ? keyAxis() : valueAxis())->rangeReversed();
26879 const bool mirrorY = (valueAxis()->orientation() == Qt::Vertical ? valueAxis() : keyAxis())->rangeReversed();
26880 const bool smoothBackup = localPainter->renderHints().testFlag(QPainter::SmoothPixmapTransform);
26881 localPainter->setRenderHint(QPainter::SmoothPixmapTransform, mInterpolate);
26882 QRegion clipBackup;
26883 if (mTightBoundary)
26884 {
26885 clipBackup = localPainter->clipRegion();
26886 QRectF tightClipRect = QRectF(coordsToPixels(mMapData->keyRange().lower, mMapData->valueRange().lower),
26887 coordsToPixels(mMapData->keyRange().upper, mMapData->valueRange().upper)).normalized();
26888 localPainter->setClipRect(tightClipRect, Qt::IntersectClip);
26889 }
26890 localPainter->drawImage(imageRect, mMapImage.mirrored(mirrorX, mirrorY));
26891 if (mTightBoundary)
26892 localPainter->setClipRegion(clipBackup);
26893 localPainter->setRenderHint(QPainter::SmoothPixmapTransform, smoothBackup);
26894
26895 if (useBuffer) // localPainter painted to mapBuffer, so now draw buffer with original painter
26896 {
26897 delete localPainter;
26898 painter->drawPixmap(mapBufferTarget.toRect(), mapBuffer);
26899 }
26900}
26901
26902/* inherits documentation from base class */
26903void QCPColorMap::drawLegendIcon(QCPPainter *painter, const QRectF &rect) const
26904{
26906 // draw map thumbnail:
26907 if (!mLegendIcon.isNull())
26908 {
26909 QPixmap scaledIcon = mLegendIcon.scaled(rect.size().toSize(), Qt::KeepAspectRatio, Qt::FastTransformation);
26910 QRectF iconRect = QRectF(0, 0, scaledIcon.width(), scaledIcon.height());
26911 iconRect.moveCenter(rect.center());
26912 painter->drawPixmap(iconRect.topLeft(), scaledIcon);
26913 }
26914 /*
26915 // draw frame:
26916 painter->setBrush(Qt::NoBrush);
26917 painter->setPen(Qt::black);
26918 painter->drawRect(rect.adjusted(1, 1, 0, 0));
26919 */
26920}
26921/* end of 'src/plottables/plottable-colormap.cpp' */
26922
26923
26924/* including file 'src/plottables/plottable-financial.cpp' */
26925/* modified 2022-11-06T12:45:57, size 42914 */
26926
26927////////////////////////////////////////////////////////////////////////////////////////////////////
26928//////////////////// QCPFinancialData
26929////////////////////////////////////////////////////////////////////////////////////////////////////
26930
26931/*! \class QCPFinancialData
26932 \brief Holds the data of one single data point for QCPFinancial.
26933
26934 The stored data is:
26935 \li \a key: coordinate on the key axis of this data point (this is the \a mainKey and the \a sortKey)
26936 \li \a open: The opening value at the data point (this is the \a mainValue)
26937 \li \a high: The high/maximum value at the data point
26938 \li \a low: The low/minimum value at the data point
26939 \li \a close: The closing value at the data point
26940
26941 The container for storing multiple data points is \ref QCPFinancialDataContainer. It is a typedef
26942 for \ref QCPDataContainer with \ref QCPFinancialData as the DataType template parameter. See the
26943 documentation there for an explanation regarding the data type's generic methods.
26944
26945 \see QCPFinancialDataContainer
26946*/
26947
26948/* start documentation of inline functions */
26949
26950/*! \fn double QCPFinancialData::sortKey() const
26951
26952 Returns the \a key member of this data point.
26953
26954 For a general explanation of what this method is good for in the context of the data container,
26955 see the documentation of \ref QCPDataContainer.
26956*/
26957
26958/*! \fn static QCPFinancialData QCPFinancialData::fromSortKey(double sortKey)
26959
26960 Returns a data point with the specified \a sortKey. All other members are set to zero.
26961
26962 For a general explanation of what this method is good for in the context of the data container,
26963 see the documentation of \ref QCPDataContainer.
26964*/
26965
26966/*! \fn static static bool QCPFinancialData::sortKeyIsMainKey()
26967
26968 Since the member \a key is both the data point key coordinate and the data ordering parameter,
26969 this method returns true.
26970
26971 For a general explanation of what this method is good for in the context of the data container,
26972 see the documentation of \ref QCPDataContainer.
26973*/
26974
26975/*! \fn double QCPFinancialData::mainKey() const
26976
26977 Returns the \a key member of this data point.
26978
26979 For a general explanation of what this method is good for in the context of the data container,
26980 see the documentation of \ref QCPDataContainer.
26981*/
26982
26983/*! \fn double QCPFinancialData::mainValue() const
26984
26985 Returns the \a open member of this data point.
26986
26987 For a general explanation of what this method is good for in the context of the data container,
26988 see the documentation of \ref QCPDataContainer.
26989*/
26990
26991/*! \fn QCPRange QCPFinancialData::valueRange() const
26992
26993 Returns a QCPRange spanning from the \a low to the \a high value of this data point.
26994
26995 For a general explanation of what this method is good for in the context of the data container,
26996 see the documentation of \ref QCPDataContainer.
26997*/
26998
26999/* end documentation of inline functions */
27000
27001/*!
27002 Constructs a data point with key and all values set to zero.
27003*/
27005 key(0),
27006 open(0),
27007 high(0),
27008 low(0),
27009 close(0)
27010{
27011}
27012
27013/*!
27014 Constructs a data point with the specified \a key and OHLC values.
27015*/
27016QCPFinancialData::QCPFinancialData(double key, double open, double high, double low, double close) :
27017 key(key),
27018 open(open),
27019 high(high),
27020 low(low),
27021 close(close)
27022{
27023}
27024
27025
27026////////////////////////////////////////////////////////////////////////////////////////////////////
27027//////////////////// QCPFinancial
27028////////////////////////////////////////////////////////////////////////////////////////////////////
27029
27030/*! \class QCPFinancial
27031 \brief A plottable representing a financial stock chart
27032
27033 \image html QCPFinancial.png
27034
27035 This plottable represents time series data binned to certain intervals, mainly used for stock
27036 charts. The two common representations OHLC (Open-High-Low-Close) bars and Candlesticks can be
27037 set via \ref setChartStyle.
27038
27039 The data is passed via \ref setData as a set of open/high/low/close values at certain keys
27040 (typically times). This means the data must be already binned appropriately. If data is only
27041 available as a series of values (e.g. \a price against \a time), you can use the static
27042 convenience function \ref timeSeriesToOhlc to generate binned OHLC-data which can then be passed
27043 to \ref setData.
27044
27045 The width of the OHLC bars/candlesticks can be controlled with \ref setWidth and \ref
27046 setWidthType. A typical choice is to set the width type to \ref wtPlotCoords (the default) and
27047 the width to (or slightly less than) one time bin interval width.
27048
27049 \section qcpfinancial-appearance Changing the appearance
27050
27051 Charts can be either single- or two-colored (\ref setTwoColored). If set to be single-colored,
27052 lines are drawn with the plottable's pen (\ref setPen) and fills with the brush (\ref setBrush).
27053
27054 If set to two-colored, positive changes of the value during an interval (\a close >= \a open) are
27055 represented with a different pen and brush than negative changes (\a close < \a open). These can
27056 be configured with \ref setPenPositive, \ref setPenNegative, \ref setBrushPositive, and \ref
27057 setBrushNegative. In two-colored mode, the normal plottable pen/brush is ignored. Upon selection
27058 however, the normal selected pen/brush (provided by the \ref selectionDecorator) is used,
27059 irrespective of whether the chart is single- or two-colored.
27060
27061 \section qcpfinancial-usage Usage
27062
27063 Like all data representing objects in QCustomPlot, the QCPFinancial is a plottable
27064 (QCPAbstractPlottable). So the plottable-interface of QCustomPlot applies
27065 (QCustomPlot::plottable, QCustomPlot::removePlottable, etc.)
27066
27067 Usually, you first create an instance:
27068
27069 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpfinancial-creation-1
27070 which registers it with the QCustomPlot instance of the passed axes. Note that this QCustomPlot
27071 instance takes ownership of the plottable, so do not delete it manually but use
27072 QCustomPlot::removePlottable() instead. The newly created plottable can be modified, e.g.:
27073
27074 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpfinancial-creation-2
27075 Here we have used the static helper method \ref timeSeriesToOhlc, to turn a time-price data
27076 series into a 24-hour binned open-high-low-close data series as QCPFinancial uses.
27077*/
27078
27079/* start of documentation of inline functions */
27080
27081/*! \fn QCPFinancialDataContainer *QCPFinancial::data() const
27082
27083 Returns a pointer to the internal data storage of type \ref QCPFinancialDataContainer. You may
27084 use it to directly manipulate the data, which may be more convenient and faster than using the
27085 regular \ref setData or \ref addData methods, in certain situations.
27086*/
27087
27088/* end of documentation of inline functions */
27089
27090/*!
27091 Constructs a financial chart which uses \a keyAxis as its key axis ("x") and \a valueAxis as its value
27092 axis ("y"). \a keyAxis and \a valueAxis must reside in the same QCustomPlot instance and not have
27093 the same orientation. If either of these restrictions is violated, a corresponding message is
27094 printed to the debug output (qDebug), the construction is not aborted, though.
27095
27096 The created QCPFinancial is automatically registered with the QCustomPlot instance inferred from \a
27097 keyAxis. This QCustomPlot instance takes ownership of the QCPFinancial, so do not delete it manually
27098 but use QCustomPlot::removePlottable() instead.
27099*/
27101 QCPAbstractPlottable1D<QCPFinancialData>(keyAxis, valueAxis),
27102 mChartStyle(csCandlestick),
27103 mWidth(0.5),
27104 mWidthType(wtPlotCoords),
27105 mTwoColored(true),
27106 mBrushPositive(QBrush(QColor(50, 160, 0))),
27107 mBrushNegative(QBrush(QColor(180, 0, 15))),
27108 mPenPositive(QPen(QColor(40, 150, 0))),
27109 mPenNegative(QPen(QColor(170, 5, 5)))
27110{
27111 mSelectionDecorator->setBrush(QBrush(QColor(160, 160, 255)));
27112}
27113
27114QCPFinancial::~QCPFinancial()
27115{
27116}
27117
27118/*! \overload
27119
27120 Replaces the current data container with the provided \a data container.
27121
27122 Since a QSharedPointer is used, multiple QCPFinancials may share the same data container safely.
27123 Modifying the data in the container will then affect all financials that share the container.
27124 Sharing can be achieved by simply exchanging the data containers wrapped in shared pointers:
27125 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpfinancial-datasharing-1
27126
27127 If you do not wish to share containers, but create a copy from an existing container, rather use
27128 the \ref QCPDataContainer<DataType>::set method on the financial's data container directly:
27129 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpfinancial-datasharing-2
27130
27131 \see addData, timeSeriesToOhlc
27132*/
27134{
27135 mDataContainer = data;
27136}
27137
27138/*! \overload
27139
27140 Replaces the current data with the provided points in \a keys, \a open, \a high, \a low and \a
27141 close. The provided vectors should have equal length. Else, the number of added points will be
27142 the size of the smallest vector.
27143
27144 If you can guarantee that the passed data points are sorted by \a keys in ascending order, you
27145 can set \a alreadySorted to true, to improve performance by saving a sorting run.
27146
27147 \see addData, timeSeriesToOhlc
27148*/
27149void QCPFinancial::setData(const QVector<double> &keys, const QVector<double> &open, const QVector<double> &high, const QVector<double> &low, const QVector<double> &close, bool alreadySorted)
27150{
27151 mDataContainer->clear();
27152 addData(keys, open, high, low, close, alreadySorted);
27153}
27154
27155/*!
27156 Sets which representation style shall be used to display the OHLC data.
27157*/
27159{
27160 mChartStyle = style;
27161}
27162
27163/*!
27164 Sets the width of the individual bars/candlesticks to \a width in plot key coordinates.
27165
27166 A typical choice is to set it to (or slightly less than) one bin interval width.
27167*/
27168void QCPFinancial::setWidth(double width)
27169{
27170 mWidth = width;
27171}
27172
27173/*!
27174 Sets how the width of the financial bars is defined. See the documentation of \ref WidthType for
27175 an explanation of the possible values for \a widthType.
27176
27177 The default value is \ref wtPlotCoords.
27178
27179 \see setWidth
27180*/
27182{
27183 mWidthType = widthType;
27184}
27185
27186/*!
27187 Sets whether this chart shall contrast positive from negative trends per data point by using two
27188 separate colors to draw the respective bars/candlesticks.
27189
27190 If \a twoColored is false, the normal plottable's pen and brush are used (\ref setPen, \ref
27191 setBrush).
27192
27193 \see setPenPositive, setPenNegative, setBrushPositive, setBrushNegative
27194*/
27195void QCPFinancial::setTwoColored(bool twoColored)
27196{
27197 mTwoColored = twoColored;
27198}
27199
27200/*!
27201 If \ref setTwoColored is set to true, this function controls the brush that is used to draw fills
27202 of data points with a positive trend (i.e. bars/candlesticks with close >= open).
27203
27204 If \a twoColored is false, the normal plottable's pen and brush are used (\ref setPen, \ref
27205 setBrush).
27206
27207 \see setBrushNegative, setPenPositive, setPenNegative
27208*/
27210{
27211 mBrushPositive = brush;
27212}
27213
27214/*!
27215 If \ref setTwoColored is set to true, this function controls the brush that is used to draw fills
27216 of data points with a negative trend (i.e. bars/candlesticks with close < open).
27217
27218 If \a twoColored is false, the normal plottable's pen and brush are used (\ref setPen, \ref
27219 setBrush).
27220
27221 \see setBrushPositive, setPenNegative, setPenPositive
27222*/
27224{
27225 mBrushNegative = brush;
27226}
27227
27228/*!
27229 If \ref setTwoColored is set to true, this function controls the pen that is used to draw
27230 outlines of data points with a positive trend (i.e. bars/candlesticks with close >= open).
27231
27232 If \a twoColored is false, the normal plottable's pen and brush are used (\ref setPen, \ref
27233 setBrush).
27234
27235 \see setPenNegative, setBrushPositive, setBrushNegative
27236*/
27238{
27239 mPenPositive = pen;
27240}
27241
27242/*!
27243 If \ref setTwoColored is set to true, this function controls the pen that is used to draw
27244 outlines of data points with a negative trend (i.e. bars/candlesticks with close < open).
27245
27246 If \a twoColored is false, the normal plottable's pen and brush are used (\ref setPen, \ref
27247 setBrush).
27248
27249 \see setPenPositive, setBrushNegative, setBrushPositive
27250*/
27252{
27253 mPenNegative = pen;
27254}
27255
27256/*! \overload
27257
27258 Adds the provided points in \a keys, \a open, \a high, \a low and \a close to the current data.
27259 The provided vectors should have equal length. Else, the number of added points will be the size
27260 of the smallest vector.
27261
27262 If you can guarantee that the passed data points are sorted by \a keys in ascending order, you
27263 can set \a alreadySorted to true, to improve performance by saving a sorting run.
27264
27265 Alternatively, you can also access and modify the data directly via the \ref data method, which
27266 returns a pointer to the internal data container.
27267
27268 \see timeSeriesToOhlc
27269*/
27270void QCPFinancial::addData(const QVector<double> &keys, const QVector<double> &open, const QVector<double> &high, const QVector<double> &low, const QVector<double> &close, bool alreadySorted)
27271{
27272 if (keys.size() != open.size() || open.size() != high.size() || high.size() != low.size() || low.size() != close.size() || close.size() != keys.size())
27273 qDebug() << Q_FUNC_INFO << "keys, open, high, low, close have different sizes:" << keys.size() << open.size() << high.size() << low.size() << close.size();
27274 const int n = qMin(keys.size(), qMin(open.size(), qMin(high.size(), qMin(low.size(), close.size()))));
27275 QVector<QCPFinancialData> tempData(n);
27277 const QVector<QCPFinancialData>::iterator itEnd = tempData.end();
27278 int i = 0;
27279 while (it != itEnd)
27280 {
27281 it->key = keys[i];
27282 it->open = open[i];
27283 it->high = high[i];
27284 it->low = low[i];
27285 it->close = close[i];
27286 ++it;
27287 ++i;
27288 }
27289 mDataContainer->add(tempData, alreadySorted); // don't modify tempData beyond this to prevent copy on write
27290}
27291
27292/*! \overload
27293
27294 Adds the provided data point as \a key, \a open, \a high, \a low and \a close to the current
27295 data.
27296
27297 Alternatively, you can also access and modify the data directly via the \ref data method, which
27298 returns a pointer to the internal data container.
27299
27300 \see timeSeriesToOhlc
27301*/
27302void QCPFinancial::addData(double key, double open, double high, double low, double close)
27303{
27304 mDataContainer->add(QCPFinancialData(key, open, high, low, close));
27305}
27306
27307/*!
27308 \copydoc QCPPlottableInterface1D::selectTestRect
27309*/
27310QCPDataSelection QCPFinancial::selectTestRect(const QRectF &rect, bool onlySelectable) const
27311{
27312 QCPDataSelection result;
27313 if ((onlySelectable && mSelectable == QCP::stNone) || mDataContainer->isEmpty())
27314 return result;
27315 if (!mKeyAxis || !mValueAxis)
27316 return result;
27317
27318 QCPFinancialDataContainer::const_iterator visibleBegin, visibleEnd;
27319 getVisibleDataBounds(visibleBegin, visibleEnd);
27320
27321 for (QCPFinancialDataContainer::const_iterator it=visibleBegin; it!=visibleEnd; ++it)
27322 {
27323 if (rect.intersects(selectionHitBox(it)))
27324 result.addDataRange(QCPDataRange(int(it-mDataContainer->constBegin()), int(it-mDataContainer->constBegin()+1)), false);
27325 }
27326 result.simplify();
27327 return result;
27328}
27329
27330/*!
27331 Implements a selectTest specific to this plottable's point geometry.
27332
27333 If \a details is not 0, it will be set to a \ref QCPDataSelection, describing the closest data
27334 point to \a pos.
27335
27336 \seebaseclassmethod \ref QCPAbstractPlottable::selectTest
27337*/
27338double QCPFinancial::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
27339{
27340 Q_UNUSED(details)
27341 if ((onlySelectable && mSelectable == QCP::stNone) || mDataContainer->isEmpty())
27342 return -1;
27343 if (!mKeyAxis || !mValueAxis)
27344 return -1;
27345
27346 if (mKeyAxis.data()->axisRect()->rect().contains(pos.toPoint()) || mParentPlot->interactions().testFlag(QCP::iSelectPlottablesBeyondAxisRect))
27347 {
27348 // get visible data range:
27349 QCPFinancialDataContainer::const_iterator visibleBegin, visibleEnd;
27350 QCPFinancialDataContainer::const_iterator closestDataPoint = mDataContainer->constEnd();
27351 getVisibleDataBounds(visibleBegin, visibleEnd);
27352 // perform select test according to configured style:
27353 double result = -1;
27354 switch (mChartStyle)
27355 {
27357 result = ohlcSelectTest(pos, visibleBegin, visibleEnd, closestDataPoint); break;
27359 result = candlestickSelectTest(pos, visibleBegin, visibleEnd, closestDataPoint); break;
27360 }
27361 if (details)
27362 {
27363 int pointIndex = int(closestDataPoint-mDataContainer->constBegin());
27364 details->setValue(QCPDataSelection(QCPDataRange(pointIndex, pointIndex+1)));
27365 }
27366 return result;
27367 }
27368
27369 return -1;
27370}
27371
27372/* inherits documentation from base class */
27373QCPRange QCPFinancial::getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain) const
27374{
27375 QCPRange range = mDataContainer->keyRange(foundRange, inSignDomain);
27376 // determine exact range by including width of bars/flags:
27377 if (foundRange)
27378 {
27379 if (inSignDomain != QCP::sdPositive || range.lower-mWidth*0.5 > 0)
27380 range.lower -= mWidth*0.5;
27381 if (inSignDomain != QCP::sdNegative || range.upper+mWidth*0.5 < 0)
27382 range.upper += mWidth*0.5;
27383 }
27384 return range;
27385}
27386
27387/* inherits documentation from base class */
27388QCPRange QCPFinancial::getValueRange(bool &foundRange, QCP::SignDomain inSignDomain, const QCPRange &inKeyRange) const
27389{
27390 return mDataContainer->valueRange(foundRange, inSignDomain, inKeyRange);
27391}
27392
27393/*!
27394 A convenience function that converts time series data (\a value against \a time) to OHLC binned
27395 data points. The return value can then be passed on to \ref QCPFinancialDataContainer::set(const
27396 QCPFinancialDataContainer&).
27397
27398 The size of the bins can be controlled with \a timeBinSize in the same units as \a time is given.
27399 For example, if the unit of \a time is seconds and single OHLC/Candlesticks should span an hour
27400 each, set \a timeBinSize to 3600.
27401
27402 \a timeBinOffset allows to control precisely at what \a time coordinate a bin should start. The
27403 value passed as \a timeBinOffset doesn't need to be in the range encompassed by the \a time keys.
27404 It merely defines the mathematical offset/phase of the bins that will be used to process the
27405 data.
27406*/
27407QCPFinancialDataContainer QCPFinancial::timeSeriesToOhlc(const QVector<double> &time, const QVector<double> &value, double timeBinSize, double timeBinOffset)
27408{
27410 int count = qMin(time.size(), value.size());
27411 if (count == 0)
27413
27414 QCPFinancialData currentBinData(0, value.first(), value.first(), value.first(), value.first());
27415 int currentBinIndex = qFloor((time.first()-timeBinOffset)/timeBinSize+0.5);
27416 for (int i=0; i<count; ++i)
27417 {
27418 int index = qFloor((time.at(i)-timeBinOffset)/timeBinSize+0.5);
27419 if (currentBinIndex == index) // data point still in current bin, extend high/low:
27420 {
27421 if (value.at(i) < currentBinData.low) currentBinData.low = value.at(i);
27422 if (value.at(i) > currentBinData.high) currentBinData.high = value.at(i);
27423 if (i == count-1) // last data point is in current bin, finalize bin:
27424 {
27425 currentBinData.close = value.at(i);
27426 currentBinData.key = timeBinOffset+(index)*timeBinSize;
27427 data.add(currentBinData);
27428 }
27429 } else // data point not anymore in current bin, set close of old and open of new bin, and add old to map:
27430 {
27431 // finalize current bin:
27432 currentBinData.close = value.at(i-1);
27433 currentBinData.key = timeBinOffset+(index-1)*timeBinSize;
27434 data.add(currentBinData);
27435 // start next bin:
27436 currentBinIndex = index;
27437 currentBinData.open = value.at(i);
27438 currentBinData.high = value.at(i);
27439 currentBinData.low = value.at(i);
27440 }
27441 }
27442
27443 return data;
27444}
27445
27446/* inherits documentation from base class */
27448{
27449 // get visible data range:
27450 QCPFinancialDataContainer::const_iterator visibleBegin, visibleEnd;
27451 getVisibleDataBounds(visibleBegin, visibleEnd);
27452
27453 // loop over and draw segments of unselected/selected data:
27454 QList<QCPDataRange> selectedSegments, unselectedSegments, allSegments;
27455 getDataSegments(selectedSegments, unselectedSegments);
27456 allSegments << unselectedSegments << selectedSegments;
27457 for (int i=0; i<allSegments.size(); ++i)
27458 {
27459 bool isSelectedSegment = i >= unselectedSegments.size();
27460 QCPFinancialDataContainer::const_iterator begin = visibleBegin;
27462 mDataContainer->limitIteratorsToDataRange(begin, end, allSegments.at(i));
27463 if (begin == end)
27464 continue;
27465
27466 // draw data segment according to configured style:
27467 switch (mChartStyle)
27468 {
27470 drawOhlcPlot(painter, begin, end, isSelectedSegment); break;
27472 drawCandlestickPlot(painter, begin, end, isSelectedSegment); break;
27473 }
27474 }
27475
27476 // draw other selection decoration that isn't just line/scatter pens and brushes:
27477 if (mSelectionDecorator)
27478 mSelectionDecorator->drawDecoration(painter, selection());
27479}
27480
27481/* inherits documentation from base class */
27482void QCPFinancial::drawLegendIcon(QCPPainter *painter, const QRectF &rect) const
27483{
27484 painter->setAntialiasing(false); // legend icon especially of csCandlestick looks better without antialiasing
27485 if (mChartStyle == csOhlc)
27486 {
27487 if (mTwoColored)
27488 {
27489 // draw upper left half icon with positive color:
27490 painter->setBrush(mBrushPositive);
27491 painter->setPen(mPenPositive);
27492 painter->setClipRegion(QRegion(QPolygon() << rect.bottomLeft().toPoint() << rect.topRight().toPoint() << rect.topLeft().toPoint()));
27493 painter->drawLine(QLineF(0, rect.height()*0.5, rect.width(), rect.height()*0.5).translated(rect.topLeft()));
27494 painter->drawLine(QLineF(rect.width()*0.2, rect.height()*0.3, rect.width()*0.2, rect.height()*0.5).translated(rect.topLeft()));
27495 painter->drawLine(QLineF(rect.width()*0.8, rect.height()*0.5, rect.width()*0.8, rect.height()*0.7).translated(rect.topLeft()));
27496 // draw bottom right half icon with negative color:
27497 painter->setBrush(mBrushNegative);
27498 painter->setPen(mPenNegative);
27499 painter->setClipRegion(QRegion(QPolygon() << rect.bottomLeft().toPoint() << rect.topRight().toPoint() << rect.bottomRight().toPoint()));
27500 painter->drawLine(QLineF(0, rect.height()*0.5, rect.width(), rect.height()*0.5).translated(rect.topLeft()));
27501 painter->drawLine(QLineF(rect.width()*0.2, rect.height()*0.3, rect.width()*0.2, rect.height()*0.5).translated(rect.topLeft()));
27502 painter->drawLine(QLineF(rect.width()*0.8, rect.height()*0.5, rect.width()*0.8, rect.height()*0.7).translated(rect.topLeft()));
27503 } else
27504 {
27505 painter->setBrush(mBrush);
27506 painter->setPen(mPen);
27507 painter->drawLine(QLineF(0, rect.height()*0.5, rect.width(), rect.height()*0.5).translated(rect.topLeft()));
27508 painter->drawLine(QLineF(rect.width()*0.2, rect.height()*0.3, rect.width()*0.2, rect.height()*0.5).translated(rect.topLeft()));
27509 painter->drawLine(QLineF(rect.width()*0.8, rect.height()*0.5, rect.width()*0.8, rect.height()*0.7).translated(rect.topLeft()));
27510 }
27511 } else if (mChartStyle == csCandlestick)
27512 {
27513 if (mTwoColored)
27514 {
27515 // draw upper left half icon with positive color:
27516 painter->setBrush(mBrushPositive);
27517 painter->setPen(mPenPositive);
27518 painter->setClipRegion(QRegion(QPolygon() << rect.bottomLeft().toPoint() << rect.topRight().toPoint() << rect.topLeft().toPoint()));
27519 painter->drawLine(QLineF(0, rect.height()*0.5, rect.width()*0.25, rect.height()*0.5).translated(rect.topLeft()));
27520 painter->drawLine(QLineF(rect.width()*0.75, rect.height()*0.5, rect.width(), rect.height()*0.5).translated(rect.topLeft()));
27521 painter->drawRect(QRectF(rect.width()*0.25, rect.height()*0.25, rect.width()*0.5, rect.height()*0.5).translated(rect.topLeft()));
27522 // draw bottom right half icon with negative color:
27523 painter->setBrush(mBrushNegative);
27524 painter->setPen(mPenNegative);
27525 painter->setClipRegion(QRegion(QPolygon() << rect.bottomLeft().toPoint() << rect.topRight().toPoint() << rect.bottomRight().toPoint()));
27526 painter->drawLine(QLineF(0, rect.height()*0.5, rect.width()*0.25, rect.height()*0.5).translated(rect.topLeft()));
27527 painter->drawLine(QLineF(rect.width()*0.75, rect.height()*0.5, rect.width(), rect.height()*0.5).translated(rect.topLeft()));
27528 painter->drawRect(QRectF(rect.width()*0.25, rect.height()*0.25, rect.width()*0.5, rect.height()*0.5).translated(rect.topLeft()));
27529 } else
27530 {
27531 painter->setBrush(mBrush);
27532 painter->setPen(mPen);
27533 painter->drawLine(QLineF(0, rect.height()*0.5, rect.width()*0.25, rect.height()*0.5).translated(rect.topLeft()));
27534 painter->drawLine(QLineF(rect.width()*0.75, rect.height()*0.5, rect.width(), rect.height()*0.5).translated(rect.topLeft()));
27535 painter->drawRect(QRectF(rect.width()*0.25, rect.height()*0.25, rect.width()*0.5, rect.height()*0.5).translated(rect.topLeft()));
27536 }
27537 }
27538}
27539
27540/*! \internal
27541
27542 Draws the data from \a begin to \a end-1 as OHLC bars with the provided \a painter.
27543
27544 This method is a helper function for \ref draw. It is used when the chart style is \ref csOhlc.
27545*/
27547{
27548 QCPAxis *keyAxis = mKeyAxis.data();
27549 QCPAxis *valueAxis = mValueAxis.data();
27550 if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
27551
27552 if (keyAxis->orientation() == Qt::Horizontal)
27553 {
27554 for (QCPFinancialDataContainer::const_iterator it = begin; it != end; ++it)
27555 {
27556 if (isSelected && mSelectionDecorator)
27557 mSelectionDecorator->applyPen(painter);
27558 else if (mTwoColored)
27559 painter->setPen(it->close >= it->open ? mPenPositive : mPenNegative);
27560 else
27561 painter->setPen(mPen);
27562 double keyPixel = keyAxis->coordToPixel(it->key);
27563 double openPixel = valueAxis->coordToPixel(it->open);
27564 double closePixel = valueAxis->coordToPixel(it->close);
27565 // draw backbone:
27566 painter->drawLine(QPointF(keyPixel, valueAxis->coordToPixel(it->high)), QPointF(keyPixel, valueAxis->coordToPixel(it->low)));
27567 // draw open:
27568 double pixelWidth = getPixelWidth(it->key, keyPixel); // sign of this makes sure open/close are on correct sides
27569 painter->drawLine(QPointF(keyPixel-pixelWidth, openPixel), QPointF(keyPixel, openPixel));
27570 // draw close:
27571 painter->drawLine(QPointF(keyPixel, closePixel), QPointF(keyPixel+pixelWidth, closePixel));
27572 }
27573 } else
27574 {
27575 for (QCPFinancialDataContainer::const_iterator it = begin; it != end; ++it)
27576 {
27577 if (isSelected && mSelectionDecorator)
27578 mSelectionDecorator->applyPen(painter);
27579 else if (mTwoColored)
27580 painter->setPen(it->close >= it->open ? mPenPositive : mPenNegative);
27581 else
27582 painter->setPen(mPen);
27583 double keyPixel = keyAxis->coordToPixel(it->key);
27584 double openPixel = valueAxis->coordToPixel(it->open);
27585 double closePixel = valueAxis->coordToPixel(it->close);
27586 // draw backbone:
27587 painter->drawLine(QPointF(valueAxis->coordToPixel(it->high), keyPixel), QPointF(valueAxis->coordToPixel(it->low), keyPixel));
27588 // draw open:
27589 double pixelWidth = getPixelWidth(it->key, keyPixel); // sign of this makes sure open/close are on correct sides
27590 painter->drawLine(QPointF(openPixel, keyPixel-pixelWidth), QPointF(openPixel, keyPixel));
27591 // draw close:
27592 painter->drawLine(QPointF(closePixel, keyPixel), QPointF(closePixel, keyPixel+pixelWidth));
27593 }
27594 }
27595}
27596
27597/*! \internal
27598
27599 Draws the data from \a begin to \a end-1 as Candlesticks with the provided \a painter.
27600
27601 This method is a helper function for \ref draw. It is used when the chart style is \ref csCandlestick.
27602*/
27604{
27605 QCPAxis *keyAxis = mKeyAxis.data();
27606 QCPAxis *valueAxis = mValueAxis.data();
27607 if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
27608
27609 if (keyAxis->orientation() == Qt::Horizontal)
27610 {
27611 for (QCPFinancialDataContainer::const_iterator it = begin; it != end; ++it)
27612 {
27613 if (isSelected && mSelectionDecorator)
27614 {
27615 mSelectionDecorator->applyPen(painter);
27616 mSelectionDecorator->applyBrush(painter);
27617 } else if (mTwoColored)
27618 {
27619 painter->setPen(it->close >= it->open ? mPenPositive : mPenNegative);
27620 painter->setBrush(it->close >= it->open ? mBrushPositive : mBrushNegative);
27621 } else
27622 {
27623 painter->setPen(mPen);
27624 painter->setBrush(mBrush);
27625 }
27626 double keyPixel = keyAxis->coordToPixel(it->key);
27627 double openPixel = valueAxis->coordToPixel(it->open);
27628 double closePixel = valueAxis->coordToPixel(it->close);
27629 // draw high:
27630 painter->drawLine(QPointF(keyPixel, valueAxis->coordToPixel(it->high)), QPointF(keyPixel, valueAxis->coordToPixel(qMax(it->open, it->close))));
27631 // draw low:
27632 painter->drawLine(QPointF(keyPixel, valueAxis->coordToPixel(it->low)), QPointF(keyPixel, valueAxis->coordToPixel(qMin(it->open, it->close))));
27633 // draw open-close box:
27634 double pixelWidth = getPixelWidth(it->key, keyPixel);
27635 painter->drawRect(QRectF(QPointF(keyPixel-pixelWidth, closePixel), QPointF(keyPixel+pixelWidth, openPixel)));
27636 }
27637 } else // keyAxis->orientation() == Qt::Vertical
27638 {
27639 for (QCPFinancialDataContainer::const_iterator it = begin; it != end; ++it)
27640 {
27641 if (isSelected && mSelectionDecorator)
27642 {
27643 mSelectionDecorator->applyPen(painter);
27644 mSelectionDecorator->applyBrush(painter);
27645 } else if (mTwoColored)
27646 {
27647 painter->setPen(it->close >= it->open ? mPenPositive : mPenNegative);
27648 painter->setBrush(it->close >= it->open ? mBrushPositive : mBrushNegative);
27649 } else
27650 {
27651 painter->setPen(mPen);
27652 painter->setBrush(mBrush);
27653 }
27654 double keyPixel = keyAxis->coordToPixel(it->key);
27655 double openPixel = valueAxis->coordToPixel(it->open);
27656 double closePixel = valueAxis->coordToPixel(it->close);
27657 // draw high:
27658 painter->drawLine(QPointF(valueAxis->coordToPixel(it->high), keyPixel), QPointF(valueAxis->coordToPixel(qMax(it->open, it->close)), keyPixel));
27659 // draw low:
27660 painter->drawLine(QPointF(valueAxis->coordToPixel(it->low), keyPixel), QPointF(valueAxis->coordToPixel(qMin(it->open, it->close)), keyPixel));
27661 // draw open-close box:
27662 double pixelWidth = getPixelWidth(it->key, keyPixel);
27663 painter->drawRect(QRectF(QPointF(closePixel, keyPixel-pixelWidth), QPointF(openPixel, keyPixel+pixelWidth)));
27664 }
27665 }
27666}
27667
27668/*! \internal
27669
27670 This function is used to determine the width of the bar at coordinate \a key, according to the
27671 specified width (\ref setWidth) and width type (\ref setWidthType). Provide the pixel position of
27672 \a key in \a keyPixel (because usually this was already calculated via \ref QCPAxis::coordToPixel
27673 when this function is called).
27674
27675 It returns the number of pixels the bar extends to higher keys, relative to the \a key
27676 coordinate. So with a non-reversed horizontal axis, the return value is positive. With a reversed
27677 horizontal axis, the return value is negative. This is important so the open/close flags on the
27678 \ref csOhlc bar are drawn to the correct side.
27679*/
27680double QCPFinancial::getPixelWidth(double key, double keyPixel) const
27681{
27682 double result = 0;
27683 switch (mWidthType)
27684 {
27685 case wtAbsolute:
27686 {
27687 if (mKeyAxis)
27688 result = mWidth*0.5*mKeyAxis.data()->pixelOrientation();
27689 break;
27690 }
27691 case wtAxisRectRatio:
27692 {
27693 if (mKeyAxis && mKeyAxis.data()->axisRect())
27694 {
27695 if (mKeyAxis.data()->orientation() == Qt::Horizontal)
27696 result = mKeyAxis.data()->axisRect()->width()*mWidth*0.5*mKeyAxis.data()->pixelOrientation();
27697 else
27698 result = mKeyAxis.data()->axisRect()->height()*mWidth*0.5*mKeyAxis.data()->pixelOrientation();
27699 } else
27700 qDebug() << Q_FUNC_INFO << "No key axis or axis rect defined";
27701 break;
27702 }
27703 case wtPlotCoords:
27704 {
27705 if (mKeyAxis)
27706 result = mKeyAxis.data()->coordToPixel(key+mWidth*0.5)-keyPixel;
27707 else
27708 qDebug() << Q_FUNC_INFO << "No key axis defined";
27709 break;
27710 }
27711 }
27712 return result;
27713}
27714
27715/*! \internal
27716
27717 This method is a helper function for \ref selectTest. It is used to test for selection when the
27718 chart style is \ref csOhlc. It only tests against the data points between \a begin and \a end.
27719
27720 Like \ref selectTest, this method returns the shortest distance of \a pos to the graphical
27721 representation of the plottable, and \a closestDataPoint will point to the respective data point.
27722*/
27724{
27725 closestDataPoint = mDataContainer->constEnd();
27726 QCPAxis *keyAxis = mKeyAxis.data();
27727 QCPAxis *valueAxis = mValueAxis.data();
27728 if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return -1; }
27729
27730 double minDistSqr = (std::numeric_limits<double>::max)();
27731 if (keyAxis->orientation() == Qt::Horizontal)
27732 {
27733 for (QCPFinancialDataContainer::const_iterator it=begin; it!=end; ++it)
27734 {
27735 double keyPixel = keyAxis->coordToPixel(it->key);
27736 // calculate distance to backbone:
27737 double currentDistSqr = QCPVector2D(pos).distanceSquaredToLine(QCPVector2D(keyPixel, valueAxis->coordToPixel(it->high)), QCPVector2D(keyPixel, valueAxis->coordToPixel(it->low)));
27738 if (currentDistSqr < minDistSqr)
27739 {
27740 minDistSqr = currentDistSqr;
27741 closestDataPoint = it;
27742 }
27743 }
27744 } else // keyAxis->orientation() == Qt::Vertical
27745 {
27746 for (QCPFinancialDataContainer::const_iterator it=begin; it!=end; ++it)
27747 {
27748 double keyPixel = keyAxis->coordToPixel(it->key);
27749 // calculate distance to backbone:
27750 double currentDistSqr = QCPVector2D(pos).distanceSquaredToLine(QCPVector2D(valueAxis->coordToPixel(it->high), keyPixel), QCPVector2D(valueAxis->coordToPixel(it->low), keyPixel));
27751 if (currentDistSqr < minDistSqr)
27752 {
27753 minDistSqr = currentDistSqr;
27754 closestDataPoint = it;
27755 }
27756 }
27757 }
27758 return qSqrt(minDistSqr);
27759}
27760
27761/*! \internal
27762
27763 This method is a helper function for \ref selectTest. It is used to test for selection when the
27764 chart style is \ref csCandlestick. It only tests against the data points between \a begin and \a
27765 end.
27766
27767 Like \ref selectTest, this method returns the shortest distance of \a pos to the graphical
27768 representation of the plottable, and \a closestDataPoint will point to the respective data point.
27769*/
27771{
27772 closestDataPoint = mDataContainer->constEnd();
27773 QCPAxis *keyAxis = mKeyAxis.data();
27774 QCPAxis *valueAxis = mValueAxis.data();
27775 if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return -1; }
27776
27777 double minDistSqr = (std::numeric_limits<double>::max)();
27778 if (keyAxis->orientation() == Qt::Horizontal)
27779 {
27780 for (QCPFinancialDataContainer::const_iterator it=begin; it!=end; ++it)
27781 {
27782 double currentDistSqr;
27783 // determine whether pos is in open-close-box:
27784 QCPRange boxKeyRange(it->key-mWidth*0.5, it->key+mWidth*0.5);
27785 QCPRange boxValueRange(it->close, it->open);
27786 double posKey, posValue;
27787 pixelsToCoords(pos, posKey, posValue);
27788 if (boxKeyRange.contains(posKey) && boxValueRange.contains(posValue)) // is in open-close-box
27789 {
27790 currentDistSqr = mParentPlot->selectionTolerance()*0.99 * mParentPlot->selectionTolerance()*0.99;
27791 } else
27792 {
27793 // calculate distance to high/low lines:
27794 double keyPixel = keyAxis->coordToPixel(it->key);
27795 double highLineDistSqr = QCPVector2D(pos).distanceSquaredToLine(QCPVector2D(keyPixel, valueAxis->coordToPixel(it->high)), QCPVector2D(keyPixel, valueAxis->coordToPixel(qMax(it->open, it->close))));
27796 double lowLineDistSqr = QCPVector2D(pos).distanceSquaredToLine(QCPVector2D(keyPixel, valueAxis->coordToPixel(it->low)), QCPVector2D(keyPixel, valueAxis->coordToPixel(qMin(it->open, it->close))));
27797 currentDistSqr = qMin(highLineDistSqr, lowLineDistSqr);
27798 }
27799 if (currentDistSqr < minDistSqr)
27800 {
27801 minDistSqr = currentDistSqr;
27802 closestDataPoint = it;
27803 }
27804 }
27805 } else // keyAxis->orientation() == Qt::Vertical
27806 {
27807 for (QCPFinancialDataContainer::const_iterator it=begin; it!=end; ++it)
27808 {
27809 double currentDistSqr;
27810 // determine whether pos is in open-close-box:
27811 QCPRange boxKeyRange(it->key-mWidth*0.5, it->key+mWidth*0.5);
27812 QCPRange boxValueRange(it->close, it->open);
27813 double posKey, posValue;
27814 pixelsToCoords(pos, posKey, posValue);
27815 if (boxKeyRange.contains(posKey) && boxValueRange.contains(posValue)) // is in open-close-box
27816 {
27817 currentDistSqr = mParentPlot->selectionTolerance()*0.99 * mParentPlot->selectionTolerance()*0.99;
27818 } else
27819 {
27820 // calculate distance to high/low lines:
27821 double keyPixel = keyAxis->coordToPixel(it->key);
27822 double highLineDistSqr = QCPVector2D(pos).distanceSquaredToLine(QCPVector2D(valueAxis->coordToPixel(it->high), keyPixel), QCPVector2D(valueAxis->coordToPixel(qMax(it->open, it->close)), keyPixel));
27823 double lowLineDistSqr = QCPVector2D(pos).distanceSquaredToLine(QCPVector2D(valueAxis->coordToPixel(it->low), keyPixel), QCPVector2D(valueAxis->coordToPixel(qMin(it->open, it->close)), keyPixel));
27824 currentDistSqr = qMin(highLineDistSqr, lowLineDistSqr);
27825 }
27826 if (currentDistSqr < minDistSqr)
27827 {
27828 minDistSqr = currentDistSqr;
27829 closestDataPoint = it;
27830 }
27831 }
27832 }
27833 return qSqrt(minDistSqr);
27834}
27835
27836/*! \internal
27837
27838 called by the drawing methods to determine which data (key) range is visible at the current key
27839 axis range setting, so only that needs to be processed.
27840
27841 \a begin returns an iterator to the lowest data point that needs to be taken into account when
27842 plotting. Note that in order to get a clean plot all the way to the edge of the axis rect, \a
27843 begin may still be just outside the visible range.
27844
27845 \a end returns the iterator just above the highest data point that needs to be taken into
27846 account. Same as before, \a end may also lie just outside of the visible range
27847
27848 if the plottable contains no data, both \a begin and \a end point to \c constEnd.
27849*/
27851{
27852 if (!mKeyAxis)
27853 {
27854 qDebug() << Q_FUNC_INFO << "invalid key axis";
27855 begin = mDataContainer->constEnd();
27856 end = mDataContainer->constEnd();
27857 return;
27858 }
27859 begin = mDataContainer->findBegin(mKeyAxis.data()->range().lower-mWidth*0.5); // subtract half width of ohlc/candlestick to include partially visible data points
27860 end = mDataContainer->findEnd(mKeyAxis.data()->range().upper+mWidth*0.5); // add half width of ohlc/candlestick to include partially visible data points
27861}
27862
27863/*! \internal
27864
27865 Returns the hit box in pixel coordinates that will be used for data selection with the selection
27866 rect (\ref selectTestRect), of the data point given by \a it.
27867*/
27869{
27870 QCPAxis *keyAxis = mKeyAxis.data();
27871 QCPAxis *valueAxis = mValueAxis.data();
27872 if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return {}; }
27873
27874 double keyPixel = keyAxis->coordToPixel(it->key);
27875 double highPixel = valueAxis->coordToPixel(it->high);
27876 double lowPixel = valueAxis->coordToPixel(it->low);
27877 double keyWidthPixels = keyPixel-keyAxis->coordToPixel(it->key-mWidth*0.5);
27878 if (keyAxis->orientation() == Qt::Horizontal)
27879 return QRectF(keyPixel-keyWidthPixels, highPixel, keyWidthPixels*2, lowPixel-highPixel).normalized();
27880 else
27881 return QRectF(highPixel, keyPixel-keyWidthPixels, lowPixel-highPixel, keyWidthPixels*2).normalized();
27882}
27883/* end of 'src/plottables/plottable-financial.cpp' */
27884
27885
27886/* including file 'src/plottables/plottable-errorbar.cpp' */
27887/* modified 2022-11-06T12:45:56, size 37679 */
27888
27889////////////////////////////////////////////////////////////////////////////////////////////////////
27890//////////////////// QCPErrorBarsData
27891////////////////////////////////////////////////////////////////////////////////////////////////////
27892
27893/*! \class QCPErrorBarsData
27894 \brief Holds the data of one single error bar for QCPErrorBars.
27895
27896 The stored data is:
27897 \li \a errorMinus: how much the error bar extends towards negative coordinates from the data
27898 point position
27899 \li \a errorPlus: how much the error bar extends towards positive coordinates from the data point
27900 position
27901
27902 The container for storing the error bar information is \ref QCPErrorBarsDataContainer. It is a
27903 typedef for <tt>QVector<\ref QCPErrorBarsData></tt>.
27904
27905 \see QCPErrorBarsDataContainer
27906*/
27907
27908/*!
27909 Constructs an error bar with errors set to zero.
27910*/
27912 errorMinus(0),
27913 errorPlus(0)
27914{
27915}
27916
27917/*!
27918 Constructs an error bar with equal \a error in both negative and positive direction.
27919*/
27921 errorMinus(error),
27922 errorPlus(error)
27923{
27924}
27925
27926/*!
27927 Constructs an error bar with negative and positive errors set to \a errorMinus and \a errorPlus,
27928 respectively.
27929*/
27930QCPErrorBarsData::QCPErrorBarsData(double errorMinus, double errorPlus) :
27931 errorMinus(errorMinus),
27932 errorPlus(errorPlus)
27933{
27934}
27935
27936
27937////////////////////////////////////////////////////////////////////////////////////////////////////
27938//////////////////// QCPErrorBars
27939////////////////////////////////////////////////////////////////////////////////////////////////////
27940
27941/*! \class QCPErrorBars
27942 \brief A plottable that adds a set of error bars to other plottables.
27943
27944 \image html QCPErrorBars.png
27945
27946 The \ref QCPErrorBars plottable can be attached to other one-dimensional plottables (e.g. \ref
27947 QCPGraph, \ref QCPCurve, \ref QCPBars, etc.) and equips them with error bars.
27948
27949 Use \ref setDataPlottable to define for which plottable the \ref QCPErrorBars shall display the
27950 error bars. The orientation of the error bars can be controlled with \ref setErrorType.
27951
27952 By using \ref setData, you can supply the actual error data, either as symmetric error or
27953 plus/minus asymmetric errors. \ref QCPErrorBars only stores the error data. The absolute
27954 key/value position of each error bar will be adopted from the configured data plottable. The
27955 error data of the \ref QCPErrorBars are associated one-to-one via their index to the data points
27956 of the data plottable. You can directly access and manipulate the error bar data via \ref data.
27957
27958 Set either of the plus/minus errors to NaN (<tt>qQNaN()</tt> or
27959 <tt>std::numeric_limits<double>::quiet_NaN()</tt>) to not show the respective error bar on the data point at
27960 that index.
27961
27962 \section qcperrorbars-appearance Changing the appearance
27963
27964 The appearance of the error bars is defined by the pen (\ref setPen), and the width of the
27965 whiskers (\ref setWhiskerWidth). Further, the error bar backbones may leave a gap around the data
27966 point center to prevent that error bars are drawn too close to or even through scatter points.
27967 This gap size can be controlled via \ref setSymbolGap.
27968*/
27969
27970/* start of documentation of inline functions */
27971
27972/*! \fn QSharedPointer<QCPErrorBarsDataContainer> QCPErrorBars::data() const
27973
27974 Returns a shared pointer to the internal data storage of type \ref QCPErrorBarsDataContainer. You
27975 may use it to directly manipulate the error values, which may be more convenient and faster than
27976 using the regular \ref setData methods.
27977*/
27978
27979/* end of documentation of inline functions */
27980
27981/*!
27982 Constructs an error bars plottable which uses \a keyAxis as its key axis ("x") and \a valueAxis as its value
27983 axis ("y"). \a keyAxis and \a valueAxis must reside in the same QCustomPlot instance and not have
27984 the same orientation. If either of these restrictions is violated, a corresponding message is
27985 printed to the debug output (qDebug), the construction is not aborted, though.
27986
27987 It is also important that the \a keyAxis and \a valueAxis are the same for the error bars
27988 plottable and the data plottable that the error bars shall be drawn on (\ref setDataPlottable).
27989
27990 The created \ref QCPErrorBars is automatically registered with the QCustomPlot instance inferred
27991 from \a keyAxis. This QCustomPlot instance takes ownership of the \ref QCPErrorBars, so do not
27992 delete it manually but use \ref QCustomPlot::removePlottable() instead.
27993*/
27995 QCPAbstractPlottable(keyAxis, valueAxis),
27996 mDataContainer(new QVector<QCPErrorBarsData>),
27997 mErrorType(etValueError),
27998 mWhiskerWidth(9),
27999 mSymbolGap(10)
28000{
28001 setPen(QPen(Qt::black, 0));
28003}
28004
28005QCPErrorBars::~QCPErrorBars()
28006{
28007}
28008
28009/*! \overload
28010
28011 Replaces the current data container with the provided \a data container.
28012
28013 Since a QSharedPointer is used, multiple \ref QCPErrorBars instances may share the same data
28014 container safely. Modifying the data in the container will then affect all \ref QCPErrorBars
28015 instances that share the container. Sharing can be achieved by simply exchanging the data
28016 containers wrapped in shared pointers:
28017 \snippet documentation/doc-code-snippets/mainwindow.cpp qcperrorbars-datasharing-1
28018
28019 If you do not wish to share containers, but create a copy from an existing container, assign the
28020 data containers directly:
28021 \snippet documentation/doc-code-snippets/mainwindow.cpp qcperrorbars-datasharing-2
28022 (This uses different notation compared with other plottables, because the \ref QCPErrorBars
28023 uses a \c QVector<QCPErrorBarsData> as its data container, instead of a \ref QCPDataContainer.)
28024
28025 \see addData
28026*/
28028{
28029 mDataContainer = data;
28030}
28031
28032/*! \overload
28033
28034 Sets symmetrical error values as specified in \a error. The errors will be associated one-to-one
28035 by the data point index to the associated data plottable (\ref setDataPlottable).
28036
28037 You can directly access and manipulate the error bar data via \ref data.
28038
28039 \see addData
28040*/
28042{
28043 mDataContainer->clear();
28044 addData(error);
28045}
28046
28047/*! \overload
28048
28049 Sets asymmetrical errors as specified in \a errorMinus and \a errorPlus. The errors will be
28050 associated one-to-one by the data point index to the associated data plottable (\ref
28051 setDataPlottable).
28052
28053 You can directly access and manipulate the error bar data via \ref data.
28054
28055 \see addData
28056*/
28057void QCPErrorBars::setData(const QVector<double> &errorMinus, const QVector<double> &errorPlus)
28058{
28059 mDataContainer->clear();
28060 addData(errorMinus, errorPlus);
28061}
28062
28063/*!
28064 Sets the data plottable to which the error bars will be applied. The error values specified e.g.
28065 via \ref setData will be associated one-to-one by the data point index to the data points of \a
28066 plottable. This means that the error bars will adopt the key/value coordinates of the data point
28067 with the same index.
28068
28069 The passed \a plottable must be a one-dimensional plottable, i.e. it must implement the \ref
28070 QCPPlottableInterface1D. Further, it must not be a \ref QCPErrorBars instance itself. If either
28071 of these restrictions is violated, a corresponding qDebug output is generated, and the data
28072 plottable of this \ref QCPErrorBars instance is set to zero.
28073
28074 For proper display, care must also be taken that the key and value axes of the \a plottable match
28075 those configured for this \ref QCPErrorBars instance.
28076*/
28078{
28079 if (plottable && qobject_cast<QCPErrorBars*>(plottable))
28080 {
28081 mDataPlottable = nullptr;
28082 qDebug() << Q_FUNC_INFO << "can't set another QCPErrorBars instance as data plottable";
28083 return;
28084 }
28085 if (plottable && !plottable->interface1D())
28086 {
28087 mDataPlottable = nullptr;
28088 qDebug() << Q_FUNC_INFO << "passed plottable doesn't implement 1d interface, can't associate with QCPErrorBars";
28089 return;
28090 }
28091
28092 mDataPlottable = plottable;
28093}
28094
28095/*!
28096 Sets in which orientation the error bars shall appear on the data points. If your data needs both
28097 error dimensions, create two \ref QCPErrorBars with different \a type.
28098*/
28100{
28101 mErrorType = type;
28102}
28103
28104/*!
28105 Sets the width of the whiskers (the short bars at the end of the actual error bar backbones) to
28106 \a pixels.
28107*/
28109{
28110 mWhiskerWidth = pixels;
28111}
28112
28113/*!
28114 Sets the gap diameter around the data points that will be left out when drawing the error bar
28115 backbones. This gap prevents that error bars are drawn too close to or even through scatter
28116 points.
28117*/
28119{
28120 mSymbolGap = pixels;
28121}
28122
28123/*! \overload
28124
28125 Adds symmetrical error values as specified in \a error. The errors will be associated one-to-one
28126 by the data point index to the associated data plottable (\ref setDataPlottable).
28127
28128 You can directly access and manipulate the error bar data via \ref data.
28129
28130 \see setData
28131*/
28133{
28134 addData(error, error);
28135}
28136
28137/*! \overload
28138
28139 Adds asymmetrical errors as specified in \a errorMinus and \a errorPlus. The errors will be
28140 associated one-to-one by the data point index to the associated data plottable (\ref
28141 setDataPlottable).
28142
28143 You can directly access and manipulate the error bar data via \ref data.
28144
28145 \see setData
28146*/
28147void QCPErrorBars::addData(const QVector<double> &errorMinus, const QVector<double> &errorPlus)
28148{
28149 if (errorMinus.size() != errorPlus.size())
28150 qDebug() << Q_FUNC_INFO << "minus and plus error vectors have different sizes:" << errorMinus.size() << errorPlus.size();
28151 const int n = qMin(errorMinus.size(), errorPlus.size());
28152 mDataContainer->reserve(n);
28153 for (int i=0; i<n; ++i)
28154 mDataContainer->append(QCPErrorBarsData(errorMinus.at(i), errorPlus.at(i)));
28155}
28156
28157/*! \overload
28158
28159 Adds a single symmetrical error bar as specified in \a error. The errors will be associated
28160 one-to-one by the data point index to the associated data plottable (\ref setDataPlottable).
28161
28162 You can directly access and manipulate the error bar data via \ref data.
28163
28164 \see setData
28165*/
28166void QCPErrorBars::addData(double error)
28167{
28168 mDataContainer->append(QCPErrorBarsData(error));
28169}
28170
28171/*! \overload
28172
28173 Adds a single asymmetrical error bar as specified in \a errorMinus and \a errorPlus. The errors
28174 will be associated one-to-one by the data point index to the associated data plottable (\ref
28175 setDataPlottable).
28176
28177 You can directly access and manipulate the error bar data via \ref data.
28178
28179 \see setData
28180*/
28181void QCPErrorBars::addData(double errorMinus, double errorPlus)
28182{
28183 mDataContainer->append(QCPErrorBarsData(errorMinus, errorPlus));
28184}
28185
28186/* inherits documentation from base class */
28188{
28189 return mDataContainer->size();
28190}
28191
28192/* inherits documentation from base class */
28193double QCPErrorBars::dataMainKey(int index) const
28194{
28195 if (mDataPlottable)
28196 return mDataPlottable->interface1D()->dataMainKey(index);
28197 else
28198 qDebug() << Q_FUNC_INFO << "no data plottable set";
28199 return 0;
28200}
28201
28202/* inherits documentation from base class */
28203double QCPErrorBars::dataSortKey(int index) const
28204{
28205 if (mDataPlottable)
28206 return mDataPlottable->interface1D()->dataSortKey(index);
28207 else
28208 qDebug() << Q_FUNC_INFO << "no data plottable set";
28209 return 0;
28210}
28211
28212/* inherits documentation from base class */
28213double QCPErrorBars::dataMainValue(int index) const
28214{
28215 if (mDataPlottable)
28216 return mDataPlottable->interface1D()->dataMainValue(index);
28217 else
28218 qDebug() << Q_FUNC_INFO << "no data plottable set";
28219 return 0;
28220}
28221
28222/* inherits documentation from base class */
28224{
28225 if (mDataPlottable)
28226 {
28227 const double value = mDataPlottable->interface1D()->dataMainValue(index);
28228 if (index >= 0 && index < mDataContainer->size() && mErrorType == etValueError)
28229 return {value-mDataContainer->at(index).errorMinus, value+mDataContainer->at(index).errorPlus};
28230 else
28231 return {value, value};
28232 } else
28233 {
28234 qDebug() << Q_FUNC_INFO << "no data plottable set";
28235 return {};
28236 }
28237}
28238
28239/* inherits documentation from base class */
28241{
28242 if (mDataPlottable)
28243 return mDataPlottable->interface1D()->dataPixelPosition(index);
28244 else
28245 qDebug() << Q_FUNC_INFO << "no data plottable set";
28246 return {};
28247}
28248
28249/* inherits documentation from base class */
28251{
28252 if (mDataPlottable)
28253 {
28254 return mDataPlottable->interface1D()->sortKeyIsMainKey();
28255 } else
28256 {
28257 qDebug() << Q_FUNC_INFO << "no data plottable set";
28258 return true;
28259 }
28260}
28261
28262/*!
28263 \copydoc QCPPlottableInterface1D::selectTestRect
28264*/
28265QCPDataSelection QCPErrorBars::selectTestRect(const QRectF &rect, bool onlySelectable) const
28266{
28267 QCPDataSelection result;
28268 if (!mDataPlottable)
28269 return result;
28270 if ((onlySelectable && mSelectable == QCP::stNone) || mDataContainer->isEmpty())
28271 return result;
28272 if (!mKeyAxis || !mValueAxis)
28273 return result;
28274
28275 QCPErrorBarsDataContainer::const_iterator visibleBegin, visibleEnd;
28276 getVisibleDataBounds(visibleBegin, visibleEnd, QCPDataRange(0, dataCount()));
28277
28278 QVector<QLineF> backbones, whiskers;
28279 for (QCPErrorBarsDataContainer::const_iterator it=visibleBegin; it!=visibleEnd; ++it)
28280 {
28281 backbones.clear();
28282 whiskers.clear();
28283 getErrorBarLines(it, backbones, whiskers);
28284 foreach (const QLineF &backbone, backbones)
28285 {
28286 if (rectIntersectsLine(rect, backbone))
28287 {
28288 result.addDataRange(QCPDataRange(int(it-mDataContainer->constBegin()), int(it-mDataContainer->constBegin()+1)), false);
28289 break;
28290 }
28291 }
28292 }
28293 result.simplify();
28294 return result;
28295}
28296
28297/* inherits documentation from base class */
28298int QCPErrorBars::findBegin(double sortKey, bool expandedRange) const
28299{
28300 if (mDataPlottable)
28301 {
28302 if (mDataContainer->isEmpty())
28303 return 0;
28304 int beginIndex = mDataPlottable->interface1D()->findBegin(sortKey, expandedRange);
28305 if (beginIndex >= mDataContainer->size())
28306 beginIndex = mDataContainer->size()-1;
28307 return beginIndex;
28308 } else
28309 qDebug() << Q_FUNC_INFO << "no data plottable set";
28310 return 0;
28311}
28312
28313/* inherits documentation from base class */
28314int QCPErrorBars::findEnd(double sortKey, bool expandedRange) const
28315{
28316 if (mDataPlottable)
28317 {
28318 if (mDataContainer->isEmpty())
28319 return 0;
28320 int endIndex = mDataPlottable->interface1D()->findEnd(sortKey, expandedRange);
28321 if (endIndex > mDataContainer->size())
28322 endIndex = mDataContainer->size();
28323 return endIndex;
28324 } else
28325 qDebug() << Q_FUNC_INFO << "no data plottable set";
28326 return 0;
28327}
28328
28329/*!
28330 Implements a selectTest specific to this plottable's point geometry.
28331
28332 If \a details is not 0, it will be set to a \ref QCPDataSelection, describing the closest data
28333 point to \a pos.
28334
28335 \seebaseclassmethod \ref QCPAbstractPlottable::selectTest
28336*/
28337double QCPErrorBars::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
28338{
28339 if (!mDataPlottable) return -1;
28340
28341 if ((onlySelectable && mSelectable == QCP::stNone) || mDataContainer->isEmpty())
28342 return -1;
28343 if (!mKeyAxis || !mValueAxis)
28344 return -1;
28345
28346 if (mKeyAxis.data()->axisRect()->rect().contains(pos.toPoint()) || mParentPlot->interactions().testFlag(QCP::iSelectPlottablesBeyondAxisRect))
28347 {
28348 QCPErrorBarsDataContainer::const_iterator closestDataPoint = mDataContainer->constEnd();
28349 double result = pointDistance(pos, closestDataPoint);
28350 if (details)
28351 {
28352 int pointIndex = int(closestDataPoint-mDataContainer->constBegin());
28353 details->setValue(QCPDataSelection(QCPDataRange(pointIndex, pointIndex+1)));
28354 }
28355 return result;
28356 } else
28357 return -1;
28358}
28359
28360/* inherits documentation from base class */
28362{
28363 if (!mDataPlottable) return;
28364 if (!mKeyAxis || !mValueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
28365 if (mKeyAxis.data()->range().size() <= 0 || mDataContainer->isEmpty()) return;
28366
28367 // if the sort key isn't the main key, we must check the visibility for each data point/error bar individually
28368 // (getVisibleDataBounds applies range restriction, but otherwise can only return full data range):
28369 bool checkPointVisibility = !mDataPlottable->interface1D()->sortKeyIsMainKey();
28370
28371 // check data validity if flag set:
28372#ifdef QCUSTOMPLOT_CHECK_DATA
28374 for (it = mDataContainer->constBegin(); it != mDataContainer->constEnd(); ++it)
28375 {
28376 if (QCP::isInvalidData(it->errorMinus, it->errorPlus))
28377 qDebug() << Q_FUNC_INFO << "Data point at index" << it-mDataContainer->constBegin() << "invalid." << "Plottable name:" << name();
28378 }
28379#endif
28380
28382 painter->setBrush(Qt::NoBrush);
28383 // loop over and draw segments of unselected/selected data:
28384 QList<QCPDataRange> selectedSegments, unselectedSegments, allSegments;
28385 getDataSegments(selectedSegments, unselectedSegments);
28386 allSegments << unselectedSegments << selectedSegments;
28387 QVector<QLineF> backbones, whiskers;
28388 for (int i=0; i<allSegments.size(); ++i)
28389 {
28391 getVisibleDataBounds(begin, end, allSegments.at(i));
28392 if (begin == end)
28393 continue;
28394
28395 bool isSelectedSegment = i >= unselectedSegments.size();
28396 if (isSelectedSegment && mSelectionDecorator)
28397 mSelectionDecorator->applyPen(painter);
28398 else
28399 painter->setPen(mPen);
28400 if (painter->pen().capStyle() == Qt::SquareCap)
28401 {
28402 QPen capFixPen(painter->pen());
28403 capFixPen.setCapStyle(Qt::FlatCap);
28404 painter->setPen(capFixPen);
28405 }
28406 backbones.clear();
28407 whiskers.clear();
28408 for (QCPErrorBarsDataContainer::const_iterator it=begin; it!=end; ++it)
28409 {
28410 if (!checkPointVisibility || errorBarVisible(int(it-mDataContainer->constBegin())))
28411 getErrorBarLines(it, backbones, whiskers);
28412 }
28413 painter->drawLines(backbones);
28414 painter->drawLines(whiskers);
28415 }
28416
28417 // draw other selection decoration that isn't just line/scatter pens and brushes:
28418 if (mSelectionDecorator)
28419 mSelectionDecorator->drawDecoration(painter, selection());
28420}
28421
28422/* inherits documentation from base class */
28423void QCPErrorBars::drawLegendIcon(QCPPainter *painter, const QRectF &rect) const
28424{
28426 painter->setPen(mPen);
28427 if (mErrorType == etValueError && mValueAxis && mValueAxis->orientation() == Qt::Vertical)
28428 {
28429 painter->drawLine(QLineF(rect.center().x(), rect.top()+2, rect.center().x(), rect.bottom()-1));
28430 painter->drawLine(QLineF(rect.center().x()-4, rect.top()+2, rect.center().x()+4, rect.top()+2));
28431 painter->drawLine(QLineF(rect.center().x()-4, rect.bottom()-1, rect.center().x()+4, rect.bottom()-1));
28432 } else
28433 {
28434 painter->drawLine(QLineF(rect.left()+2, rect.center().y(), rect.right()-2, rect.center().y()));
28435 painter->drawLine(QLineF(rect.left()+2, rect.center().y()-4, rect.left()+2, rect.center().y()+4));
28436 painter->drawLine(QLineF(rect.right()-2, rect.center().y()-4, rect.right()-2, rect.center().y()+4));
28437 }
28438}
28439
28440/* inherits documentation from base class */
28441QCPRange QCPErrorBars::getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain) const
28442{
28443 if (!mDataPlottable)
28444 {
28445 foundRange = false;
28446 return {};
28447 }
28448
28449 QCPRange range;
28450 bool haveLower = false;
28451 bool haveUpper = false;
28453 for (it = mDataContainer->constBegin(); it != mDataContainer->constEnd(); ++it)
28454 {
28455 if (mErrorType == etValueError)
28456 {
28457 // error bar doesn't extend in key dimension (except whisker but we ignore that here), so only use data point center
28458 const double current = mDataPlottable->interface1D()->dataMainKey(int(it-mDataContainer->constBegin()));
28459 if (qIsNaN(current)) continue;
28460 if (inSignDomain == QCP::sdBoth || (inSignDomain == QCP::sdNegative && current < 0) || (inSignDomain == QCP::sdPositive && current > 0))
28461 {
28462 if (current < range.lower || !haveLower)
28463 {
28464 range.lower = current;
28465 haveLower = true;
28466 }
28467 if (current > range.upper || !haveUpper)
28468 {
28469 range.upper = current;
28470 haveUpper = true;
28471 }
28472 }
28473 } else // mErrorType == etKeyError
28474 {
28475 const double dataKey = mDataPlottable->interface1D()->dataMainKey(int(it-mDataContainer->constBegin()));
28476 if (qIsNaN(dataKey)) continue;
28477 // plus error:
28478 double current = dataKey + (qIsNaN(it->errorPlus) ? 0 : it->errorPlus);
28479 if (inSignDomain == QCP::sdBoth || (inSignDomain == QCP::sdNegative && current < 0) || (inSignDomain == QCP::sdPositive && current > 0))
28480 {
28481 if (current > range.upper || !haveUpper)
28482 {
28483 range.upper = current;
28484 haveUpper = true;
28485 }
28486 }
28487 // minus error:
28488 current = dataKey - (qIsNaN(it->errorMinus) ? 0 : it->errorMinus);
28489 if (inSignDomain == QCP::sdBoth || (inSignDomain == QCP::sdNegative && current < 0) || (inSignDomain == QCP::sdPositive && current > 0))
28490 {
28491 if (current < range.lower || !haveLower)
28492 {
28493 range.lower = current;
28494 haveLower = true;
28495 }
28496 }
28497 }
28498 }
28499
28500 if (haveUpper && !haveLower)
28501 {
28502 range.lower = range.upper;
28503 haveLower = true;
28504 } else if (haveLower && !haveUpper)
28505 {
28506 range.upper = range.lower;
28507 haveUpper = true;
28508 }
28509
28510 foundRange = haveLower && haveUpper;
28511 return range;
28512}
28513
28514/* inherits documentation from base class */
28515QCPRange QCPErrorBars::getValueRange(bool &foundRange, QCP::SignDomain inSignDomain, const QCPRange &inKeyRange) const
28516{
28517 if (!mDataPlottable)
28518 {
28519 foundRange = false;
28520 return {};
28521 }
28522
28523 QCPRange range;
28524 const bool restrictKeyRange = inKeyRange != QCPRange();
28525 bool haveLower = false;
28526 bool haveUpper = false;
28527 QCPErrorBarsDataContainer::const_iterator itBegin = mDataContainer->constBegin();
28528 QCPErrorBarsDataContainer::const_iterator itEnd = mDataContainer->constEnd();
28529 if (mDataPlottable->interface1D()->sortKeyIsMainKey() && restrictKeyRange)
28530 {
28531 itBegin = mDataContainer->constBegin()+findBegin(inKeyRange.lower, false);
28532 itEnd = mDataContainer->constBegin()+findEnd(inKeyRange.upper, false);
28533 }
28534 for (QCPErrorBarsDataContainer::const_iterator it = itBegin; it != itEnd; ++it)
28535 {
28536 if (restrictKeyRange)
28537 {
28538 const double dataKey = mDataPlottable->interface1D()->dataMainKey(int(it-mDataContainer->constBegin()));
28539 if (dataKey < inKeyRange.lower || dataKey > inKeyRange.upper)
28540 continue;
28541 }
28542 if (mErrorType == etValueError)
28543 {
28544 const double dataValue = mDataPlottable->interface1D()->dataMainValue(int(it-mDataContainer->constBegin()));
28545 if (qIsNaN(dataValue)) continue;
28546 // plus error:
28547 double current = dataValue + (qIsNaN(it->errorPlus) ? 0 : it->errorPlus);
28548 if (inSignDomain == QCP::sdBoth || (inSignDomain == QCP::sdNegative && current < 0) || (inSignDomain == QCP::sdPositive && current > 0))
28549 {
28550 if (current > range.upper || !haveUpper)
28551 {
28552 range.upper = current;
28553 haveUpper = true;
28554 }
28555 }
28556 // minus error:
28557 current = dataValue - (qIsNaN(it->errorMinus) ? 0 : it->errorMinus);
28558 if (inSignDomain == QCP::sdBoth || (inSignDomain == QCP::sdNegative && current < 0) || (inSignDomain == QCP::sdPositive && current > 0))
28559 {
28560 if (current < range.lower || !haveLower)
28561 {
28562 range.lower = current;
28563 haveLower = true;
28564 }
28565 }
28566 } else // mErrorType == etKeyError
28567 {
28568 // error bar doesn't extend in value dimension (except whisker but we ignore that here), so only use data point center
28569 const double current = mDataPlottable->interface1D()->dataMainValue(int(it-mDataContainer->constBegin()));
28570 if (qIsNaN(current)) continue;
28571 if (inSignDomain == QCP::sdBoth || (inSignDomain == QCP::sdNegative && current < 0) || (inSignDomain == QCP::sdPositive && current > 0))
28572 {
28573 if (current < range.lower || !haveLower)
28574 {
28575 range.lower = current;
28576 haveLower = true;
28577 }
28578 if (current > range.upper || !haveUpper)
28579 {
28580 range.upper = current;
28581 haveUpper = true;
28582 }
28583 }
28584 }
28585 }
28586
28587 if (haveUpper && !haveLower)
28588 {
28589 range.lower = range.upper;
28590 haveLower = true;
28591 } else if (haveLower && !haveUpper)
28592 {
28593 range.upper = range.lower;
28594 haveUpper = true;
28595 }
28596
28597 foundRange = haveLower && haveUpper;
28598 return range;
28599}
28600
28601/*! \internal
28602
28603 Calculates the lines that make up the error bar belonging to the data point \a it.
28604
28605 The resulting lines are added to \a backbones and \a whiskers. The vectors are not cleared, so
28606 calling this method with different \a it but the same \a backbones and \a whiskers allows to
28607 accumulate lines for multiple data points.
28608
28609 This method assumes that \a it is a valid iterator within the bounds of this \ref QCPErrorBars
28610 instance and within the bounds of the associated data plottable.
28611*/
28613{
28614 if (!mDataPlottable) return;
28615
28616 int index = int(it-mDataContainer->constBegin());
28617 QPointF centerPixel = mDataPlottable->interface1D()->dataPixelPosition(index);
28618 if (qIsNaN(centerPixel.x()) || qIsNaN(centerPixel.y()))
28619 return;
28620 QCPAxis *errorAxis = mErrorType == etValueError ? mValueAxis.data() : mKeyAxis.data();
28621 QCPAxis *orthoAxis = mErrorType == etValueError ? mKeyAxis.data() : mValueAxis.data();
28622 const double centerErrorAxisPixel = errorAxis->orientation() == Qt::Horizontal ? centerPixel.x() : centerPixel.y();
28623 const double centerOrthoAxisPixel = orthoAxis->orientation() == Qt::Horizontal ? centerPixel.x() : centerPixel.y();
28624 const double centerErrorAxisCoord = errorAxis->pixelToCoord(centerErrorAxisPixel); // depending on plottable, this might be different from just mDataPlottable->interface1D()->dataMainKey/Value
28625 const double symbolGap = mSymbolGap*0.5*errorAxis->pixelOrientation();
28626 // plus error:
28627 double errorStart, errorEnd;
28628 if (!qIsNaN(it->errorPlus))
28629 {
28630 errorStart = centerErrorAxisPixel+symbolGap;
28631 errorEnd = errorAxis->coordToPixel(centerErrorAxisCoord+it->errorPlus);
28632 if (errorAxis->orientation() == Qt::Vertical)
28633 {
28634 if ((errorStart > errorEnd) != errorAxis->rangeReversed())
28635 backbones.append(QLineF(centerOrthoAxisPixel, errorStart, centerOrthoAxisPixel, errorEnd));
28636 whiskers.append(QLineF(centerOrthoAxisPixel-mWhiskerWidth*0.5, errorEnd, centerOrthoAxisPixel+mWhiskerWidth*0.5, errorEnd));
28637 } else
28638 {
28639 if ((errorStart < errorEnd) != errorAxis->rangeReversed())
28640 backbones.append(QLineF(errorStart, centerOrthoAxisPixel, errorEnd, centerOrthoAxisPixel));
28641 whiskers.append(QLineF(errorEnd, centerOrthoAxisPixel-mWhiskerWidth*0.5, errorEnd, centerOrthoAxisPixel+mWhiskerWidth*0.5));
28642 }
28643 }
28644 // minus error:
28645 if (!qIsNaN(it->errorMinus))
28646 {
28647 errorStart = centerErrorAxisPixel-symbolGap;
28648 errorEnd = errorAxis->coordToPixel(centerErrorAxisCoord-it->errorMinus);
28649 if (errorAxis->orientation() == Qt::Vertical)
28650 {
28651 if ((errorStart < errorEnd) != errorAxis->rangeReversed())
28652 backbones.append(QLineF(centerOrthoAxisPixel, errorStart, centerOrthoAxisPixel, errorEnd));
28653 whiskers.append(QLineF(centerOrthoAxisPixel-mWhiskerWidth*0.5, errorEnd, centerOrthoAxisPixel+mWhiskerWidth*0.5, errorEnd));
28654 } else
28655 {
28656 if ((errorStart > errorEnd) != errorAxis->rangeReversed())
28657 backbones.append(QLineF(errorStart, centerOrthoAxisPixel, errorEnd, centerOrthoAxisPixel));
28658 whiskers.append(QLineF(errorEnd, centerOrthoAxisPixel-mWhiskerWidth*0.5, errorEnd, centerOrthoAxisPixel+mWhiskerWidth*0.5));
28659 }
28660 }
28661}
28662
28663/*! \internal
28664
28665 This method outputs the currently visible data range via \a begin and \a end. The returned range
28666 will also never exceed \a rangeRestriction.
28667
28668 Since error bars with type \ref etKeyError may extend to arbitrarily positive and negative key
28669 coordinates relative to their data point key, this method checks all outer error bars whether
28670 they truly don't reach into the visible portion of the axis rect, by calling \ref
28671 errorBarVisible. On the other hand error bars with type \ref etValueError that are associated
28672 with data plottables whose sort key is equal to the main key (see \ref qcpdatacontainer-datatype
28673 "QCPDataContainer DataType") can be handled very efficiently by finding the visible range of
28674 error bars through binary search (\ref QCPPlottableInterface1D::findBegin and \ref
28675 QCPPlottableInterface1D::findEnd).
28676
28677 If the plottable's sort key is not equal to the main key, this method returns the full data
28678 range, only restricted by \a rangeRestriction. Drawing optimization then has to be done on a
28679 point-by-point basis in the \ref draw method.
28680*/
28682{
28683 QCPAxis *keyAxis = mKeyAxis.data();
28684 QCPAxis *valueAxis = mValueAxis.data();
28685 if (!keyAxis || !valueAxis)
28686 {
28687 qDebug() << Q_FUNC_INFO << "invalid key or value axis";
28688 end = mDataContainer->constEnd();
28689 begin = end;
28690 return;
28691 }
28692 if (!mDataPlottable || rangeRestriction.isEmpty())
28693 {
28694 end = mDataContainer->constEnd();
28695 begin = end;
28696 return;
28697 }
28698 if (!mDataPlottable->interface1D()->sortKeyIsMainKey())
28699 {
28700 // if the sort key isn't the main key, it's not possible to find a contiguous range of visible
28701 // data points, so this method then only applies the range restriction and otherwise returns
28702 // the full data range. Visibility checks must be done on a per-datapoin-basis during drawing
28703 QCPDataRange dataRange(0, mDataContainer->size());
28704 dataRange = dataRange.bounded(rangeRestriction);
28705 begin = mDataContainer->constBegin()+dataRange.begin();
28706 end = mDataContainer->constBegin()+dataRange.end();
28707 return;
28708 }
28709
28710 // get visible data range via interface from data plottable, and then restrict to available error data points:
28711 const int n = qMin(mDataContainer->size(), mDataPlottable->interface1D()->dataCount());
28712 int beginIndex = mDataPlottable->interface1D()->findBegin(keyAxis->range().lower);
28713 int endIndex = mDataPlottable->interface1D()->findEnd(keyAxis->range().upper);
28714 int i = beginIndex;
28715 while (i > 0 && i < n && i > rangeRestriction.begin())
28716 {
28717 if (errorBarVisible(i))
28718 beginIndex = i;
28719 --i;
28720 }
28721 i = endIndex;
28722 while (i >= 0 && i < n && i < rangeRestriction.end())
28723 {
28724 if (errorBarVisible(i))
28725 endIndex = i+1;
28726 ++i;
28727 }
28728 QCPDataRange dataRange(beginIndex, endIndex);
28729 dataRange = dataRange.bounded(rangeRestriction.bounded(QCPDataRange(0, mDataContainer->size())));
28730 begin = mDataContainer->constBegin()+dataRange.begin();
28731 end = mDataContainer->constBegin()+dataRange.end();
28732}
28733
28734/*! \internal
28735
28736 Calculates the minimum distance in pixels the error bars' representation has from the given \a
28737 pixelPoint. This is used to determine whether the error bar was clicked or not, e.g. in \ref
28738 selectTest. The closest data point to \a pixelPoint is returned in \a closestData.
28739*/
28741{
28742 closestData = mDataContainer->constEnd();
28743 if (!mDataPlottable || mDataContainer->isEmpty())
28744 return -1.0;
28745 if (!mKeyAxis || !mValueAxis)
28746 {
28747 qDebug() << Q_FUNC_INFO << "invalid key or value axis";
28748 return -1.0;
28749 }
28750
28752 getVisibleDataBounds(begin, end, QCPDataRange(0, dataCount()));
28753
28754 // calculate minimum distances to error backbones (whiskers are ignored for speed) and find closestData iterator:
28755 double minDistSqr = (std::numeric_limits<double>::max)();
28756 QVector<QLineF> backbones, whiskers;
28757 for (QCPErrorBarsDataContainer::const_iterator it=begin; it!=end; ++it)
28758 {
28759 getErrorBarLines(it, backbones, whiskers);
28760 foreach (const QLineF &backbone, backbones)
28761 {
28762 const double currentDistSqr = QCPVector2D(pixelPoint).distanceSquaredToLine(backbone);
28763 if (currentDistSqr < minDistSqr)
28764 {
28765 minDistSqr = currentDistSqr;
28766 closestData = it;
28767 }
28768 }
28769 }
28770 return qSqrt(minDistSqr);
28771}
28772
28773/*! \internal
28774
28775 \note This method is identical to \ref QCPAbstractPlottable1D::getDataSegments but needs to be
28776 reproduced here since the \ref QCPErrorBars plottable, as a special case that doesn't have its
28777 own key/value data coordinates, doesn't derive from \ref QCPAbstractPlottable1D. See the
28778 documentation there for details.
28779*/
28780void QCPErrorBars::getDataSegments(QList<QCPDataRange> &selectedSegments, QList<QCPDataRange> &unselectedSegments) const
28781{
28782 selectedSegments.clear();
28783 unselectedSegments.clear();
28784 if (mSelectable == QCP::stWhole) // stWhole selection type draws the entire plottable with selected style if mSelection isn't empty
28785 {
28786 if (selected())
28787 selectedSegments << QCPDataRange(0, dataCount());
28788 else
28789 unselectedSegments << QCPDataRange(0, dataCount());
28790 } else
28791 {
28793 sel.simplify();
28794 selectedSegments = sel.dataRanges();
28795 unselectedSegments = sel.inverse(QCPDataRange(0, dataCount())).dataRanges();
28796 }
28797}
28798
28799/*! \internal
28800
28801 Returns whether the error bar at the specified \a index is visible within the current key axis
28802 range.
28803
28804 This method assumes for performance reasons without checking that the key axis, the value axis,
28805 and the data plottable (\ref setDataPlottable) are not \c nullptr and that \a index is within
28806 valid bounds of this \ref QCPErrorBars instance and the bounds of the data plottable.
28807*/
28809{
28810 QPointF centerPixel = mDataPlottable->interface1D()->dataPixelPosition(index);
28811 const double centerKeyPixel = mKeyAxis->orientation() == Qt::Horizontal ? centerPixel.x() : centerPixel.y();
28812 if (qIsNaN(centerKeyPixel))
28813 return false;
28814
28815 double keyMin, keyMax;
28816 if (mErrorType == etKeyError)
28817 {
28818 const double centerKey = mKeyAxis->pixelToCoord(centerKeyPixel);
28819 const double errorPlus = mDataContainer->at(index).errorPlus;
28820 const double errorMinus = mDataContainer->at(index).errorMinus;
28821 keyMax = centerKey+(qIsNaN(errorPlus) ? 0 : errorPlus);
28822 keyMin = centerKey-(qIsNaN(errorMinus) ? 0 : errorMinus);
28823 } else // mErrorType == etValueError
28824 {
28825 keyMax = mKeyAxis->pixelToCoord(centerKeyPixel+mWhiskerWidth*0.5*mKeyAxis->pixelOrientation());
28826 keyMin = mKeyAxis->pixelToCoord(centerKeyPixel-mWhiskerWidth*0.5*mKeyAxis->pixelOrientation());
28827 }
28828 return ((keyMax > mKeyAxis->range().lower) && (keyMin < mKeyAxis->range().upper));
28829}
28830
28831/*! \internal
28832
28833 Returns whether \a line intersects (or is contained in) \a pixelRect.
28834
28835 \a line is assumed to be either perfectly horizontal or perfectly vertical, as is the case for
28836 error bar lines.
28837*/
28838bool QCPErrorBars::rectIntersectsLine(const QRectF &pixelRect, const QLineF &line) const
28839{
28840 if (pixelRect.left() > line.x1() && pixelRect.left() > line.x2())
28841 return false;
28842 else if (pixelRect.right() < line.x1() && pixelRect.right() < line.x2())
28843 return false;
28844 else if (pixelRect.top() > line.y1() && pixelRect.top() > line.y2())
28845 return false;
28846 else if (pixelRect.bottom() < line.y1() && pixelRect.bottom() < line.y2())
28847 return false;
28848 else
28849 return true;
28850}
28851/* end of 'src/plottables/plottable-errorbar.cpp' */
28852
28853
28854/* including file 'src/items/item-straightline.cpp' */
28855/* modified 2022-11-06T12:45:56, size 7596 */
28856
28857////////////////////////////////////////////////////////////////////////////////////////////////////
28858//////////////////// QCPItemStraightLine
28859////////////////////////////////////////////////////////////////////////////////////////////////////
28860
28861/*! \class QCPItemStraightLine
28862 \brief A straight line that spans infinitely in both directions
28863
28864 \image html QCPItemStraightLine.png "Straight line example. Blue dotted circles are anchors, solid blue discs are positions."
28865
28866 It has two positions, \a point1 and \a point2, which define the straight line.
28867*/
28868
28869/*!
28870 Creates a straight line item and sets default values.
28871
28872 The created item is automatically registered with \a parentPlot. This QCustomPlot instance takes
28873 ownership of the item, so do not delete it manually but use QCustomPlot::removeItem() instead.
28874*/
28876 QCPAbstractItem(parentPlot),
28877 point1(createPosition(QLatin1String("point1"))),
28878 point2(createPosition(QLatin1String("point2")))
28879{
28880 point1->setCoords(0, 0);
28881 point2->setCoords(1, 1);
28882
28885}
28886
28887QCPItemStraightLine::~QCPItemStraightLine()
28888{
28889}
28890
28891/*!
28892 Sets the pen that will be used to draw the line
28893
28894 \see setSelectedPen
28895*/
28897{
28898 mPen = pen;
28899}
28900
28901/*!
28902 Sets the pen that will be used to draw the line when selected
28903
28904 \see setPen, setSelected
28905*/
28907{
28908 mSelectedPen = pen;
28909}
28910
28911/* inherits documentation from base class */
28912double QCPItemStraightLine::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
28913{
28914 Q_UNUSED(details)
28915 if (onlySelectable && !mSelectable)
28916 return -1;
28917
28918 return QCPVector2D(pos).distanceToStraightLine(point1->pixelPosition(), point2->pixelPosition()-point1->pixelPosition());
28919}
28920
28921/* inherits documentation from base class */
28923{
28924 QCPVector2D start(point1->pixelPosition());
28925 QCPVector2D end(point2->pixelPosition());
28926 // get visible segment of straight line inside clipRect:
28927 int clipPad = qCeil(mainPen().widthF());
28928 QLineF line = getRectClippedStraightLine(start, end-start, clipRect().adjusted(-clipPad, -clipPad, clipPad, clipPad));
28929 // paint visible segment, if existent:
28930 if (!line.isNull())
28931 {
28932 painter->setPen(mainPen());
28933 painter->drawLine(line);
28934 }
28935}
28936
28937/*! \internal
28938
28939 Returns the section of the straight line defined by \a base and direction vector \a
28940 vec, that is visible in the specified \a rect.
28941
28942 This is a helper function for \ref draw.
28943*/
28945{
28946 double bx, by;
28947 double gamma;
28948 QLineF result;
28949 if (vec.x() == 0 && vec.y() == 0)
28950 return result;
28951 if (qFuzzyIsNull(vec.x())) // line is vertical
28952 {
28953 // check top of rect:
28954 bx = rect.left();
28955 by = rect.top();
28956 gamma = base.x()-bx + (by-base.y())*vec.x()/vec.y();
28957 if (gamma >= 0 && gamma <= rect.width())
28958 result.setLine(bx+gamma, rect.top(), bx+gamma, rect.bottom()); // no need to check bottom because we know line is vertical
28959 } else if (qFuzzyIsNull(vec.y())) // line is horizontal
28960 {
28961 // check left of rect:
28962 bx = rect.left();
28963 by = rect.top();
28964 gamma = base.y()-by + (bx-base.x())*vec.y()/vec.x();
28965 if (gamma >= 0 && gamma <= rect.height())
28966 result.setLine(rect.left(), by+gamma, rect.right(), by+gamma); // no need to check right because we know line is horizontal
28967 } else // line is skewed
28968 {
28969 QList<QCPVector2D> pointVectors;
28970 // check top of rect:
28971 bx = rect.left();
28972 by = rect.top();
28973 gamma = base.x()-bx + (by-base.y())*vec.x()/vec.y();
28974 if (gamma >= 0 && gamma <= rect.width())
28975 pointVectors.append(QCPVector2D(bx+gamma, by));
28976 // check bottom of rect:
28977 bx = rect.left();
28978 by = rect.bottom();
28979 gamma = base.x()-bx + (by-base.y())*vec.x()/vec.y();
28980 if (gamma >= 0 && gamma <= rect.width())
28981 pointVectors.append(QCPVector2D(bx+gamma, by));
28982 // check left of rect:
28983 bx = rect.left();
28984 by = rect.top();
28985 gamma = base.y()-by + (bx-base.x())*vec.y()/vec.x();
28986 if (gamma >= 0 && gamma <= rect.height())
28987 pointVectors.append(QCPVector2D(bx, by+gamma));
28988 // check right of rect:
28989 bx = rect.right();
28990 by = rect.top();
28991 gamma = base.y()-by + (bx-base.x())*vec.y()/vec.x();
28992 if (gamma >= 0 && gamma <= rect.height())
28993 pointVectors.append(QCPVector2D(bx, by+gamma));
28994
28995 // evaluate points:
28996 if (pointVectors.size() == 2)
28997 {
28998 result.setPoints(pointVectors.at(0).toPointF(), pointVectors.at(1).toPointF());
28999 } else if (pointVectors.size() > 2)
29000 {
29001 // line probably goes through corner of rect, and we got two points there. single out the point pair with greatest distance:
29002 double distSqrMax = 0;
29003 QCPVector2D pv1, pv2;
29004 for (int i=0; i<pointVectors.size()-1; ++i)
29005 {
29006 for (int k=i+1; k<pointVectors.size(); ++k)
29007 {
29008 double distSqr = (pointVectors.at(i)-pointVectors.at(k)).lengthSquared();
29009 if (distSqr > distSqrMax)
29010 {
29011 pv1 = pointVectors.at(i);
29012 pv2 = pointVectors.at(k);
29013 distSqrMax = distSqr;
29014 }
29015 }
29016 }
29017 result.setPoints(pv1.toPointF(), pv2.toPointF());
29018 }
29019 }
29020 return result;
29021}
29022
29023/*! \internal
29024
29025 Returns the pen that should be used for drawing lines. Returns mPen when the
29026 item is not selected and mSelectedPen when it is.
29027*/
29029{
29030 return mSelected ? mSelectedPen : mPen;
29031}
29032/* end of 'src/items/item-straightline.cpp' */
29033
29034
29035/* including file 'src/items/item-line.cpp' */
29036/* modified 2022-11-06T12:45:56, size 8525 */
29037
29038////////////////////////////////////////////////////////////////////////////////////////////////////
29039//////////////////// QCPItemLine
29040////////////////////////////////////////////////////////////////////////////////////////////////////
29041
29042/*! \class QCPItemLine
29043 \brief A line from one point to another
29044
29045 \image html QCPItemLine.png "Line example. Blue dotted circles are anchors, solid blue discs are positions."
29046
29047 It has two positions, \a start and \a end, which define the end points of the line.
29048
29049 With \ref setHead and \ref setTail you may set different line ending styles, e.g. to create an arrow.
29050*/
29051
29052/*!
29053 Creates a line item and sets default values.
29054
29055 The created item is automatically registered with \a parentPlot. This QCustomPlot instance takes
29056 ownership of the item, so do not delete it manually but use QCustomPlot::removeItem() instead.
29057*/
29059 QCPAbstractItem(parentPlot),
29060 start(createPosition(QLatin1String("start"))),
29061 end(createPosition(QLatin1String("end")))
29062{
29063 start->setCoords(0, 0);
29064 end->setCoords(1, 1);
29065
29068}
29069
29070QCPItemLine::~QCPItemLine()
29071{
29072}
29073
29074/*!
29075 Sets the pen that will be used to draw the line
29076
29077 \see setSelectedPen
29078*/
29080{
29081 mPen = pen;
29082}
29083
29084/*!
29085 Sets the pen that will be used to draw the line when selected
29086
29087 \see setPen, setSelected
29088*/
29090{
29091 mSelectedPen = pen;
29092}
29093
29094/*!
29095 Sets the line ending style of the head. The head corresponds to the \a end position.
29096
29097 Note that due to the overloaded QCPLineEnding constructor, you may directly specify
29098 a QCPLineEnding::EndingStyle here, e.g. \code setHead(QCPLineEnding::esSpikeArrow) \endcode
29099
29100 \see setTail
29101*/
29103{
29104 mHead = head;
29105}
29106
29107/*!
29108 Sets the line ending style of the tail. The tail corresponds to the \a start position.
29109
29110 Note that due to the overloaded QCPLineEnding constructor, you may directly specify
29111 a QCPLineEnding::EndingStyle here, e.g. \code setTail(QCPLineEnding::esSpikeArrow) \endcode
29112
29113 \see setHead
29114*/
29116{
29117 mTail = tail;
29118}
29119
29120/* inherits documentation from base class */
29121double QCPItemLine::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
29122{
29123 Q_UNUSED(details)
29124 if (onlySelectable && !mSelectable)
29125 return -1;
29126
29127 return qSqrt(QCPVector2D(pos).distanceSquaredToLine(start->pixelPosition(), end->pixelPosition()));
29128}
29129
29130/* inherits documentation from base class */
29132{
29133 QCPVector2D startVec(start->pixelPosition());
29134 QCPVector2D endVec(end->pixelPosition());
29135 if (qFuzzyIsNull((startVec-endVec).lengthSquared()))
29136 return;
29137 // get visible segment of straight line inside clipRect:
29138 int clipPad = int(qMax(mHead.boundingDistance(), mTail.boundingDistance()));
29139 clipPad = qMax(clipPad, qCeil(mainPen().widthF()));
29140 QLineF line = getRectClippedLine(startVec, endVec, clipRect().adjusted(-clipPad, -clipPad, clipPad, clipPad));
29141 // paint visible segment, if existent:
29142 if (!line.isNull())
29143 {
29144 painter->setPen(mainPen());
29145 painter->drawLine(line);
29146 painter->setBrush(Qt::SolidPattern);
29147 if (mTail.style() != QCPLineEnding::esNone)
29148 mTail.draw(painter, startVec, startVec-endVec);
29149 if (mHead.style() != QCPLineEnding::esNone)
29150 mHead.draw(painter, endVec, endVec-startVec);
29151 }
29152}
29153
29154/*! \internal
29155
29156 Returns the section of the line defined by \a start and \a end, that is visible in the specified
29157 \a rect.
29158
29159 This is a helper function for \ref draw.
29160*/
29161QLineF QCPItemLine::getRectClippedLine(const QCPVector2D &start, const QCPVector2D &end, const QRect &rect) const
29162{
29163 bool containsStart = rect.contains(qRound(start.x()), qRound(start.y()));
29164 bool containsEnd = rect.contains(qRound(end.x()), qRound(end.y()));
29165 if (containsStart && containsEnd)
29166 return {start.toPointF(), end.toPointF()};
29167
29168 QCPVector2D base = start;
29169 QCPVector2D vec = end-start;
29170 double bx, by;
29171 double gamma, mu;
29172 QLineF result;
29173 QList<QCPVector2D> pointVectors;
29174
29175 if (!qFuzzyIsNull(vec.y())) // line is not horizontal
29176 {
29177 // check top of rect:
29178 bx = rect.left();
29179 by = rect.top();
29180 mu = (by-base.y())/vec.y();
29181 if (mu >= 0 && mu <= 1)
29182 {
29183 gamma = base.x()-bx + mu*vec.x();
29184 if (gamma >= 0 && gamma <= rect.width())
29185 pointVectors.append(QCPVector2D(bx+gamma, by));
29186 }
29187 // check bottom of rect:
29188 bx = rect.left();
29189 by = rect.bottom();
29190 mu = (by-base.y())/vec.y();
29191 if (mu >= 0 && mu <= 1)
29192 {
29193 gamma = base.x()-bx + mu*vec.x();
29194 if (gamma >= 0 && gamma <= rect.width())
29195 pointVectors.append(QCPVector2D(bx+gamma, by));
29196 }
29197 }
29198 if (!qFuzzyIsNull(vec.x())) // line is not vertical
29199 {
29200 // check left of rect:
29201 bx = rect.left();
29202 by = rect.top();
29203 mu = (bx-base.x())/vec.x();
29204 if (mu >= 0 && mu <= 1)
29205 {
29206 gamma = base.y()-by + mu*vec.y();
29207 if (gamma >= 0 && gamma <= rect.height())
29208 pointVectors.append(QCPVector2D(bx, by+gamma));
29209 }
29210 // check right of rect:
29211 bx = rect.right();
29212 by = rect.top();
29213 mu = (bx-base.x())/vec.x();
29214 if (mu >= 0 && mu <= 1)
29215 {
29216 gamma = base.y()-by + mu*vec.y();
29217 if (gamma >= 0 && gamma <= rect.height())
29218 pointVectors.append(QCPVector2D(bx, by+gamma));
29219 }
29220 }
29221
29222 if (containsStart)
29223 pointVectors.append(start);
29224 if (containsEnd)
29225 pointVectors.append(end);
29226
29227 // evaluate points:
29228 if (pointVectors.size() == 2)
29229 {
29230 result.setPoints(pointVectors.at(0).toPointF(), pointVectors.at(1).toPointF());
29231 } else if (pointVectors.size() > 2)
29232 {
29233 // line probably goes through corner of rect, and we got two points there. single out the point pair with greatest distance:
29234 double distSqrMax = 0;
29235 QCPVector2D pv1, pv2;
29236 for (int i=0; i<pointVectors.size()-1; ++i)
29237 {
29238 for (int k=i+1; k<pointVectors.size(); ++k)
29239 {
29240 double distSqr = (pointVectors.at(i)-pointVectors.at(k)).lengthSquared();
29241 if (distSqr > distSqrMax)
29242 {
29243 pv1 = pointVectors.at(i);
29244 pv2 = pointVectors.at(k);
29245 distSqrMax = distSqr;
29246 }
29247 }
29248 }
29249 result.setPoints(pv1.toPointF(), pv2.toPointF());
29250 }
29251 return result;
29252}
29253
29254/*! \internal
29255
29256 Returns the pen that should be used for drawing lines. Returns mPen when the
29257 item is not selected and mSelectedPen when it is.
29258*/
29260{
29261 return mSelected ? mSelectedPen : mPen;
29262}
29263/* end of 'src/items/item-line.cpp' */
29264
29265
29266/* including file 'src/items/item-curve.cpp' */
29267/* modified 2022-11-06T12:45:56, size 7273 */
29268
29269////////////////////////////////////////////////////////////////////////////////////////////////////
29270//////////////////// QCPItemCurve
29271////////////////////////////////////////////////////////////////////////////////////////////////////
29272
29273/*! \class QCPItemCurve
29274 \brief A curved line from one point to another
29275
29276 \image html QCPItemCurve.png "Curve example. Blue dotted circles are anchors, solid blue discs are positions."
29277
29278 It has four positions, \a start and \a end, which define the end points of the line, and two
29279 control points which define the direction the line exits from the start and the direction from
29280 which it approaches the end: \a startDir and \a endDir.
29281
29282 With \ref setHead and \ref setTail you may set different line ending styles, e.g. to create an
29283 arrow.
29284
29285 Often it is desirable for the control points to stay at fixed relative positions to the start/end
29286 point. This can be achieved by setting the parent anchor e.g. of \a startDir simply to \a start,
29287 and then specify the desired pixel offset with QCPItemPosition::setCoords on \a startDir.
29288*/
29289
29290/*!
29291 Creates a curve item and sets default values.
29292
29293 The created item is automatically registered with \a parentPlot. This QCustomPlot instance takes
29294 ownership of the item, so do not delete it manually but use QCustomPlot::removeItem() instead.
29295*/
29297 QCPAbstractItem(parentPlot),
29298 start(createPosition(QLatin1String("start"))),
29299 startDir(createPosition(QLatin1String("startDir"))),
29300 endDir(createPosition(QLatin1String("endDir"))),
29301 end(createPosition(QLatin1String("end")))
29302{
29303 start->setCoords(0, 0);
29304 startDir->setCoords(0.5, 0);
29305 endDir->setCoords(0, 0.5);
29306 end->setCoords(1, 1);
29307
29310}
29311
29312QCPItemCurve::~QCPItemCurve()
29313{
29314}
29315
29316/*!
29317 Sets the pen that will be used to draw the line
29318
29319 \see setSelectedPen
29320*/
29322{
29323 mPen = pen;
29324}
29325
29326/*!
29327 Sets the pen that will be used to draw the line when selected
29328
29329 \see setPen, setSelected
29330*/
29332{
29333 mSelectedPen = pen;
29334}
29335
29336/*!
29337 Sets the line ending style of the head. The head corresponds to the \a end position.
29338
29339 Note that due to the overloaded QCPLineEnding constructor, you may directly specify
29340 a QCPLineEnding::EndingStyle here, e.g. \code setHead(QCPLineEnding::esSpikeArrow) \endcode
29341
29342 \see setTail
29343*/
29345{
29346 mHead = head;
29347}
29348
29349/*!
29350 Sets the line ending style of the tail. The tail corresponds to the \a start position.
29351
29352 Note that due to the overloaded QCPLineEnding constructor, you may directly specify
29353 a QCPLineEnding::EndingStyle here, e.g. \code setTail(QCPLineEnding::esSpikeArrow) \endcode
29354
29355 \see setHead
29356*/
29358{
29359 mTail = tail;
29360}
29361
29362/* inherits documentation from base class */
29363double QCPItemCurve::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
29364{
29365 Q_UNUSED(details)
29366 if (onlySelectable && !mSelectable)
29367 return -1;
29368
29369 QPointF startVec(start->pixelPosition());
29370 QPointF startDirVec(startDir->pixelPosition());
29371 QPointF endDirVec(endDir->pixelPosition());
29372 QPointF endVec(end->pixelPosition());
29373
29374 QPainterPath cubicPath(startVec);
29375 cubicPath.cubicTo(startDirVec, endDirVec, endVec);
29376
29377 QList<QPolygonF> polygons = cubicPath.toSubpathPolygons();
29378 if (polygons.isEmpty())
29379 return -1;
29380 const QPolygonF polygon = polygons.first();
29381 QCPVector2D p(pos);
29382 double minDistSqr = (std::numeric_limits<double>::max)();
29383 for (int i=1; i<polygon.size(); ++i)
29384 {
29385 double distSqr = p.distanceSquaredToLine(polygon.at(i-1), polygon.at(i));
29386 if (distSqr < minDistSqr)
29387 minDistSqr = distSqr;
29388 }
29389 return qSqrt(minDistSqr);
29390}
29391
29392/* inherits documentation from base class */
29394{
29395 QCPVector2D startVec(start->pixelPosition());
29396 QCPVector2D startDirVec(startDir->pixelPosition());
29397 QCPVector2D endDirVec(endDir->pixelPosition());
29398 QCPVector2D endVec(end->pixelPosition());
29399 if ((endVec-startVec).length() > 1e10) // too large curves cause crash
29400 return;
29401
29402 QPainterPath cubicPath(startVec.toPointF());
29403 cubicPath.cubicTo(startDirVec.toPointF(), endDirVec.toPointF(), endVec.toPointF());
29404
29405 // paint visible segment, if existent:
29406 const int clipEnlarge = qCeil(mainPen().widthF());
29407 QRect clip = clipRect().adjusted(-clipEnlarge, -clipEnlarge, clipEnlarge, clipEnlarge);
29408 QRect cubicRect = cubicPath.controlPointRect().toRect();
29409 if (cubicRect.isEmpty()) // may happen when start and end exactly on same x or y position
29410 cubicRect.adjust(0, 0, 1, 1);
29411 if (clip.intersects(cubicRect))
29412 {
29413 painter->setPen(mainPen());
29414 painter->drawPath(cubicPath);
29415 painter->setBrush(Qt::SolidPattern);
29416 if (mTail.style() != QCPLineEnding::esNone)
29417 mTail.draw(painter, startVec, M_PI-cubicPath.angleAtPercent(0)/180.0*M_PI);
29418 if (mHead.style() != QCPLineEnding::esNone)
29419 mHead.draw(painter, endVec, -cubicPath.angleAtPercent(1)/180.0*M_PI);
29420 }
29421}
29422
29423/*! \internal
29424
29425 Returns the pen that should be used for drawing lines. Returns mPen when the
29426 item is not selected and mSelectedPen when it is.
29427*/
29429{
29430 return mSelected ? mSelectedPen : mPen;
29431}
29432/* end of 'src/items/item-curve.cpp' */
29433
29434
29435/* including file 'src/items/item-rect.cpp' */
29436/* modified 2022-11-06T12:45:56, size 6472 */
29437
29438////////////////////////////////////////////////////////////////////////////////////////////////////
29439//////////////////// QCPItemRect
29440////////////////////////////////////////////////////////////////////////////////////////////////////
29441
29442/*! \class QCPItemRect
29443 \brief A rectangle
29444
29445 \image html QCPItemRect.png "Rectangle example. Blue dotted circles are anchors, solid blue discs are positions."
29446
29447 It has two positions, \a topLeft and \a bottomRight, which define the rectangle.
29448*/
29449
29450/*!
29451 Creates a rectangle item and sets default values.
29452
29453 The created item is automatically registered with \a parentPlot. This QCustomPlot instance takes
29454 ownership of the item, so do not delete it manually but use QCustomPlot::removeItem() instead.
29455*/
29457 QCPAbstractItem(parentPlot),
29458 topLeft(createPosition(QLatin1String("topLeft"))),
29459 bottomRight(createPosition(QLatin1String("bottomRight"))),
29460 top(createAnchor(QLatin1String("top"), aiTop)),
29461 topRight(createAnchor(QLatin1String("topRight"), aiTopRight)),
29462 right(createAnchor(QLatin1String("right"), aiRight)),
29463 bottom(createAnchor(QLatin1String("bottom"), aiBottom)),
29464 bottomLeft(createAnchor(QLatin1String("bottomLeft"), aiBottomLeft)),
29465 left(createAnchor(QLatin1String("left"), aiLeft))
29466{
29467 topLeft->setCoords(0, 1);
29468 bottomRight->setCoords(1, 0);
29469
29474}
29475
29476QCPItemRect::~QCPItemRect()
29477{
29478}
29479
29480/*!
29481 Sets the pen that will be used to draw the line of the rectangle
29482
29483 \see setSelectedPen, setBrush
29484*/
29486{
29487 mPen = pen;
29488}
29489
29490/*!
29491 Sets the pen that will be used to draw the line of the rectangle when selected
29492
29493 \see setPen, setSelected
29494*/
29496{
29497 mSelectedPen = pen;
29498}
29499
29500/*!
29501 Sets the brush that will be used to fill the rectangle. To disable filling, set \a brush to
29502 Qt::NoBrush.
29503
29504 \see setSelectedBrush, setPen
29505*/
29507{
29508 mBrush = brush;
29509}
29510
29511/*!
29512 Sets the brush that will be used to fill the rectangle when selected. To disable filling, set \a
29513 brush to Qt::NoBrush.
29514
29515 \see setBrush
29516*/
29518{
29519 mSelectedBrush = brush;
29520}
29521
29522/* inherits documentation from base class */
29523double QCPItemRect::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
29524{
29525 Q_UNUSED(details)
29526 if (onlySelectable && !mSelectable)
29527 return -1;
29528
29529 QRectF rect = QRectF(topLeft->pixelPosition(), bottomRight->pixelPosition()).normalized();
29530 bool filledRect = mBrush.style() != Qt::NoBrush && mBrush.color().alpha() != 0;
29531 return rectDistance(rect, pos, filledRect);
29532}
29533
29534/* inherits documentation from base class */
29536{
29537 QPointF p1 = topLeft->pixelPosition();
29538 QPointF p2 = bottomRight->pixelPosition();
29539 if (p1.toPoint() == p2.toPoint())
29540 return;
29541 QRectF rect = QRectF(p1, p2).normalized();
29542 double clipPad = mainPen().widthF();
29543 QRectF boundingRect = rect.adjusted(-clipPad, -clipPad, clipPad, clipPad);
29544 if (boundingRect.intersects(clipRect())) // only draw if bounding rect of rect item is visible in cliprect
29545 {
29546 painter->setPen(mainPen());
29547 painter->setBrush(mainBrush());
29548 painter->drawRect(rect);
29549 }
29550}
29551
29552/* inherits documentation from base class */
29554{
29555 QRectF rect = QRectF(topLeft->pixelPosition(), bottomRight->pixelPosition());
29556 switch (anchorId)
29557 {
29558 case aiTop: return (rect.topLeft()+rect.topRight())*0.5;
29559 case aiTopRight: return rect.topRight();
29560 case aiRight: return (rect.topRight()+rect.bottomRight())*0.5;
29561 case aiBottom: return (rect.bottomLeft()+rect.bottomRight())*0.5;
29562 case aiBottomLeft: return rect.bottomLeft();
29563 case aiLeft: return (rect.topLeft()+rect.bottomLeft())*0.5;
29564 }
29565
29566 qDebug() << Q_FUNC_INFO << "invalid anchorId" << anchorId;
29567 return {};
29568}
29569
29570/*! \internal
29571
29572 Returns the pen that should be used for drawing lines. Returns mPen when the item is not selected
29573 and mSelectedPen when it is.
29574*/
29576{
29577 return mSelected ? mSelectedPen : mPen;
29578}
29579
29580/*! \internal
29581
29582 Returns the brush that should be used for drawing fills of the item. Returns mBrush when the item
29583 is not selected and mSelectedBrush when it is.
29584*/
29586{
29587 return mSelected ? mSelectedBrush : mBrush;
29588}
29589/* end of 'src/items/item-rect.cpp' */
29590
29591
29592/* including file 'src/items/item-text.cpp' */
29593/* modified 2022-11-06T12:45:56, size 13335 */
29594
29595////////////////////////////////////////////////////////////////////////////////////////////////////
29596//////////////////// QCPItemText
29597////////////////////////////////////////////////////////////////////////////////////////////////////
29598
29599/*! \class QCPItemText
29600 \brief A text label
29601
29602 \image html QCPItemText.png "Text example. Blue dotted circles are anchors, solid blue discs are positions."
29603
29604 Its position is defined by the member \a position and the setting of \ref setPositionAlignment.
29605 The latter controls which part of the text rect shall be aligned with \a position.
29606
29607 The text alignment itself (i.e. left, center, right) can be controlled with \ref
29608 setTextAlignment.
29609
29610 The text may be rotated around the \a position point with \ref setRotation.
29611*/
29612
29613/*!
29614 Creates a text item and sets default values.
29615
29616 The created item is automatically registered with \a parentPlot. This QCustomPlot instance takes
29617 ownership of the item, so do not delete it manually but use QCustomPlot::removeItem() instead.
29618*/
29620 QCPAbstractItem(parentPlot),
29621 position(createPosition(QLatin1String("position"))),
29622 topLeft(createAnchor(QLatin1String("topLeft"), aiTopLeft)),
29623 top(createAnchor(QLatin1String("top"), aiTop)),
29624 topRight(createAnchor(QLatin1String("topRight"), aiTopRight)),
29625 right(createAnchor(QLatin1String("right"), aiRight)),
29626 bottomRight(createAnchor(QLatin1String("bottomRight"), aiBottomRight)),
29627 bottom(createAnchor(QLatin1String("bottom"), aiBottom)),
29628 bottomLeft(createAnchor(QLatin1String("bottomLeft"), aiBottomLeft)),
29629 left(createAnchor(QLatin1String("left"), aiLeft)),
29630 mText(QLatin1String("text")),
29631 mPositionAlignment(Qt::AlignCenter),
29632 mTextAlignment(Qt::AlignTop|Qt::AlignHCenter),
29633 mRotation(0)
29634{
29635 position->setCoords(0, 0);
29636
29643}
29644
29645QCPItemText::~QCPItemText()
29646{
29647}
29648
29649/*!
29650 Sets the color of the text.
29651*/
29653{
29654 mColor = color;
29655}
29656
29657/*!
29658 Sets the color of the text that will be used when the item is selected.
29659*/
29661{
29662 mSelectedColor = color;
29663}
29664
29665/*!
29666 Sets the pen that will be used do draw a rectangular border around the text. To disable the
29667 border, set \a pen to Qt::NoPen.
29668
29669 \see setSelectedPen, setBrush, setPadding
29670*/
29672{
29673 mPen = pen;
29674}
29675
29676/*!
29677 Sets the pen that will be used do draw a rectangular border around the text, when the item is
29678 selected. To disable the border, set \a pen to Qt::NoPen.
29679
29680 \see setPen
29681*/
29683{
29684 mSelectedPen = pen;
29685}
29686
29687/*!
29688 Sets the brush that will be used do fill the background of the text. To disable the
29689 background, set \a brush to Qt::NoBrush.
29690
29691 \see setSelectedBrush, setPen, setPadding
29692*/
29694{
29695 mBrush = brush;
29696}
29697
29698/*!
29699 Sets the brush that will be used do fill the background of the text, when the item is selected. To disable the
29700 background, set \a brush to Qt::NoBrush.
29701
29702 \see setBrush
29703*/
29705{
29706 mSelectedBrush = brush;
29707}
29708
29709/*!
29710 Sets the font of the text.
29711
29712 \see setSelectedFont, setColor
29713*/
29715{
29716 mFont = font;
29717}
29718
29719/*!
29720 Sets the font of the text that will be used when the item is selected.
29721
29722 \see setFont
29723*/
29725{
29726 mSelectedFont = font;
29727}
29728
29729/*!
29730 Sets the text that will be displayed. Multi-line texts are supported by inserting a line break
29731 character, e.g. '\n'.
29732
29733 \see setFont, setColor, setTextAlignment
29734*/
29736{
29737 mText = text;
29738}
29739
29740/*!
29741 Sets which point of the text rect shall be aligned with \a position.
29742
29743 Examples:
29744 \li If \a alignment is <tt>Qt::AlignHCenter | Qt::AlignTop</tt>, the text will be positioned such
29745 that the top of the text rect will be horizontally centered on \a position.
29746 \li If \a alignment is <tt>Qt::AlignLeft | Qt::AlignBottom</tt>, \a position will indicate the
29747 bottom left corner of the text rect.
29748
29749 If you want to control the alignment of (multi-lined) text within the text rect, use \ref
29750 setTextAlignment.
29751*/
29753{
29754 mPositionAlignment = alignment;
29755}
29756
29757/*!
29758 Controls how (multi-lined) text is aligned inside the text rect (typically Qt::AlignLeft, Qt::AlignCenter or Qt::AlignRight).
29759*/
29761{
29762 mTextAlignment = alignment;
29763}
29764
29765/*!
29766 Sets the angle in degrees by which the text (and the text rectangle, if visible) will be rotated
29767 around \a position.
29768*/
29769void QCPItemText::setRotation(double degrees)
29770{
29771 mRotation = degrees;
29772}
29773
29774/*!
29775 Sets the distance between the border of the text rectangle and the text. The appearance (and
29776 visibility) of the text rectangle can be controlled with \ref setPen and \ref setBrush.
29777*/
29779{
29780 mPadding = padding;
29781}
29782
29783/* inherits documentation from base class */
29784double QCPItemText::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
29785{
29786 Q_UNUSED(details)
29787 if (onlySelectable && !mSelectable)
29788 return -1;
29789
29790 // The rect may be rotated, so we transform the actual clicked pos to the rotated
29791 // coordinate system, so we can use the normal rectDistance function for non-rotated rects:
29792 QPointF positionPixels(position->pixelPosition());
29793 QTransform inputTransform;
29794 inputTransform.translate(positionPixels.x(), positionPixels.y());
29795 inputTransform.rotate(-mRotation);
29796 inputTransform.translate(-positionPixels.x(), -positionPixels.y());
29797 QPointF rotatedPos = inputTransform.map(pos);
29798 QFontMetrics fontMetrics(mFont);
29799 QRect textRect = fontMetrics.boundingRect(0, 0, 0, 0, Qt::TextDontClip|mTextAlignment, mText);
29800 QRect textBoxRect = textRect.adjusted(-mPadding.left(), -mPadding.top(), mPadding.right(), mPadding.bottom());
29801 QPointF textPos = getTextDrawPoint(positionPixels, textBoxRect, mPositionAlignment);
29802 textBoxRect.moveTopLeft(textPos.toPoint());
29803
29804 return rectDistance(textBoxRect, rotatedPos, true);
29805}
29806
29807/* inherits documentation from base class */
29809{
29810 QPointF pos(position->pixelPosition());
29811 QTransform transform = painter->transform();
29812 transform.translate(pos.x(), pos.y());
29813 if (!qFuzzyIsNull(mRotation))
29814 transform.rotate(mRotation);
29815 painter->setFont(mainFont());
29816 QRect textRect = painter->fontMetrics().boundingRect(0, 0, 0, 0, Qt::TextDontClip|mTextAlignment, mText);
29817 QRect textBoxRect = textRect.adjusted(-mPadding.left(), -mPadding.top(), mPadding.right(), mPadding.bottom());
29818 QPointF textPos = getTextDrawPoint(QPointF(0, 0), textBoxRect, mPositionAlignment); // 0, 0 because the transform does the translation
29819 textRect.moveTopLeft(textPos.toPoint()+QPoint(mPadding.left(), mPadding.top()));
29820 textBoxRect.moveTopLeft(textPos.toPoint());
29821 int clipPad = qCeil(mainPen().widthF());
29822 QRect boundingRect = textBoxRect.adjusted(-clipPad, -clipPad, clipPad, clipPad);
29823 if (transform.mapRect(boundingRect).intersects(painter->transform().mapRect(clipRect())))
29824 {
29825 painter->setTransform(transform);
29826 if ((mainBrush().style() != Qt::NoBrush && mainBrush().color().alpha() != 0) ||
29827 (mainPen().style() != Qt::NoPen && mainPen().color().alpha() != 0))
29828 {
29829 painter->setPen(mainPen());
29830 painter->setBrush(mainBrush());
29831 painter->drawRect(textBoxRect);
29832 }
29833 painter->setBrush(Qt::NoBrush);
29834 painter->setPen(QPen(mainColor()));
29835 painter->drawText(textRect, Qt::TextDontClip|mTextAlignment, mText);
29836 }
29837}
29838
29839/* inherits documentation from base class */
29841{
29842 // get actual rect points (pretty much copied from draw function):
29843 QPointF pos(position->pixelPosition());
29844 QTransform transform;
29845 transform.translate(pos.x(), pos.y());
29846 if (!qFuzzyIsNull(mRotation))
29847 transform.rotate(mRotation);
29848 QFontMetrics fontMetrics(mainFont());
29849 QRect textRect = fontMetrics.boundingRect(0, 0, 0, 0, Qt::TextDontClip|mTextAlignment, mText);
29850 QRectF textBoxRect = textRect.adjusted(-mPadding.left(), -mPadding.top(), mPadding.right(), mPadding.bottom());
29851 QPointF textPos = getTextDrawPoint(QPointF(0, 0), textBoxRect, mPositionAlignment); // 0, 0 because the transform does the translation
29852 textBoxRect.moveTopLeft(textPos.toPoint());
29853 QPolygonF rectPoly = transform.map(QPolygonF(textBoxRect));
29854
29855 switch (anchorId)
29856 {
29857 case aiTopLeft: return rectPoly.at(0);
29858 case aiTop: return (rectPoly.at(0)+rectPoly.at(1))*0.5;
29859 case aiTopRight: return rectPoly.at(1);
29860 case aiRight: return (rectPoly.at(1)+rectPoly.at(2))*0.5;
29861 case aiBottomRight: return rectPoly.at(2);
29862 case aiBottom: return (rectPoly.at(2)+rectPoly.at(3))*0.5;
29863 case aiBottomLeft: return rectPoly.at(3);
29864 case aiLeft: return (rectPoly.at(3)+rectPoly.at(0))*0.5;
29865 }
29866
29867 qDebug() << Q_FUNC_INFO << "invalid anchorId" << anchorId;
29868 return {};
29869}
29870
29871/*! \internal
29872
29873 Returns the point that must be given to the QPainter::drawText function (which expects the top
29874 left point of the text rect), according to the position \a pos, the text bounding box \a rect and
29875 the requested \a positionAlignment.
29876
29877 For example, if \a positionAlignment is <tt>Qt::AlignLeft | Qt::AlignBottom</tt> the returned point
29878 will be shifted upward by the height of \a rect, starting from \a pos. So if the text is finally
29879 drawn at that point, the lower left corner of the resulting text rect is at \a pos.
29880*/
29881QPointF QCPItemText::getTextDrawPoint(const QPointF &pos, const QRectF &rect, Qt::Alignment positionAlignment) const
29882{
29883 if (positionAlignment == 0 || positionAlignment == (Qt::AlignLeft|Qt::AlignTop))
29884 return pos;
29885
29886 QPointF result = pos; // start at top left
29887 if (positionAlignment.testFlag(Qt::AlignHCenter))
29888 result.rx() -= rect.width()/2.0;
29889 else if (positionAlignment.testFlag(Qt::AlignRight))
29890 result.rx() -= rect.width();
29891 if (positionAlignment.testFlag(Qt::AlignVCenter))
29892 result.ry() -= rect.height()/2.0;
29893 else if (positionAlignment.testFlag(Qt::AlignBottom))
29894 result.ry() -= rect.height();
29895 return result;
29896}
29897
29898/*! \internal
29899
29900 Returns the font that should be used for drawing text. Returns mFont when the item is not selected
29901 and mSelectedFont when it is.
29902*/
29904{
29905 return mSelected ? mSelectedFont : mFont;
29906}
29907
29908/*! \internal
29909
29910 Returns the color that should be used for drawing text. Returns mColor when the item is not
29911 selected and mSelectedColor when it is.
29912*/
29914{
29915 return mSelected ? mSelectedColor : mColor;
29916}
29917
29918/*! \internal
29919
29920 Returns the pen that should be used for drawing lines. Returns mPen when the item is not selected
29921 and mSelectedPen when it is.
29922*/
29924{
29925 return mSelected ? mSelectedPen : mPen;
29926}
29927
29928/*! \internal
29929
29930 Returns the brush that should be used for drawing fills of the item. Returns mBrush when the item
29931 is not selected and mSelectedBrush when it is.
29932*/
29934{
29935 return mSelected ? mSelectedBrush : mBrush;
29936}
29937/* end of 'src/items/item-text.cpp' */
29938
29939
29940/* including file 'src/items/item-ellipse.cpp' */
29941/* modified 2022-11-06T12:45:56, size 7881 */
29942
29943////////////////////////////////////////////////////////////////////////////////////////////////////
29944//////////////////// QCPItemEllipse
29945////////////////////////////////////////////////////////////////////////////////////////////////////
29946
29947/*! \class QCPItemEllipse
29948 \brief An ellipse
29949
29950 \image html QCPItemEllipse.png "Ellipse example. Blue dotted circles are anchors, solid blue discs are positions."
29951
29952 It has two positions, \a topLeft and \a bottomRight, which define the rect the ellipse will be drawn in.
29953*/
29954
29955/*!
29956 Creates an ellipse item and sets default values.
29957
29958 The created item is automatically registered with \a parentPlot. This QCustomPlot instance takes
29959 ownership of the item, so do not delete it manually but use QCustomPlot::removeItem() instead.
29960*/
29962 QCPAbstractItem(parentPlot),
29963 topLeft(createPosition(QLatin1String("topLeft"))),
29964 bottomRight(createPosition(QLatin1String("bottomRight"))),
29965 topLeftRim(createAnchor(QLatin1String("topLeftRim"), aiTopLeftRim)),
29966 top(createAnchor(QLatin1String("top"), aiTop)),
29967 topRightRim(createAnchor(QLatin1String("topRightRim"), aiTopRightRim)),
29968 right(createAnchor(QLatin1String("right"), aiRight)),
29969 bottomRightRim(createAnchor(QLatin1String("bottomRightRim"), aiBottomRightRim)),
29970 bottom(createAnchor(QLatin1String("bottom"), aiBottom)),
29971 bottomLeftRim(createAnchor(QLatin1String("bottomLeftRim"), aiBottomLeftRim)),
29972 left(createAnchor(QLatin1String("left"), aiLeft)),
29973 center(createAnchor(QLatin1String("center"), aiCenter))
29974{
29975 topLeft->setCoords(0, 1);
29976 bottomRight->setCoords(1, 0);
29977
29982}
29983
29984QCPItemEllipse::~QCPItemEllipse()
29985{
29986}
29987
29988/*!
29989 Sets the pen that will be used to draw the line of the ellipse
29990
29991 \see setSelectedPen, setBrush
29992*/
29994{
29995 mPen = pen;
29996}
29997
29998/*!
29999 Sets the pen that will be used to draw the line of the ellipse when selected
30000
30001 \see setPen, setSelected
30002*/
30004{
30005 mSelectedPen = pen;
30006}
30007
30008/*!
30009 Sets the brush that will be used to fill the ellipse. To disable filling, set \a brush to
30010 Qt::NoBrush.
30011
30012 \see setSelectedBrush, setPen
30013*/
30015{
30016 mBrush = brush;
30017}
30018
30019/*!
30020 Sets the brush that will be used to fill the ellipse when selected. To disable filling, set \a
30021 brush to Qt::NoBrush.
30022
30023 \see setBrush
30024*/
30026{
30027 mSelectedBrush = brush;
30028}
30029
30030/* inherits documentation from base class */
30031double QCPItemEllipse::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
30032{
30033 Q_UNUSED(details)
30034 if (onlySelectable && !mSelectable)
30035 return -1;
30036
30037 QPointF p1 = topLeft->pixelPosition();
30038 QPointF p2 = bottomRight->pixelPosition();
30039 QPointF center((p1+p2)/2.0);
30040 double a = qAbs(p1.x()-p2.x())/2.0;
30041 double b = qAbs(p1.y()-p2.y())/2.0;
30042 double x = pos.x()-center.x();
30043 double y = pos.y()-center.y();
30044
30045 // distance to border:
30046 double c = 1.0/qSqrt(x*x/(a*a)+y*y/(b*b));
30047 double result = qAbs(c-1)*qSqrt(x*x+y*y);
30048 // filled ellipse, allow click inside to count as hit:
30049 if (result > mParentPlot->selectionTolerance()*0.99 && mBrush.style() != Qt::NoBrush && mBrush.color().alpha() != 0)
30050 {
30051 if (x*x/(a*a) + y*y/(b*b) <= 1)
30052 result = mParentPlot->selectionTolerance()*0.99;
30053 }
30054 return result;
30055}
30056
30057/* inherits documentation from base class */
30059{
30060 QPointF p1 = topLeft->pixelPosition();
30061 QPointF p2 = bottomRight->pixelPosition();
30062 if (p1.toPoint() == p2.toPoint())
30063 return;
30064 QRectF ellipseRect = QRectF(p1, p2).normalized();
30065 const int clipEnlarge = qCeil(mainPen().widthF());
30066 QRect clip = clipRect().adjusted(-clipEnlarge, -clipEnlarge, clipEnlarge, clipEnlarge);
30067 if (ellipseRect.intersects(clip)) // only draw if bounding rect of ellipse is visible in cliprect
30068 {
30069 painter->setPen(mainPen());
30070 painter->setBrush(mainBrush());
30071#ifdef __EXCEPTIONS
30072 try // drawEllipse sometimes throws exceptions if ellipse is too big
30073 {
30074#endif
30075 painter->drawEllipse(ellipseRect);
30076#ifdef __EXCEPTIONS
30077 } catch (...)
30078 {
30079 qDebug() << Q_FUNC_INFO << "Item too large for memory, setting invisible";
30080 setVisible(false);
30081 }
30082#endif
30083 }
30084}
30085
30086/* inherits documentation from base class */
30088{
30089 QRectF rect = QRectF(topLeft->pixelPosition(), bottomRight->pixelPosition());
30090 switch (anchorId)
30091 {
30092 case aiTopLeftRim: return rect.center()+(rect.topLeft()-rect.center())*1/qSqrt(2);
30093 case aiTop: return (rect.topLeft()+rect.topRight())*0.5;
30094 case aiTopRightRim: return rect.center()+(rect.topRight()-rect.center())*1/qSqrt(2);
30095 case aiRight: return (rect.topRight()+rect.bottomRight())*0.5;
30096 case aiBottomRightRim: return rect.center()+(rect.bottomRight()-rect.center())*1/qSqrt(2);
30097 case aiBottom: return (rect.bottomLeft()+rect.bottomRight())*0.5;
30098 case aiBottomLeftRim: return rect.center()+(rect.bottomLeft()-rect.center())*1/qSqrt(2);
30099 case aiLeft: return (rect.topLeft()+rect.bottomLeft())*0.5;
30100 case aiCenter: return (rect.topLeft()+rect.bottomRight())*0.5;
30101 }
30102
30103 qDebug() << Q_FUNC_INFO << "invalid anchorId" << anchorId;
30104 return {};
30105}
30106
30107/*! \internal
30108
30109 Returns the pen that should be used for drawing lines. Returns mPen when the item is not selected
30110 and mSelectedPen when it is.
30111*/
30113{
30114 return mSelected ? mSelectedPen : mPen;
30115}
30116
30117/*! \internal
30118
30119 Returns the brush that should be used for drawing fills of the item. Returns mBrush when the item
30120 is not selected and mSelectedBrush when it is.
30121*/
30123{
30124 return mSelected ? mSelectedBrush : mBrush;
30125}
30126/* end of 'src/items/item-ellipse.cpp' */
30127
30128
30129/* including file 'src/items/item-pixmap.cpp' */
30130/* modified 2022-11-06T12:45:56, size 10622 */
30131
30132////////////////////////////////////////////////////////////////////////////////////////////////////
30133//////////////////// QCPItemPixmap
30134////////////////////////////////////////////////////////////////////////////////////////////////////
30135
30136/*! \class QCPItemPixmap
30137 \brief An arbitrary pixmap
30138
30139 \image html QCPItemPixmap.png "Pixmap example. Blue dotted circles are anchors, solid blue discs are positions."
30140
30141 It has two positions, \a topLeft and \a bottomRight, which define the rectangle the pixmap will
30142 be drawn in. Depending on the scale setting (\ref setScaled), the pixmap will be either scaled to
30143 fit the rectangle or be drawn aligned to the topLeft position.
30144
30145 If scaling is enabled and \a topLeft is further to the bottom/right than \a bottomRight (as shown
30146 on the right side of the example image), the pixmap will be flipped in the respective
30147 orientations.
30148*/
30149
30150/*!
30151 Creates a rectangle item and sets default values.
30152
30153 The created item is automatically registered with \a parentPlot. This QCustomPlot instance takes
30154 ownership of the item, so do not delete it manually but use QCustomPlot::removeItem() instead.
30155*/
30157 QCPAbstractItem(parentPlot),
30158 topLeft(createPosition(QLatin1String("topLeft"))),
30159 bottomRight(createPosition(QLatin1String("bottomRight"))),
30160 top(createAnchor(QLatin1String("top"), aiTop)),
30161 topRight(createAnchor(QLatin1String("topRight"), aiTopRight)),
30162 right(createAnchor(QLatin1String("right"), aiRight)),
30163 bottom(createAnchor(QLatin1String("bottom"), aiBottom)),
30164 bottomLeft(createAnchor(QLatin1String("bottomLeft"), aiBottomLeft)),
30165 left(createAnchor(QLatin1String("left"), aiLeft)),
30166 mScaled(false),
30167 mScaledPixmapInvalidated(true),
30168 mAspectRatioMode(Qt::KeepAspectRatio),
30169 mTransformationMode(Qt::SmoothTransformation)
30170{
30171 topLeft->setCoords(0, 1);
30172 bottomRight->setCoords(1, 0);
30173
30176}
30177
30178QCPItemPixmap::~QCPItemPixmap()
30179{
30180}
30181
30182/*!
30183 Sets the pixmap that will be displayed.
30184*/
30186{
30187 mPixmap = pixmap;
30188 mScaledPixmapInvalidated = true;
30189 if (mPixmap.isNull())
30190 qDebug() << Q_FUNC_INFO << "pixmap is null";
30191}
30192
30193/*!
30194 Sets whether the pixmap will be scaled to fit the rectangle defined by the \a topLeft and \a
30195 bottomRight positions.
30196*/
30197void QCPItemPixmap::setScaled(bool scaled, Qt::AspectRatioMode aspectRatioMode, Qt::TransformationMode transformationMode)
30198{
30199 mScaled = scaled;
30200 mAspectRatioMode = aspectRatioMode;
30201 mTransformationMode = transformationMode;
30202 mScaledPixmapInvalidated = true;
30203}
30204
30205/*!
30206 Sets the pen that will be used to draw a border around the pixmap.
30207
30208 \see setSelectedPen, setBrush
30209*/
30211{
30212 mPen = pen;
30213}
30214
30215/*!
30216 Sets the pen that will be used to draw a border around the pixmap when selected
30217
30218 \see setPen, setSelected
30219*/
30221{
30222 mSelectedPen = pen;
30223}
30224
30225/* inherits documentation from base class */
30226double QCPItemPixmap::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
30227{
30228 Q_UNUSED(details)
30229 if (onlySelectable && !mSelectable)
30230 return -1;
30231
30232 return rectDistance(getFinalRect(), pos, true);
30233}
30234
30235/* inherits documentation from base class */
30237{
30238 bool flipHorz = false;
30239 bool flipVert = false;
30240 QRect rect = getFinalRect(&flipHorz, &flipVert);
30241 int clipPad = mainPen().style() == Qt::NoPen ? 0 : qCeil(mainPen().widthF());
30242 QRect boundingRect = rect.adjusted(-clipPad, -clipPad, clipPad, clipPad);
30243 if (boundingRect.intersects(clipRect()))
30244 {
30245 updateScaledPixmap(rect, flipHorz, flipVert);
30246 painter->drawPixmap(rect.topLeft(), mScaled ? mScaledPixmap : mPixmap);
30247 QPen pen = mainPen();
30248 if (pen.style() != Qt::NoPen)
30249 {
30250 painter->setPen(pen);
30251 painter->setBrush(Qt::NoBrush);
30252 painter->drawRect(rect);
30253 }
30254 }
30255}
30256
30257/* inherits documentation from base class */
30259{
30260 bool flipHorz = false;
30261 bool flipVert = false;
30262 QRect rect = getFinalRect(&flipHorz, &flipVert);
30263 // we actually want denormal rects (negative width/height) here, so restore
30264 // the flipped state:
30265 if (flipHorz)
30266 rect.adjust(rect.width(), 0, -rect.width(), 0);
30267 if (flipVert)
30268 rect.adjust(0, rect.height(), 0, -rect.height());
30269
30270 switch (anchorId)
30271 {
30272 case aiTop: return (rect.topLeft()+rect.topRight())*0.5;
30273 case aiTopRight: return rect.topRight();
30274 case aiRight: return (rect.topRight()+rect.bottomRight())*0.5;
30275 case aiBottom: return (rect.bottomLeft()+rect.bottomRight())*0.5;
30276 case aiBottomLeft: return rect.bottomLeft();
30277 case aiLeft: return (rect.topLeft()+rect.bottomLeft())*0.5;
30278 }
30279
30280 qDebug() << Q_FUNC_INFO << "invalid anchorId" << anchorId;
30281 return {};
30282}
30283
30284/*! \internal
30285
30286 Creates the buffered scaled image (\a mScaledPixmap) to fit the specified \a finalRect. The
30287 parameters \a flipHorz and \a flipVert control whether the resulting image shall be flipped
30288 horizontally or vertically. (This is used when \a topLeft is further to the bottom/right than \a
30289 bottomRight.)
30290
30291 This function only creates the scaled pixmap when the buffered pixmap has a different size than
30292 the expected result, so calling this function repeatedly, e.g. in the \ref draw function, does
30293 not cause expensive rescaling every time.
30294
30295 If scaling is disabled, sets mScaledPixmap to a null QPixmap.
30296*/
30297void QCPItemPixmap::updateScaledPixmap(QRect finalRect, bool flipHorz, bool flipVert)
30298{
30299 if (mPixmap.isNull())
30300 return;
30301
30302 if (mScaled)
30303 {
30304#ifdef QCP_DEVICEPIXELRATIO_SUPPORTED
30305 double devicePixelRatio = mPixmap.devicePixelRatio();
30306#else
30307 double devicePixelRatio = 1.0;
30308#endif
30309 if (finalRect.isNull())
30310 finalRect = getFinalRect(&flipHorz, &flipVert);
30311 if (mScaledPixmapInvalidated || finalRect.size() != mScaledPixmap.size()/devicePixelRatio)
30312 {
30313 mScaledPixmap = mPixmap.scaled(finalRect.size()*devicePixelRatio, mAspectRatioMode, mTransformationMode);
30314 if (flipHorz || flipVert)
30315 mScaledPixmap = QPixmap::fromImage(mScaledPixmap.toImage().mirrored(flipHorz, flipVert));
30316#ifdef QCP_DEVICEPIXELRATIO_SUPPORTED
30317 mScaledPixmap.setDevicePixelRatio(devicePixelRatio);
30318#endif
30319 }
30320 } else if (!mScaledPixmap.isNull())
30321 mScaledPixmap = QPixmap();
30322 mScaledPixmapInvalidated = false;
30323}
30324
30325/*! \internal
30326
30327 Returns the final (tight) rect the pixmap is drawn in, depending on the current item positions
30328 and scaling settings.
30329
30330 The output parameters \a flippedHorz and \a flippedVert return whether the pixmap should be drawn
30331 flipped horizontally or vertically in the returned rect. (The returned rect itself is always
30332 normalized, i.e. the top left corner of the rect is actually further to the top/left than the
30333 bottom right corner). This is the case when the item position \a topLeft is further to the
30334 bottom/right than \a bottomRight.
30335
30336 If scaling is disabled, returns a rect with size of the original pixmap and the top left corner
30337 aligned with the item position \a topLeft. The position \a bottomRight is ignored.
30338*/
30339QRect QCPItemPixmap::getFinalRect(bool *flippedHorz, bool *flippedVert) const
30340{
30341 QRect result;
30342 bool flipHorz = false;
30343 bool flipVert = false;
30344 QPoint p1 = topLeft->pixelPosition().toPoint();
30345 QPoint p2 = bottomRight->pixelPosition().toPoint();
30346 if (p1 == p2)
30347 return {p1, QSize(0, 0)};
30348 if (mScaled)
30349 {
30350 QSize newSize = QSize(p2.x()-p1.x(), p2.y()-p1.y());
30351 QPoint topLeft = p1;
30352 if (newSize.width() < 0)
30353 {
30354 flipHorz = true;
30355 newSize.rwidth() *= -1;
30356 topLeft.setX(p2.x());
30357 }
30358 if (newSize.height() < 0)
30359 {
30360 flipVert = true;
30361 newSize.rheight() *= -1;
30362 topLeft.setY(p2.y());
30363 }
30364 QSize scaledSize = mPixmap.size();
30365#ifdef QCP_DEVICEPIXELRATIO_SUPPORTED
30366 scaledSize /= mPixmap.devicePixelRatio();
30367 scaledSize.scale(newSize*mPixmap.devicePixelRatio(), mAspectRatioMode);
30368#else
30369 scaledSize.scale(newSize, mAspectRatioMode);
30370#endif
30371 result = QRect(topLeft, scaledSize);
30372 } else
30373 {
30374#ifdef QCP_DEVICEPIXELRATIO_SUPPORTED
30375 result = QRect(p1, mPixmap.size()/mPixmap.devicePixelRatio());
30376#else
30377 result = QRect(p1, mPixmap.size());
30378#endif
30379 }
30380 if (flippedHorz)
30381 *flippedHorz = flipHorz;
30382 if (flippedVert)
30383 *flippedVert = flipVert;
30384 return result;
30385}
30386
30387/*! \internal
30388
30389 Returns the pen that should be used for drawing lines. Returns mPen when the item is not selected
30390 and mSelectedPen when it is.
30391*/
30393{
30394 return mSelected ? mSelectedPen : mPen;
30395}
30396/* end of 'src/items/item-pixmap.cpp' */
30397
30398
30399/* including file 'src/items/item-tracer.cpp' */
30400/* modified 2022-11-06T12:45:56, size 14645 */
30401
30402////////////////////////////////////////////////////////////////////////////////////////////////////
30403//////////////////// QCPItemTracer
30404////////////////////////////////////////////////////////////////////////////////////////////////////
30405
30406/*! \class QCPItemTracer
30407 \brief Item that sticks to QCPGraph data points
30408
30409 \image html QCPItemTracer.png "Tracer example. Blue dotted circles are anchors, solid blue discs are positions."
30410
30411 The tracer can be connected with a QCPGraph via \ref setGraph. Then it will automatically adopt
30412 the coordinate axes of the graph and update its \a position to be on the graph's data. This means
30413 the key stays controllable via \ref setGraphKey, but the value will follow the graph data. If a
30414 QCPGraph is connected, note that setting the coordinates of the tracer item directly via \a
30415 position will have no effect because they will be overriden in the next redraw (this is when the
30416 coordinate update happens).
30417
30418 If the specified key in \ref setGraphKey is outside the key bounds of the graph, the tracer will
30419 stay at the corresponding end of the graph.
30420
30421 With \ref setInterpolating you may specify whether the tracer may only stay exactly on data
30422 points or whether it interpolates data points linearly, if given a key that lies between two data
30423 points of the graph.
30424
30425 The tracer has different visual styles, see \ref setStyle. It is also possible to make the tracer
30426 have no own visual appearance (set the style to \ref tsNone), and just connect other item
30427 positions to the tracer \a position (used as an anchor) via \ref
30428 QCPItemPosition::setParentAnchor.
30429
30430 \note The tracer position is only automatically updated upon redraws. So when the data of the
30431 graph changes and immediately afterwards (without a redraw) the position coordinates of the
30432 tracer are retrieved, they will not reflect the updated data of the graph. In this case \ref
30433 updatePosition must be called manually, prior to reading the tracer coordinates.
30434*/
30435
30436/*!
30437 Creates a tracer item and sets default values.
30438
30439 The created item is automatically registered with \a parentPlot. This QCustomPlot instance takes
30440 ownership of the item, so do not delete it manually but use QCustomPlot::removeItem() instead.
30441*/
30443 QCPAbstractItem(parentPlot),
30444 position(createPosition(QLatin1String("position"))),
30445 mSize(6),
30446 mStyle(tsCrosshair),
30447 mGraph(nullptr),
30448 mGraphKey(0),
30449 mInterpolating(false)
30450{
30451 position->setCoords(0, 0);
30452
30457}
30458
30459QCPItemTracer::~QCPItemTracer()
30460{
30461}
30462
30463/*!
30464 Sets the pen that will be used to draw the line of the tracer
30465
30466 \see setSelectedPen, setBrush
30467*/
30469{
30470 mPen = pen;
30471}
30472
30473/*!
30474 Sets the pen that will be used to draw the line of the tracer when selected
30475
30476 \see setPen, setSelected
30477*/
30479{
30480 mSelectedPen = pen;
30481}
30482
30483/*!
30484 Sets the brush that will be used to draw any fills of the tracer
30485
30486 \see setSelectedBrush, setPen
30487*/
30489{
30490 mBrush = brush;
30491}
30492
30493/*!
30494 Sets the brush that will be used to draw any fills of the tracer, when selected.
30495
30496 \see setBrush, setSelected
30497*/
30499{
30500 mSelectedBrush = brush;
30501}
30502
30503/*!
30504 Sets the size of the tracer in pixels, if the style supports setting a size (e.g. \ref tsSquare
30505 does, \ref tsCrosshair does not).
30506*/
30507void QCPItemTracer::setSize(double size)
30508{
30509 mSize = size;
30510}
30511
30512/*!
30513 Sets the style/visual appearance of the tracer.
30514
30515 If you only want to use the tracer \a position as an anchor for other items, set \a style to
30516 \ref tsNone.
30517*/
30519{
30520 mStyle = style;
30521}
30522
30523/*!
30524 Sets the QCPGraph this tracer sticks to. The tracer \a position will be set to type
30525 QCPItemPosition::ptPlotCoords and the axes will be set to the axes of \a graph.
30526
30527 To free the tracer from any graph, set \a graph to \c nullptr. The tracer \a position can then be
30528 placed freely like any other item position. This is the state the tracer will assume when its
30529 graph gets deleted while still attached to it.
30530
30531 \see setGraphKey
30532*/
30534{
30535 if (graph)
30536 {
30537 if (graph->parentPlot() == mParentPlot)
30538 {
30540 position->setAxes(graph->keyAxis(), graph->valueAxis());
30541 mGraph = graph;
30543 } else
30544 qDebug() << Q_FUNC_INFO << "graph isn't in same QCustomPlot instance as this item";
30545 } else
30546 {
30547 mGraph = nullptr;
30548 }
30549}
30550
30551/*!
30552 Sets the key of the graph's data point the tracer will be positioned at. This is the only free
30553 coordinate of a tracer when attached to a graph.
30554
30555 Depending on \ref setInterpolating, the tracer will be either positioned on the data point
30556 closest to \a key, or will stay exactly at \a key and interpolate the value linearly.
30557
30558 \see setGraph, setInterpolating
30559*/
30561{
30562 mGraphKey = key;
30563}
30564
30565/*!
30566 Sets whether the value of the graph's data points shall be interpolated, when positioning the
30567 tracer.
30568
30569 If \a enabled is set to false and a key is given with \ref setGraphKey, the tracer is placed on
30570 the data point of the graph which is closest to the key, but which is not necessarily exactly
30571 there. If \a enabled is true, the tracer will be positioned exactly at the specified key, and
30572 the appropriate value will be interpolated from the graph's data points linearly.
30573
30574 \see setGraph, setGraphKey
30575*/
30577{
30578 mInterpolating = enabled;
30579}
30580
30581/* inherits documentation from base class */
30582double QCPItemTracer::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
30583{
30584 Q_UNUSED(details)
30585 if (onlySelectable && !mSelectable)
30586 return -1;
30587
30588 QPointF center(position->pixelPosition());
30589 double w = mSize/2.0;
30590 QRect clip = clipRect();
30591 switch (mStyle)
30592 {
30593 case tsNone: return -1;
30594 case tsPlus:
30595 {
30596 if (clipRect().intersects(QRectF(center-QPointF(w, w), center+QPointF(w, w)).toRect()))
30597 return qSqrt(qMin(QCPVector2D(pos).distanceSquaredToLine(center+QPointF(-w, 0), center+QPointF(w, 0)),
30598 QCPVector2D(pos).distanceSquaredToLine(center+QPointF(0, -w), center+QPointF(0, w))));
30599 break;
30600 }
30601 case tsCrosshair:
30602 {
30603 return qSqrt(qMin(QCPVector2D(pos).distanceSquaredToLine(QCPVector2D(clip.left(), center.y()), QCPVector2D(clip.right(), center.y())),
30604 QCPVector2D(pos).distanceSquaredToLine(QCPVector2D(center.x(), clip.top()), QCPVector2D(center.x(), clip.bottom()))));
30605 }
30606 case tsCircle:
30607 {
30608 if (clip.intersects(QRectF(center-QPointF(w, w), center+QPointF(w, w)).toRect()))
30609 {
30610 // distance to border:
30611 double centerDist = QCPVector2D(center-pos).length();
30612 double circleLine = w;
30613 double result = qAbs(centerDist-circleLine);
30614 // filled ellipse, allow click inside to count as hit:
30615 if (result > mParentPlot->selectionTolerance()*0.99 && mBrush.style() != Qt::NoBrush && mBrush.color().alpha() != 0)
30616 {
30617 if (centerDist <= circleLine)
30618 result = mParentPlot->selectionTolerance()*0.99;
30619 }
30620 return result;
30621 }
30622 break;
30623 }
30624 case tsSquare:
30625 {
30626 if (clip.intersects(QRectF(center-QPointF(w, w), center+QPointF(w, w)).toRect()))
30627 {
30628 QRectF rect = QRectF(center-QPointF(w, w), center+QPointF(w, w));
30629 bool filledRect = mBrush.style() != Qt::NoBrush && mBrush.color().alpha() != 0;
30630 return rectDistance(rect, pos, filledRect);
30631 }
30632 break;
30633 }
30634 }
30635 return -1;
30636}
30637
30638/* inherits documentation from base class */
30640{
30642 if (mStyle == tsNone)
30643 return;
30644
30645 painter->setPen(mainPen());
30646 painter->setBrush(mainBrush());
30647 QPointF center(position->pixelPosition());
30648 double w = mSize/2.0;
30649 QRect clip = clipRect();
30650 switch (mStyle)
30651 {
30652 case tsNone: return;
30653 case tsPlus:
30654 {
30655 if (clip.intersects(QRectF(center-QPointF(w, w), center+QPointF(w, w)).toRect()))
30656 {
30657 painter->drawLine(QLineF(center+QPointF(-w, 0), center+QPointF(w, 0)));
30658 painter->drawLine(QLineF(center+QPointF(0, -w), center+QPointF(0, w)));
30659 }
30660 break;
30661 }
30662 case tsCrosshair:
30663 {
30664 if (center.y() > clip.top() && center.y() < clip.bottom())
30665 painter->drawLine(QLineF(clip.left(), center.y(), clip.right(), center.y()));
30666 if (center.x() > clip.left() && center.x() < clip.right())
30667 painter->drawLine(QLineF(center.x(), clip.top(), center.x(), clip.bottom()));
30668 break;
30669 }
30670 case tsCircle:
30671 {
30672 if (clip.intersects(QRectF(center-QPointF(w, w), center+QPointF(w, w)).toRect()))
30673 painter->drawEllipse(center, w, w);
30674 break;
30675 }
30676 case tsSquare:
30677 {
30678 if (clip.intersects(QRectF(center-QPointF(w, w), center+QPointF(w, w)).toRect()))
30679 painter->drawRect(QRectF(center-QPointF(w, w), center+QPointF(w, w)));
30680 break;
30681 }
30682 }
30683}
30684
30685/*!
30686 If the tracer is connected with a graph (\ref setGraph), this function updates the tracer's \a
30687 position to reside on the graph data, depending on the configured key (\ref setGraphKey).
30688
30689 It is called automatically on every redraw and normally doesn't need to be called manually. One
30690 exception is when you want to read the tracer coordinates via \a position and are not sure that
30691 the graph's data (or the tracer key with \ref setGraphKey) hasn't changed since the last redraw.
30692 In that situation, call this function before accessing \a position, to make sure you don't get
30693 out-of-date coordinates.
30694
30695 If there is no graph set on this tracer, this function does nothing.
30696*/
30698{
30699 if (mGraph)
30700 {
30701 if (mParentPlot->hasPlottable(mGraph))
30702 {
30703 if (mGraph->data()->size() > 1)
30704 {
30705 QCPGraphDataContainer::const_iterator first = mGraph->data()->constBegin();
30706 QCPGraphDataContainer::const_iterator last = mGraph->data()->constEnd()-1;
30707 if (mGraphKey <= first->key)
30708 position->setCoords(first->key, first->value);
30709 else if (mGraphKey >= last->key)
30710 position->setCoords(last->key, last->value);
30711 else
30712 {
30713 QCPGraphDataContainer::const_iterator it = mGraph->data()->findBegin(mGraphKey);
30714 if (it != mGraph->data()->constEnd()) // mGraphKey is not exactly on last iterator, but somewhere between iterators
30715 {
30717 ++it; // won't advance to constEnd because we handled that case (mGraphKey >= last->key) before
30718 if (mInterpolating)
30719 {
30720 // interpolate between iterators around mGraphKey:
30721 double slope = 0;
30722 if (!qFuzzyCompare(double(it->key), double(prevIt->key)))
30723 slope = (it->value-prevIt->value)/(it->key-prevIt->key);
30724 position->setCoords(mGraphKey, (mGraphKey-prevIt->key)*slope+prevIt->value);
30725 } else
30726 {
30727 // find iterator with key closest to mGraphKey:
30728 if (mGraphKey < (prevIt->key+it->key)*0.5)
30729 position->setCoords(prevIt->key, prevIt->value);
30730 else
30731 position->setCoords(it->key, it->value);
30732 }
30733 } else // mGraphKey is exactly on last iterator (should actually be caught when comparing first/last keys, but this is a failsafe for fp uncertainty)
30734 position->setCoords(it->key, it->value);
30735 }
30736 } else if (mGraph->data()->size() == 1)
30737 {
30738 QCPGraphDataContainer::const_iterator it = mGraph->data()->constBegin();
30739 position->setCoords(it->key, it->value);
30740 } else
30741 qDebug() << Q_FUNC_INFO << "graph has no data";
30742 } else
30743 qDebug() << Q_FUNC_INFO << "graph not contained in QCustomPlot instance (anymore)";
30744 }
30745}
30746
30747/*! \internal
30748
30749 Returns the pen that should be used for drawing lines. Returns mPen when the item is not selected
30750 and mSelectedPen when it is.
30751*/
30753{
30754 return mSelected ? mSelectedPen : mPen;
30755}
30756
30757/*! \internal
30758
30759 Returns the brush that should be used for drawing fills of the item. Returns mBrush when the item
30760 is not selected and mSelectedBrush when it is.
30761*/
30763{
30764 return mSelected ? mSelectedBrush : mBrush;
30765}
30766/* end of 'src/items/item-tracer.cpp' */
30767
30768
30769/* including file 'src/items/item-bracket.cpp' */
30770/* modified 2022-11-06T12:45:56, size 10705 */
30771
30772////////////////////////////////////////////////////////////////////////////////////////////////////
30773//////////////////// QCPItemBracket
30774////////////////////////////////////////////////////////////////////////////////////////////////////
30775
30776/*! \class QCPItemBracket
30777 \brief A bracket for referencing/highlighting certain parts in the plot.
30778
30779 \image html QCPItemBracket.png "Bracket example. Blue dotted circles are anchors, solid blue discs are positions."
30780
30781 It has two positions, \a left and \a right, which define the span of the bracket. If \a left is
30782 actually farther to the left than \a right, the bracket is opened to the bottom, as shown in the
30783 example image.
30784
30785 The bracket supports multiple styles via \ref setStyle. The length, i.e. how far the bracket
30786 stretches away from the embraced span, can be controlled with \ref setLength.
30787
30788 \image html QCPItemBracket-length.png
30789 <center>Demonstrating the effect of different values for \ref setLength, for styles \ref
30790 bsCalligraphic and \ref bsSquare. Anchors and positions are displayed for reference.</center>
30791
30792 It provides an anchor \a center, to allow connection of other items, e.g. an arrow (QCPItemLine
30793 or QCPItemCurve) or a text label (QCPItemText), to the bracket.
30794*/
30795
30796/*!
30797 Creates a bracket item and sets default values.
30798
30799 The created item is automatically registered with \a parentPlot. This QCustomPlot instance takes
30800 ownership of the item, so do not delete it manually but use QCustomPlot::removeItem() instead.
30801*/
30803 QCPAbstractItem(parentPlot),
30804 left(createPosition(QLatin1String("left"))),
30805 right(createPosition(QLatin1String("right"))),
30806 center(createAnchor(QLatin1String("center"), aiCenter)),
30807 mLength(8),
30808 mStyle(bsCalligraphic)
30809{
30810 left->setCoords(0, 0);
30811 right->setCoords(1, 1);
30812
30815}
30816
30817QCPItemBracket::~QCPItemBracket()
30818{
30819}
30820
30821/*!
30822 Sets the pen that will be used to draw the bracket.
30823
30824 Note that when the style is \ref bsCalligraphic, only the color will be taken from the pen, the
30825 stroke and width are ignored. To change the apparent stroke width of a calligraphic bracket, use
30826 \ref setLength, which has a similar effect.
30827
30828 \see setSelectedPen
30829*/
30831{
30832 mPen = pen;
30833}
30834
30835/*!
30836 Sets the pen that will be used to draw the bracket when selected
30837
30838 \see setPen, setSelected
30839*/
30841{
30842 mSelectedPen = pen;
30843}
30844
30845/*!
30846 Sets the \a length in pixels how far the bracket extends in the direction towards the embraced
30847 span of the bracket (i.e. perpendicular to the <i>left</i>-<i>right</i>-direction)
30848
30849 \image html QCPItemBracket-length.png
30850 <center>Demonstrating the effect of different values for \ref setLength, for styles \ref
30851 bsCalligraphic and \ref bsSquare. Anchors and positions are displayed for reference.</center>
30852*/
30853void QCPItemBracket::setLength(double length)
30854{
30855 mLength = length;
30856}
30857
30858/*!
30859 Sets the style of the bracket, i.e. the shape/visual appearance.
30860
30861 \see setPen
30862*/
30864{
30865 mStyle = style;
30866}
30867
30868/* inherits documentation from base class */
30869double QCPItemBracket::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
30870{
30871 Q_UNUSED(details)
30872 if (onlySelectable && !mSelectable)
30873 return -1;
30874
30875 QCPVector2D p(pos);
30876 QCPVector2D leftVec(left->pixelPosition());
30877 QCPVector2D rightVec(right->pixelPosition());
30878 if (leftVec.toPoint() == rightVec.toPoint())
30879 return -1;
30880
30881 QCPVector2D widthVec = (rightVec-leftVec)*0.5;
30882 QCPVector2D lengthVec = widthVec.perpendicular().normalized()*mLength;
30883 QCPVector2D centerVec = (rightVec+leftVec)*0.5-lengthVec;
30884
30885 switch (mStyle)
30886 {
30889 {
30890 double a = p.distanceSquaredToLine(centerVec-widthVec, centerVec+widthVec);
30891 double b = p.distanceSquaredToLine(centerVec-widthVec+lengthVec, centerVec-widthVec);
30892 double c = p.distanceSquaredToLine(centerVec+widthVec+lengthVec, centerVec+widthVec);
30893 return qSqrt(qMin(qMin(a, b), c));
30894 }
30897 {
30898 double a = p.distanceSquaredToLine(centerVec-widthVec*0.75+lengthVec*0.15, centerVec+lengthVec*0.3);
30899 double b = p.distanceSquaredToLine(centerVec-widthVec+lengthVec*0.7, centerVec-widthVec*0.75+lengthVec*0.15);
30900 double c = p.distanceSquaredToLine(centerVec+widthVec*0.75+lengthVec*0.15, centerVec+lengthVec*0.3);
30901 double d = p.distanceSquaredToLine(centerVec+widthVec+lengthVec*0.7, centerVec+widthVec*0.75+lengthVec*0.15);
30902 return qSqrt(qMin(qMin(a, b), qMin(c, d)));
30903 }
30904 }
30905 return -1;
30906}
30907
30908/* inherits documentation from base class */
30910{
30911 QCPVector2D leftVec(left->pixelPosition());
30912 QCPVector2D rightVec(right->pixelPosition());
30913 if (leftVec.toPoint() == rightVec.toPoint())
30914 return;
30915
30916 QCPVector2D widthVec = (rightVec-leftVec)*0.5;
30917 QCPVector2D lengthVec = widthVec.perpendicular().normalized()*mLength;
30918 QCPVector2D centerVec = (rightVec+leftVec)*0.5-lengthVec;
30919
30920 QPolygon boundingPoly;
30921 boundingPoly << leftVec.toPoint() << rightVec.toPoint()
30922 << (rightVec-lengthVec).toPoint() << (leftVec-lengthVec).toPoint();
30923 const int clipEnlarge = qCeil(mainPen().widthF());
30924 QRect clip = clipRect().adjusted(-clipEnlarge, -clipEnlarge, clipEnlarge, clipEnlarge);
30925 if (clip.intersects(boundingPoly.boundingRect()))
30926 {
30927 painter->setPen(mainPen());
30928 switch (mStyle)
30929 {
30930 case bsSquare:
30931 {
30932 painter->drawLine((centerVec+widthVec).toPointF(), (centerVec-widthVec).toPointF());
30933 painter->drawLine((centerVec+widthVec).toPointF(), (centerVec+widthVec+lengthVec).toPointF());
30934 painter->drawLine((centerVec-widthVec).toPointF(), (centerVec-widthVec+lengthVec).toPointF());
30935 break;
30936 }
30937 case bsRound:
30938 {
30939 painter->setBrush(Qt::NoBrush);
30940 QPainterPath path;
30941 path.moveTo((centerVec+widthVec+lengthVec).toPointF());
30942 path.cubicTo((centerVec+widthVec).toPointF(), (centerVec+widthVec).toPointF(), centerVec.toPointF());
30943 path.cubicTo((centerVec-widthVec).toPointF(), (centerVec-widthVec).toPointF(), (centerVec-widthVec+lengthVec).toPointF());
30944 painter->drawPath(path);
30945 break;
30946 }
30947 case bsCurly:
30948 {
30949 painter->setBrush(Qt::NoBrush);
30950 QPainterPath path;
30951 path.moveTo((centerVec+widthVec+lengthVec).toPointF());
30952 path.cubicTo((centerVec+widthVec-lengthVec*0.8).toPointF(), (centerVec+0.4*widthVec+lengthVec).toPointF(), centerVec.toPointF());
30953 path.cubicTo((centerVec-0.4*widthVec+lengthVec).toPointF(), (centerVec-widthVec-lengthVec*0.8).toPointF(), (centerVec-widthVec+lengthVec).toPointF());
30954 painter->drawPath(path);
30955 break;
30956 }
30957 case bsCalligraphic:
30958 {
30959 painter->setPen(Qt::NoPen);
30960 painter->setBrush(QBrush(mainPen().color()));
30961 QPainterPath path;
30962 path.moveTo((centerVec+widthVec+lengthVec).toPointF());
30963
30964 path.cubicTo((centerVec+widthVec-lengthVec*0.8).toPointF(), (centerVec+0.4*widthVec+0.8*lengthVec).toPointF(), centerVec.toPointF());
30965 path.cubicTo((centerVec-0.4*widthVec+0.8*lengthVec).toPointF(), (centerVec-widthVec-lengthVec*0.8).toPointF(), (centerVec-widthVec+lengthVec).toPointF());
30966
30967 path.cubicTo((centerVec-widthVec-lengthVec*0.5).toPointF(), (centerVec-0.2*widthVec+1.2*lengthVec).toPointF(), (centerVec+lengthVec*0.2).toPointF());
30968 path.cubicTo((centerVec+0.2*widthVec+1.2*lengthVec).toPointF(), (centerVec+widthVec-lengthVec*0.5).toPointF(), (centerVec+widthVec+lengthVec).toPointF());
30969
30970 painter->drawPath(path);
30971 break;
30972 }
30973 }
30974 }
30975}
30976
30977/* inherits documentation from base class */
30979{
30980 QCPVector2D leftVec(left->pixelPosition());
30981 QCPVector2D rightVec(right->pixelPosition());
30982 if (leftVec.toPoint() == rightVec.toPoint())
30983 return leftVec.toPointF();
30984
30985 QCPVector2D widthVec = (rightVec-leftVec)*0.5;
30986 QCPVector2D lengthVec = widthVec.perpendicular().normalized()*mLength;
30987 QCPVector2D centerVec = (rightVec+leftVec)*0.5-lengthVec;
30988
30989 switch (anchorId)
30990 {
30991 case aiCenter:
30992 return centerVec.toPointF();
30993 }
30994 qDebug() << Q_FUNC_INFO << "invalid anchorId" << anchorId;
30995 return {};
30996}
30997
30998/*! \internal
30999
31000 Returns the pen that should be used for drawing lines. Returns mPen when the
31001 item is not selected and mSelectedPen when it is.
31002*/
31004{
31005 return mSelected ? mSelectedPen : mPen;
31006}
31007/* end of 'src/items/item-bracket.cpp' */
31008
31009
31010/* including file 'src/polar/radialaxis.cpp' */
31011/* modified 2022-11-06T12:45:57, size 49415 */
31012
31013
31014
31015////////////////////////////////////////////////////////////////////////////////////////////////////
31016//////////////////// QCPPolarAxisRadial
31017////////////////////////////////////////////////////////////////////////////////////////////////////
31018
31019/*! \class QCPPolarAxisRadial
31020 \brief The radial axis inside a radial plot
31021
31022 \warning In this QCustomPlot version, polar plots are a tech preview. Expect documentation and
31023 functionality to be incomplete, as well as changing public interfaces in the future.
31024
31025 Each axis holds an instance of QCPAxisTicker which is used to generate the tick coordinates and
31026 tick labels. You can access the currently installed \ref ticker or set a new one (possibly one of
31027 the specialized subclasses, or your own subclass) via \ref setTicker. For details, see the
31028 documentation of QCPAxisTicker.
31029*/
31030
31031/* start of documentation of inline functions */
31032
31033/*! \fn QSharedPointer<QCPAxisTicker> QCPPolarAxisRadial::ticker() const
31034
31035 Returns a modifiable shared pointer to the currently installed axis ticker. The axis ticker is
31036 responsible for generating the tick positions and tick labels of this axis. You can access the
31037 \ref QCPAxisTicker with this method and modify basic properties such as the approximate tick count
31038 (\ref QCPAxisTicker::setTickCount).
31039
31040 You can gain more control over the axis ticks by setting a different \ref QCPAxisTicker subclass, see
31041 the documentation there. A new axis ticker can be set with \ref setTicker.
31042
31043 Since the ticker is stored in the axis as a shared pointer, multiple axes may share the same axis
31044 ticker simply by passing the same shared pointer to multiple axes.
31045
31046 \see setTicker
31047*/
31048
31049/* end of documentation of inline functions */
31050/* start of documentation of signals */
31051
31052/*! \fn void QCPPolarAxisRadial::rangeChanged(const QCPRange &newRange)
31053
31054 This signal is emitted when the range of this axis has changed. You can connect it to the \ref
31055 setRange slot of another axis to communicate the new range to the other axis, in order for it to
31056 be synchronized.
31057
31058 You may also manipulate/correct the range with \ref setRange in a slot connected to this signal.
31059 This is useful if for example a maximum range span shall not be exceeded, or if the lower/upper
31060 range shouldn't go beyond certain values (see \ref QCPRange::bounded). For example, the following
31061 slot would limit the x axis to ranges between 0 and 10:
31062 \code
31063 customPlot->xAxis->setRange(newRange.bounded(0, 10))
31064 \endcode
31065*/
31066
31067/*! \fn void QCPPolarAxisRadial::rangeChanged(const QCPRange &newRange, const QCPRange &oldRange)
31068 \overload
31069
31070 Additionally to the new range, this signal also provides the previous range held by the axis as
31071 \a oldRange.
31072*/
31073
31074/*! \fn void QCPPolarAxisRadial::scaleTypeChanged(QCPPolarAxisRadial::ScaleType scaleType);
31075
31076 This signal is emitted when the scale type changes, by calls to \ref setScaleType
31077*/
31078
31079/*! \fn void QCPPolarAxisRadial::selectionChanged(QCPPolarAxisRadial::SelectableParts selection)
31080
31081 This signal is emitted when the selection state of this axis has changed, either by user interaction
31082 or by a direct call to \ref setSelectedParts.
31083*/
31084
31085/*! \fn void QCPPolarAxisRadial::selectableChanged(const QCPPolarAxisRadial::SelectableParts &parts);
31086
31087 This signal is emitted when the selectability changes, by calls to \ref setSelectableParts
31088*/
31089
31090/* end of documentation of signals */
31091
31092/*!
31093 Constructs an Axis instance of Type \a type for the axis rect \a parent.
31094
31095 Usually it isn't necessary to instantiate axes directly, because you can let QCustomPlot create
31096 them for you with \ref QCPAxisRect::addAxis. If you want to use own QCPAxis-subclasses however,
31097 create them manually and then inject them also via \ref QCPAxisRect::addAxis.
31098*/
31100 QCPLayerable(parent->parentPlot(), QString(), parent),
31101 mRangeDrag(true),
31102 mRangeZoom(true),
31103 mRangeZoomFactor(0.85),
31104 // axis base:
31105 mAngularAxis(parent),
31106 mAngle(45),
31107 mAngleReference(arAngularAxis),
31108 mSelectableParts(spAxis | spTickLabels | spAxisLabel),
31109 mSelectedParts(spNone),
31110 mBasePen(QPen(Qt::black, 0, Qt::SolidLine, Qt::SquareCap)),
31111 mSelectedBasePen(QPen(Qt::blue, 2)),
31112 // axis label:
31113 mLabelPadding(0),
31114 mLabel(),
31115 mLabelFont(mParentPlot->font()),
31116 mSelectedLabelFont(QFont(mLabelFont.family(), mLabelFont.pointSize(), QFont::Bold)),
31117 mLabelColor(Qt::black),
31118 mSelectedLabelColor(Qt::blue),
31119 // tick labels:
31120 // mTickLabelPadding(0), in label painter
31121 mTickLabels(true),
31122 // mTickLabelRotation(0), in label painter
31123 mTickLabelFont(mParentPlot->font()),
31124 mSelectedTickLabelFont(QFont(mTickLabelFont.family(), mTickLabelFont.pointSize(), QFont::Bold)),
31125 mTickLabelColor(Qt::black),
31126 mSelectedTickLabelColor(Qt::blue),
31127 mNumberPrecision(6),
31128 mNumberFormatChar('g'),
31129 mNumberBeautifulPowers(true),
31130 mNumberMultiplyCross(false),
31131 // ticks and subticks:
31132 mTicks(true),
31133 mSubTicks(true),
31134 mTickLengthIn(5),
31135 mTickLengthOut(0),
31136 mSubTickLengthIn(2),
31137 mSubTickLengthOut(0),
31138 mTickPen(QPen(Qt::black, 0, Qt::SolidLine, Qt::SquareCap)),
31139 mSelectedTickPen(QPen(Qt::blue, 2)),
31140 mSubTickPen(QPen(Qt::black, 0, Qt::SolidLine, Qt::SquareCap)),
31141 mSelectedSubTickPen(QPen(Qt::blue, 2)),
31142 // scale and range:
31143 mRange(0, 5),
31144 mRangeReversed(false),
31145 mScaleType(stLinear),
31146 // internal members:
31147 mRadius(1), // non-zero initial value, will be overwritten in ::update() according to inner rect
31148 mTicker(new QCPAxisTicker),
31149 mLabelPainter(mParentPlot)
31150{
31152 setAntialiased(true);
31153
31156 setTickLabelMode(lmUpright);
31157 mLabelPainter.setAnchorReferenceType(QCPLabelPainterPrivate::artTangent);
31158 mLabelPainter.setAbbreviateDecimalPowers(false);
31159}
31160
31161QCPPolarAxisRadial::~QCPPolarAxisRadial()
31162{
31163}
31164
31165QCPPolarAxisRadial::LabelMode QCPPolarAxisRadial::tickLabelMode() const
31166{
31167 switch (mLabelPainter.anchorMode())
31168 {
31169 case QCPLabelPainterPrivate::amSkewedUpright: return lmUpright;
31170 case QCPLabelPainterPrivate::amSkewedRotated: return lmRotated;
31171 default: qDebug() << Q_FUNC_INFO << "invalid mode for polar axis"; break;
31172 }
31173 return lmUpright;
31174}
31175
31176/* No documentation as it is a property getter */
31177QString QCPPolarAxisRadial::numberFormat() const
31178{
31179 QString result;
31180 result.append(mNumberFormatChar);
31181 if (mNumberBeautifulPowers)
31182 {
31183 result.append(QLatin1Char('b'));
31184 if (mNumberMultiplyCross)
31185 result.append(QLatin1Char('c'));
31186 }
31187 return result;
31188}
31189
31190/* No documentation as it is a property getter */
31191int QCPPolarAxisRadial::tickLengthIn() const
31192{
31193 return mTickLengthIn;
31194}
31195
31196/* No documentation as it is a property getter */
31197int QCPPolarAxisRadial::tickLengthOut() const
31198{
31199 return mTickLengthOut;
31200}
31201
31202/* No documentation as it is a property getter */
31203int QCPPolarAxisRadial::subTickLengthIn() const
31204{
31205 return mSubTickLengthIn;
31206}
31207
31208/* No documentation as it is a property getter */
31209int QCPPolarAxisRadial::subTickLengthOut() const
31210{
31211 return mSubTickLengthOut;
31212}
31213
31214/* No documentation as it is a property getter */
31215int QCPPolarAxisRadial::labelPadding() const
31216{
31217 return mLabelPadding;
31218}
31219
31220void QCPPolarAxisRadial::setRangeDrag(bool enabled)
31221{
31222 mRangeDrag = enabled;
31223}
31224
31225void QCPPolarAxisRadial::setRangeZoom(bool enabled)
31226{
31227 mRangeZoom = enabled;
31228}
31229
31230void QCPPolarAxisRadial::setRangeZoomFactor(double factor)
31231{
31232 mRangeZoomFactor = factor;
31233}
31234
31235/*!
31236 Sets whether the axis uses a linear scale or a logarithmic scale.
31237
31238 Note that this method controls the coordinate transformation. For logarithmic scales, you will
31239 likely also want to use a logarithmic tick spacing and labeling, which can be achieved by setting
31240 the axis ticker to an instance of \ref QCPAxisTickerLog :
31241
31242 \snippet documentation/doc-code-snippets/mainwindow.cpp qcpaxisticker-log-creation
31243
31244 See the documentation of \ref QCPAxisTickerLog about the details of logarithmic axis tick
31245 creation.
31246
31247 \ref setNumberPrecision
31248*/
31250{
31251 if (mScaleType != type)
31252 {
31253 mScaleType = type;
31254 if (mScaleType == stLogarithmic)
31256 //mCachedMarginValid = false;
31257 emit scaleTypeChanged(mScaleType);
31258 }
31259}
31260
31261/*!
31262 Sets the range of the axis.
31263
31264 This slot may be connected with the \ref rangeChanged signal of another axis so this axis
31265 is always synchronized with the other axis range, when it changes.
31266
31267 To invert the direction of an axis, use \ref setRangeReversed.
31268*/
31270{
31271 if (range.lower == mRange.lower && range.upper == mRange.upper)
31272 return;
31273
31274 if (!QCPRange::validRange(range)) return;
31275 QCPRange oldRange = mRange;
31276 if (mScaleType == stLogarithmic)
31277 {
31278 mRange = range.sanitizedForLogScale();
31279 } else
31280 {
31281 mRange = range.sanitizedForLinScale();
31282 }
31283 emit rangeChanged(mRange);
31284 emit rangeChanged(mRange, oldRange);
31285}
31286
31287/*!
31288 Sets whether the user can (de-)select the parts in \a selectable by clicking on the QCustomPlot surface.
31289 (When \ref QCustomPlot::setInteractions contains iSelectAxes.)
31290
31291 However, even when \a selectable is set to a value not allowing the selection of a specific part,
31292 it is still possible to set the selection of this part manually, by calling \ref setSelectedParts
31293 directly.
31294
31295 \see SelectablePart, setSelectedParts
31296*/
31298{
31299 if (mSelectableParts != selectable)
31300 {
31301 mSelectableParts = selectable;
31302 emit selectableChanged(mSelectableParts);
31303 }
31304}
31305
31306/*!
31307 Sets the selected state of the respective axis parts described by \ref SelectablePart. When a part
31308 is selected, it uses a different pen/font.
31309
31310 The entire selection mechanism for axes is handled automatically when \ref
31311 QCustomPlot::setInteractions contains iSelectAxes. You only need to call this function when you
31312 wish to change the selection state manually.
31313
31314 This function can change the selection state of a part, independent of the \ref setSelectableParts setting.
31315
31316 emits the \ref selectionChanged signal when \a selected is different from the previous selection state.
31317
31318 \see SelectablePart, setSelectableParts, selectTest, setSelectedBasePen, setSelectedTickPen, setSelectedSubTickPen,
31319 setSelectedTickLabelFont, setSelectedLabelFont, setSelectedTickLabelColor, setSelectedLabelColor
31320*/
31322{
31323 if (mSelectedParts != selected)
31324 {
31325 mSelectedParts = selected;
31326 emit selectionChanged(mSelectedParts);
31327 }
31328}
31329
31330/*!
31331 \overload
31332
31333 Sets the lower and upper bound of the axis range.
31334
31335 To invert the direction of an axis, use \ref setRangeReversed.
31336
31337 There is also a slot to set a range, see \ref setRange(const QCPRange &range).
31338*/
31339void QCPPolarAxisRadial::setRange(double lower, double upper)
31340{
31341 if (lower == mRange.lower && upper == mRange.upper)
31342 return;
31343
31344 if (!QCPRange::validRange(lower, upper)) return;
31345 QCPRange oldRange = mRange;
31346 mRange.lower = lower;
31347 mRange.upper = upper;
31348 if (mScaleType == stLogarithmic)
31349 {
31350 mRange = mRange.sanitizedForLogScale();
31351 } else
31352 {
31353 mRange = mRange.sanitizedForLinScale();
31354 }
31355 emit rangeChanged(mRange);
31356 emit rangeChanged(mRange, oldRange);
31357}
31358
31359/*!
31360 \overload
31361
31362 Sets the range of the axis.
31363
31364 The \a position coordinate indicates together with the \a alignment parameter, where the new
31365 range will be positioned. \a size defines the size of the new axis range. \a alignment may be
31366 Qt::AlignLeft, Qt::AlignRight or Qt::AlignCenter. This will cause the left border, right border,
31367 or center of the range to be aligned with \a position. Any other values of \a alignment will
31368 default to Qt::AlignCenter.
31369*/
31370void QCPPolarAxisRadial::setRange(double position, double size, Qt::AlignmentFlag alignment)
31371{
31372 if (alignment == Qt::AlignLeft)
31373 setRange(position, position+size);
31374 else if (alignment == Qt::AlignRight)
31375 setRange(position-size, position);
31376 else // alignment == Qt::AlignCenter
31377 setRange(position-size/2.0, position+size/2.0);
31378}
31379
31380/*!
31381 Sets the lower bound of the axis range. The upper bound is not changed.
31382 \see setRange
31383*/
31385{
31386 if (mRange.lower == lower)
31387 return;
31388
31389 QCPRange oldRange = mRange;
31390 mRange.lower = lower;
31391 if (mScaleType == stLogarithmic)
31392 {
31393 mRange = mRange.sanitizedForLogScale();
31394 } else
31395 {
31396 mRange = mRange.sanitizedForLinScale();
31397 }
31398 emit rangeChanged(mRange);
31399 emit rangeChanged(mRange, oldRange);
31400}
31401
31402/*!
31403 Sets the upper bound of the axis range. The lower bound is not changed.
31404 \see setRange
31405*/
31407{
31408 if (mRange.upper == upper)
31409 return;
31410
31411 QCPRange oldRange = mRange;
31412 mRange.upper = upper;
31413 if (mScaleType == stLogarithmic)
31414 {
31415 mRange = mRange.sanitizedForLogScale();
31416 } else
31417 {
31418 mRange = mRange.sanitizedForLinScale();
31419 }
31420 emit rangeChanged(mRange);
31421 emit rangeChanged(mRange, oldRange);
31422}
31423
31424/*!
31425 Sets whether the axis range (direction) is displayed reversed. Normally, the values on horizontal
31426 axes increase left to right, on vertical axes bottom to top. When \a reversed is set to true, the
31427 direction of increasing values is inverted.
31428
31429 Note that the range and data interface stays the same for reversed axes, e.g. the \a lower part
31430 of the \ref setRange interface will still reference the mathematically smaller number than the \a
31431 upper part.
31432*/
31434{
31435 mRangeReversed = reversed;
31436}
31437
31438void QCPPolarAxisRadial::setAngle(double degrees)
31439{
31440 mAngle = degrees;
31441}
31442
31443void QCPPolarAxisRadial::setAngleReference(AngleReference reference)
31444{
31445 mAngleReference = reference;
31446}
31447
31448/*!
31449 The axis ticker is responsible for generating the tick positions and tick labels. See the
31450 documentation of QCPAxisTicker for details on how to work with axis tickers.
31451
31452 You can change the tick positioning/labeling behaviour of this axis by setting a different
31453 QCPAxisTicker subclass using this method. If you only wish to modify the currently installed axis
31454 ticker, access it via \ref ticker.
31455
31456 Since the ticker is stored in the axis as a shared pointer, multiple axes may share the same axis
31457 ticker simply by passing the same shared pointer to multiple axes.
31458
31459 \see ticker
31460*/
31462{
31463 if (ticker)
31464 mTicker = ticker;
31465 else
31466 qDebug() << Q_FUNC_INFO << "can not set 0 as axis ticker";
31467 // no need to invalidate margin cache here because produced tick labels are checked for changes in setupTickVector
31468}
31469
31470/*!
31471 Sets whether tick marks are displayed.
31472
31473 Note that setting \a show to false does not imply that tick labels are invisible, too. To achieve
31474 that, see \ref setTickLabels.
31475
31476 \see setSubTicks
31477*/
31479{
31480 if (mTicks != show)
31481 {
31482 mTicks = show;
31483 //mCachedMarginValid = false;
31484 }
31485}
31486
31487/*!
31488 Sets whether tick labels are displayed. Tick labels are the numbers drawn next to tick marks.
31489*/
31491{
31492 if (mTickLabels != show)
31493 {
31494 mTickLabels = show;
31495 //mCachedMarginValid = false;
31496 if (!mTickLabels)
31497 mTickVectorLabels.clear();
31498 }
31499}
31500
31501/*!
31502 Sets the distance between the axis base line (including any outward ticks) and the tick labels.
31503 \see setLabelPadding, setPadding
31504*/
31506{
31507 mLabelPainter.setPadding(padding);
31508}
31509
31510/*!
31511 Sets the font of the tick labels.
31512
31513 \see setTickLabels, setTickLabelColor
31514*/
31516{
31517 if (font != mTickLabelFont)
31518 {
31519 mTickLabelFont = font;
31520 //mCachedMarginValid = false;
31521 }
31522}
31523
31524/*!
31525 Sets the color of the tick labels.
31526
31527 \see setTickLabels, setTickLabelFont
31528*/
31530{
31531 mTickLabelColor = color;
31532}
31533
31534/*!
31535 Sets the rotation of the tick labels. If \a degrees is zero, the labels are drawn normally. Else,
31536 the tick labels are drawn rotated by \a degrees clockwise. The specified angle is bound to values
31537 from -90 to 90 degrees.
31538
31539 If \a degrees is exactly -90, 0 or 90, the tick labels are centered on the tick coordinate. For
31540 other angles, the label is drawn with an offset such that it seems to point toward or away from
31541 the tick mark.
31542*/
31544{
31545 mLabelPainter.setRotation(degrees);
31546}
31547
31548void QCPPolarAxisRadial::setTickLabelMode(LabelMode mode)
31549{
31550 switch (mode)
31551 {
31552 case lmUpright: mLabelPainter.setAnchorMode(QCPLabelPainterPrivate::amSkewedUpright); break;
31553 case lmRotated: mLabelPainter.setAnchorMode(QCPLabelPainterPrivate::amSkewedRotated); break;
31554 }
31555}
31556
31557/*!
31558 Sets the number format for the numbers in tick labels. This \a formatCode is an extended version
31559 of the format code used e.g. by QString::number() and QLocale::toString(). For reference about
31560 that, see the "Argument Formats" section in the detailed description of the QString class.
31561
31562 \a formatCode is a string of one, two or three characters. The first character is identical to
31563 the normal format code used by Qt. In short, this means: 'e'/'E' scientific format, 'f' fixed
31564 format, 'g'/'G' scientific or fixed, whichever is shorter.
31565
31566 The second and third characters are optional and specific to QCustomPlot:\n
31567 If the first char was 'e' or 'g', numbers are/might be displayed in the scientific format, e.g.
31568 "5.5e9", which is ugly in a plot. So when the second char of \a formatCode is set to 'b' (for
31569 "beautiful"), those exponential numbers are formatted in a more natural way, i.e. "5.5
31570 [multiplication sign] 10 [superscript] 9". By default, the multiplication sign is a centered dot.
31571 If instead a cross should be shown (as is usual in the USA), the third char of \a formatCode can
31572 be set to 'c'. The inserted multiplication signs are the UTF-8 characters 215 (0xD7) for the
31573 cross and 183 (0xB7) for the dot.
31574
31575 Examples for \a formatCode:
31576 \li \c g normal format code behaviour. If number is small, fixed format is used, if number is large,
31577 normal scientific format is used
31578 \li \c gb If number is small, fixed format is used, if number is large, scientific format is used with
31579 beautifully typeset decimal powers and a dot as multiplication sign
31580 \li \c ebc All numbers are in scientific format with beautifully typeset decimal power and a cross as
31581 multiplication sign
31582 \li \c fb illegal format code, since fixed format doesn't support (or need) beautifully typeset decimal
31583 powers. Format code will be reduced to 'f'.
31584 \li \c hello illegal format code, since first char is not 'e', 'E', 'f', 'g' or 'G'. Current format
31585 code will not be changed.
31586*/
31588{
31589 if (formatCode.isEmpty())
31590 {
31591 qDebug() << Q_FUNC_INFO << "Passed formatCode is empty";
31592 return;
31593 }
31594 //mCachedMarginValid = false;
31595
31596 // interpret first char as number format char:
31597 QString allowedFormatChars(QLatin1String("eEfgG"));
31598 if (allowedFormatChars.contains(formatCode.at(0)))
31599 {
31600 mNumberFormatChar = QLatin1Char(formatCode.at(0).toLatin1());
31601 } else
31602 {
31603 qDebug() << Q_FUNC_INFO << "Invalid number format code (first char not in 'eEfgG'):" << formatCode;
31604 return;
31605 }
31606
31607 if (formatCode.length() < 2)
31608 {
31609 mNumberBeautifulPowers = false;
31610 mNumberMultiplyCross = false;
31611 } else
31612 {
31613 // interpret second char as indicator for beautiful decimal powers:
31614 if (formatCode.at(1) == QLatin1Char('b') && (mNumberFormatChar == QLatin1Char('e') || mNumberFormatChar == QLatin1Char('g')))
31615 mNumberBeautifulPowers = true;
31616 else
31617 qDebug() << Q_FUNC_INFO << "Invalid number format code (second char not 'b' or first char neither 'e' nor 'g'):" << formatCode;
31618
31619 if (formatCode.length() < 3)
31620 {
31621 mNumberMultiplyCross = false;
31622 } else
31623 {
31624 // interpret third char as indicator for dot or cross multiplication symbol:
31625 if (formatCode.at(2) == QLatin1Char('c'))
31626 mNumberMultiplyCross = true;
31627 else if (formatCode.at(2) == QLatin1Char('d'))
31628 mNumberMultiplyCross = false;
31629 else
31630 qDebug() << Q_FUNC_INFO << "Invalid number format code (third char neither 'c' nor 'd'):" << formatCode;
31631 }
31632 }
31633 mLabelPainter.setSubstituteExponent(mNumberBeautifulPowers);
31634 mLabelPainter.setMultiplicationSymbol(mNumberMultiplyCross ? QCPLabelPainterPrivate::SymbolCross : QCPLabelPainterPrivate::SymbolDot);
31635}
31636
31637/*!
31638 Sets the precision of the tick label numbers. See QLocale::toString(double i, char f, int prec)
31639 for details. The effect of precisions are most notably for number Formats starting with 'e', see
31640 \ref setNumberFormat
31641*/
31643{
31644 if (mNumberPrecision != precision)
31645 {
31646 mNumberPrecision = precision;
31647 //mCachedMarginValid = false;
31648 }
31649}
31650
31651/*!
31652 Sets the length of the ticks in pixels. \a inside is the length the ticks will reach inside the
31653 plot and \a outside is the length they will reach outside the plot. If \a outside is greater than
31654 zero, the tick labels and axis label will increase their distance to the axis accordingly, so
31655 they won't collide with the ticks.
31656
31657 \see setSubTickLength, setTickLengthIn, setTickLengthOut
31658*/
31659void QCPPolarAxisRadial::setTickLength(int inside, int outside)
31660{
31661 setTickLengthIn(inside);
31662 setTickLengthOut(outside);
31663}
31664
31665/*!
31666 Sets the length of the inward ticks in pixels. \a inside is the length the ticks will reach
31667 inside the plot.
31668
31669 \see setTickLengthOut, setTickLength, setSubTickLength
31670*/
31672{
31673 if (mTickLengthIn != inside)
31674 {
31675 mTickLengthIn = inside;
31676 }
31677}
31678
31679/*!
31680 Sets the length of the outward ticks in pixels. \a outside is the length the ticks will reach
31681 outside the plot. If \a outside is greater than zero, the tick labels and axis label will
31682 increase their distance to the axis accordingly, so they won't collide with the ticks.
31683
31684 \see setTickLengthIn, setTickLength, setSubTickLength
31685*/
31687{
31688 if (mTickLengthOut != outside)
31689 {
31690 mTickLengthOut = outside;
31691 //mCachedMarginValid = false; // only outside tick length can change margin
31692 }
31693}
31694
31695/*!
31696 Sets whether sub tick marks are displayed.
31697
31698 Sub ticks are only potentially visible if (major) ticks are also visible (see \ref setTicks)
31699
31700 \see setTicks
31701*/
31703{
31704 if (mSubTicks != show)
31705 {
31706 mSubTicks = show;
31707 //mCachedMarginValid = false;
31708 }
31709}
31710
31711/*!
31712 Sets the length of the subticks in pixels. \a inside is the length the subticks will reach inside
31713 the plot and \a outside is the length they will reach outside the plot. If \a outside is greater
31714 than zero, the tick labels and axis label will increase their distance to the axis accordingly,
31715 so they won't collide with the ticks.
31716
31717 \see setTickLength, setSubTickLengthIn, setSubTickLengthOut
31718*/
31719void QCPPolarAxisRadial::setSubTickLength(int inside, int outside)
31720{
31721 setSubTickLengthIn(inside);
31722 setSubTickLengthOut(outside);
31723}
31724
31725/*!
31726 Sets the length of the inward subticks in pixels. \a inside is the length the subticks will reach inside
31727 the plot.
31728
31729 \see setSubTickLengthOut, setSubTickLength, setTickLength
31730*/
31732{
31733 if (mSubTickLengthIn != inside)
31734 {
31735 mSubTickLengthIn = inside;
31736 }
31737}
31738
31739/*!
31740 Sets the length of the outward subticks in pixels. \a outside is the length the subticks will reach
31741 outside the plot. If \a outside is greater than zero, the tick labels will increase their
31742 distance to the axis accordingly, so they won't collide with the ticks.
31743
31744 \see setSubTickLengthIn, setSubTickLength, setTickLength
31745*/
31747{
31748 if (mSubTickLengthOut != outside)
31749 {
31750 mSubTickLengthOut = outside;
31751 //mCachedMarginValid = false; // only outside tick length can change margin
31752 }
31753}
31754
31755/*!
31756 Sets the pen, the axis base line is drawn with.
31757
31758 \see setTickPen, setSubTickPen
31759*/
31761{
31762 mBasePen = pen;
31763}
31764
31765/*!
31766 Sets the pen, tick marks will be drawn with.
31767
31768 \see setTickLength, setBasePen
31769*/
31771{
31772 mTickPen = pen;
31773}
31774
31775/*!
31776 Sets the pen, subtick marks will be drawn with.
31777
31778 \see setSubTickCount, setSubTickLength, setBasePen
31779*/
31781{
31782 mSubTickPen = pen;
31783}
31784
31785/*!
31786 Sets the font of the axis label.
31787
31788 \see setLabelColor
31789*/
31791{
31792 if (mLabelFont != font)
31793 {
31794 mLabelFont = font;
31795 //mCachedMarginValid = false;
31796 }
31797}
31798
31799/*!
31800 Sets the color of the axis label.
31801
31802 \see setLabelFont
31803*/
31805{
31806 mLabelColor = color;
31807}
31808
31809/*!
31810 Sets the text of the axis label that will be shown below/above or next to the axis, depending on
31811 its orientation. To disable axis labels, pass an empty string as \a str.
31812*/
31814{
31815 if (mLabel != str)
31816 {
31817 mLabel = str;
31818 //mCachedMarginValid = false;
31819 }
31820}
31821
31822/*!
31823 Sets the distance between the tick labels and the axis label.
31824
31825 \see setTickLabelPadding, setPadding
31826*/
31828{
31829 if (mLabelPadding != padding)
31830 {
31831 mLabelPadding = padding;
31832 //mCachedMarginValid = false;
31833 }
31834}
31835
31836/*!
31837 Sets the font that is used for tick labels when they are selected.
31838
31839 \see setTickLabelFont, setSelectableParts, setSelectedParts, QCustomPlot::setInteractions
31840*/
31842{
31843 if (font != mSelectedTickLabelFont)
31844 {
31845 mSelectedTickLabelFont = font;
31846 // don't set mCachedMarginValid to false here because margin calculation is always done with non-selected fonts
31847 }
31848}
31849
31850/*!
31851 Sets the font that is used for the axis label when it is selected.
31852
31853 \see setLabelFont, setSelectableParts, setSelectedParts, QCustomPlot::setInteractions
31854*/
31856{
31857 mSelectedLabelFont = font;
31858 // don't set mCachedMarginValid to false here because margin calculation is always done with non-selected fonts
31859}
31860
31861/*!
31862 Sets the color that is used for tick labels when they are selected.
31863
31864 \see setTickLabelColor, setSelectableParts, setSelectedParts, QCustomPlot::setInteractions
31865*/
31867{
31868 if (color != mSelectedTickLabelColor)
31869 {
31870 mSelectedTickLabelColor = color;
31871 }
31872}
31873
31874/*!
31875 Sets the color that is used for the axis label when it is selected.
31876
31877 \see setLabelColor, setSelectableParts, setSelectedParts, QCustomPlot::setInteractions
31878*/
31880{
31881 mSelectedLabelColor = color;
31882}
31883
31884/*!
31885 Sets the pen that is used to draw the axis base line when selected.
31886
31887 \see setBasePen, setSelectableParts, setSelectedParts, QCustomPlot::setInteractions
31888*/
31890{
31891 mSelectedBasePen = pen;
31892}
31893
31894/*!
31895 Sets the pen that is used to draw the (major) ticks when selected.
31896
31897 \see setTickPen, setSelectableParts, setSelectedParts, QCustomPlot::setInteractions
31898*/
31900{
31901 mSelectedTickPen = pen;
31902}
31903
31904/*!
31905 Sets the pen that is used to draw the subticks when selected.
31906
31907 \see setSubTickPen, setSelectableParts, setSelectedParts, QCustomPlot::setInteractions
31908*/
31910{
31911 mSelectedSubTickPen = pen;
31912}
31913
31914/*!
31915 If the scale type (\ref setScaleType) is \ref stLinear, \a diff is added to the lower and upper
31916 bounds of the range. The range is simply moved by \a diff.
31917
31918 If the scale type is \ref stLogarithmic, the range bounds are multiplied by \a diff. This
31919 corresponds to an apparent "linear" move in logarithmic scaling by a distance of log(diff).
31920*/
31922{
31923 QCPRange oldRange = mRange;
31924 if (mScaleType == stLinear)
31925 {
31926 mRange.lower += diff;
31927 mRange.upper += diff;
31928 } else // mScaleType == stLogarithmic
31929 {
31930 mRange.lower *= diff;
31931 mRange.upper *= diff;
31932 }
31933 emit rangeChanged(mRange);
31934 emit rangeChanged(mRange, oldRange);
31935}
31936
31937/*!
31938 Scales the range of this axis by \a factor around the center of the current axis range. For
31939 example, if \a factor is 2.0, then the axis range will double its size, and the point at the axis
31940 range center won't have changed its position in the QCustomPlot widget (i.e. coordinates around
31941 the center will have moved symmetrically closer).
31942
31943 If you wish to scale around a different coordinate than the current axis range center, use the
31944 overload \ref scaleRange(double factor, double center).
31945*/
31947{
31948 scaleRange(factor, range().center());
31949}
31950
31951/*! \overload
31952
31953 Scales the range of this axis by \a factor around the coordinate \a center. For example, if \a
31954 factor is 2.0, \a center is 1.0, then the axis range will double its size, and the point at
31955 coordinate 1.0 won't have changed its position in the QCustomPlot widget (i.e. coordinates
31956 around 1.0 will have moved symmetrically closer to 1.0).
31957
31958 \see scaleRange(double factor)
31959*/
31960void QCPPolarAxisRadial::scaleRange(double factor, double center)
31961{
31962 QCPRange oldRange = mRange;
31963 if (mScaleType == stLinear)
31964 {
31965 QCPRange newRange;
31966 newRange.lower = (mRange.lower-center)*factor + center;
31967 newRange.upper = (mRange.upper-center)*factor + center;
31968 if (QCPRange::validRange(newRange))
31969 mRange = newRange.sanitizedForLinScale();
31970 } else // mScaleType == stLogarithmic
31971 {
31972 if ((mRange.upper < 0 && center < 0) || (mRange.upper > 0 && center > 0)) // make sure center has same sign as range
31973 {
31974 QCPRange newRange;
31975 newRange.lower = qPow(mRange.lower/center, factor)*center;
31976 newRange.upper = qPow(mRange.upper/center, factor)*center;
31977 if (QCPRange::validRange(newRange))
31978 mRange = newRange.sanitizedForLogScale();
31979 } else
31980 qDebug() << Q_FUNC_INFO << "Center of scaling operation doesn't lie in same logarithmic sign domain as range:" << center;
31981 }
31982 emit rangeChanged(mRange);
31983 emit rangeChanged(mRange, oldRange);
31984}
31985
31986/*!
31987 Changes the axis range such that all plottables associated with this axis are fully visible in
31988 that dimension.
31989
31990 \see QCPAbstractPlottable::rescaleAxes, QCustomPlot::rescaleAxes
31991*/
31992void QCPPolarAxisRadial::rescale(bool onlyVisiblePlottables)
31993{
31994 Q_UNUSED(onlyVisiblePlottables)
31995 /* TODO
31996 QList<QCPAbstractPlottable*> p = plottables();
31997 QCPRange newRange;
31998 bool haveRange = false;
31999 for (int i=0; i<p.size(); ++i)
32000 {
32001 if (!p.at(i)->realVisibility() && onlyVisiblePlottables)
32002 continue;
32003 QCPRange plottableRange;
32004 bool currentFoundRange;
32005 QCP::SignDomain signDomain = QCP::sdBoth;
32006 if (mScaleType == stLogarithmic)
32007 signDomain = (mRange.upper < 0 ? QCP::sdNegative : QCP::sdPositive);
32008 if (p.at(i)->keyAxis() == this)
32009 plottableRange = p.at(i)->getKeyRange(currentFoundRange, signDomain);
32010 else
32011 plottableRange = p.at(i)->getValueRange(currentFoundRange, signDomain);
32012 if (currentFoundRange)
32013 {
32014 if (!haveRange)
32015 newRange = plottableRange;
32016 else
32017 newRange.expand(plottableRange);
32018 haveRange = true;
32019 }
32020 }
32021 if (haveRange)
32022 {
32023 if (!QCPRange::validRange(newRange)) // likely due to range being zero (plottable has only constant data in this axis dimension), shift current range to at least center the plottable
32024 {
32025 double center = (newRange.lower+newRange.upper)*0.5; // upper and lower should be equal anyway, but just to make sure, incase validRange returned false for other reason
32026 if (mScaleType == stLinear)
32027 {
32028 newRange.lower = center-mRange.size()/2.0;
32029 newRange.upper = center+mRange.size()/2.0;
32030 } else // mScaleType == stLogarithmic
32031 {
32032 newRange.lower = center/qSqrt(mRange.upper/mRange.lower);
32033 newRange.upper = center*qSqrt(mRange.upper/mRange.lower);
32034 }
32035 }
32036 setRange(newRange);
32037 }
32038 */
32039}
32040
32041/*!
32042 Transforms \a value, in pixel coordinates of the QCustomPlot widget, to axis coordinates.
32043*/
32044void QCPPolarAxisRadial::pixelToCoord(QPointF pixelPos, double &angleCoord, double &radiusCoord) const
32045{
32046 QCPVector2D posVector(pixelPos-mCenter);
32047 radiusCoord = radiusToCoord(posVector.length());
32048 angleCoord = mAngularAxis->angleRadToCoord(posVector.angle());
32049}
32050
32051/*!
32052 Transforms \a value, in coordinates of the axis, to pixel coordinates of the QCustomPlot widget.
32053*/
32054QPointF QCPPolarAxisRadial::coordToPixel(double angleCoord, double radiusCoord) const
32055{
32056 const double radiusPixel = coordToRadius(radiusCoord);
32057 const double angleRad = mAngularAxis->coordToAngleRad(angleCoord);
32058 return QPointF(mCenter.x()+qCos(angleRad)*radiusPixel, mCenter.y()+qSin(angleRad)*radiusPixel);
32059}
32060
32061double QCPPolarAxisRadial::coordToRadius(double coord) const
32062{
32063 if (mScaleType == stLinear)
32064 {
32065 if (!mRangeReversed)
32066 return (coord-mRange.lower)/mRange.size()*mRadius;
32067 else
32068 return (mRange.upper-coord)/mRange.size()*mRadius;
32069 } else // mScaleType == stLogarithmic
32070 {
32071 if (coord >= 0.0 && mRange.upper < 0.0) // invalid value for logarithmic scale, just return outside visible range
32072 return !mRangeReversed ? mRadius+200 : mRadius-200;
32073 else if (coord <= 0.0 && mRange.upper >= 0.0) // invalid value for logarithmic scale, just return outside visible range
32074 return !mRangeReversed ? mRadius-200 :mRadius+200;
32075 else
32076 {
32077 if (!mRangeReversed)
32078 return qLn(coord/mRange.lower)/qLn(mRange.upper/mRange.lower)*mRadius;
32079 else
32080 return qLn(mRange.upper/coord)/qLn(mRange.upper/mRange.lower)*mRadius;
32081 }
32082 }
32083}
32084
32085double QCPPolarAxisRadial::radiusToCoord(double radius) const
32086{
32087 if (mScaleType == stLinear)
32088 {
32089 if (!mRangeReversed)
32090 return (radius)/mRadius*mRange.size()+mRange.lower;
32091 else
32092 return -(radius)/mRadius*mRange.size()+mRange.upper;
32093 } else // mScaleType == stLogarithmic
32094 {
32095 if (!mRangeReversed)
32096 return qPow(mRange.upper/mRange.lower, (radius)/mRadius)*mRange.lower;
32097 else
32098 return qPow(mRange.upper/mRange.lower, (-radius)/mRadius)*mRange.upper;
32099 }
32100}
32101
32102
32103/*!
32104 Returns the part of the axis that is hit by \a pos (in pixels). The return value of this function
32105 is independent of the user-selectable parts defined with \ref setSelectableParts. Further, this
32106 function does not change the current selection state of the axis.
32107
32108 If the axis is not visible (\ref setVisible), this function always returns \ref spNone.
32109
32110 \see setSelectedParts, setSelectableParts, QCustomPlot::setInteractions
32111*/
32113{
32114 Q_UNUSED(pos) // TODO remove later
32115 if (!mVisible)
32116 return spNone;
32117
32118 /*
32119 TODO:
32120 if (mAxisPainter->axisSelectionBox().contains(pos.toPoint()))
32121 return spAxis;
32122 else if (mAxisPainter->tickLabelsSelectionBox().contains(pos.toPoint()))
32123 return spTickLabels;
32124 else if (mAxisPainter->labelSelectionBox().contains(pos.toPoint()))
32125 return spAxisLabel;
32126 else */
32127 return spNone;
32128}
32129
32130/* inherits documentation from base class */
32131double QCPPolarAxisRadial::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
32132{
32133 if (!mParentPlot) return -1;
32134 SelectablePart part = getPartAt(pos);
32135 if ((onlySelectable && !mSelectableParts.testFlag(part)) || part == spNone)
32136 return -1;
32137
32138 if (details)
32139 details->setValue(part);
32140 return mParentPlot->selectionTolerance()*0.99;
32141}
32142
32143/* inherits documentation from base class */
32144void QCPPolarAxisRadial::selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged)
32145{
32146 Q_UNUSED(event)
32147 SelectablePart part = details.value<SelectablePart>();
32148 if (mSelectableParts.testFlag(part))
32149 {
32150 SelectableParts selBefore = mSelectedParts;
32151 setSelectedParts(additive ? mSelectedParts^part : part);
32152 if (selectionStateChanged)
32153 *selectionStateChanged = mSelectedParts != selBefore;
32154 }
32155}
32156
32157/* inherits documentation from base class */
32158void QCPPolarAxisRadial::deselectEvent(bool *selectionStateChanged)
32159{
32160 SelectableParts selBefore = mSelectedParts;
32161 setSelectedParts(mSelectedParts & ~mSelectableParts);
32162 if (selectionStateChanged)
32163 *selectionStateChanged = mSelectedParts != selBefore;
32164}
32165
32166/*! \internal
32167
32168 This mouse event reimplementation provides the functionality to let the user drag individual axes
32169 exclusively, by startig the drag on top of the axis.
32170
32171 For the axis to accept this event and perform the single axis drag, the parent \ref QCPAxisRect
32172 must be configured accordingly, i.e. it must allow range dragging in the orientation of this axis
32173 (\ref QCPAxisRect::setRangeDrag) and this axis must be a draggable axis (\ref
32174 QCPAxisRect::setRangeDragAxes)
32175
32176 \seebaseclassmethod
32177
32178 \note The dragging of possibly multiple axes at once by starting the drag anywhere in the axis
32179 rect is handled by the axis rect's mouse event, e.g. \ref QCPAxisRect::mousePressEvent.
32180*/
32182{
32183 Q_UNUSED(details)
32184 if (!mParentPlot->interactions().testFlag(QCP::iRangeDrag))
32185 {
32186 event->ignore();
32187 return;
32188 }
32189
32190 if (event->buttons() & Qt::LeftButton)
32191 {
32192 mDragging = true;
32193 // initialize antialiasing backup in case we start dragging:
32194 if (mParentPlot->noAntialiasingOnDrag())
32195 {
32196 mAADragBackup = mParentPlot->antialiasedElements();
32197 mNotAADragBackup = mParentPlot->notAntialiasedElements();
32198 }
32199 // Mouse range dragging interaction:
32200 if (mParentPlot->interactions().testFlag(QCP::iRangeDrag))
32201 mDragStartRange = mRange;
32202 }
32203}
32204
32205/*! \internal
32206
32207 This mouse event reimplementation provides the functionality to let the user drag individual axes
32208 exclusively, by startig the drag on top of the axis.
32209
32210 \seebaseclassmethod
32211
32212 \note The dragging of possibly multiple axes at once by starting the drag anywhere in the axis
32213 rect is handled by the axis rect's mouse event, e.g. \ref QCPAxisRect::mousePressEvent.
32214
32215 \see QCPAxis::mousePressEvent
32216*/
32218{
32219 Q_UNUSED(event) // TODO remove later
32220 Q_UNUSED(startPos) // TODO remove later
32221 if (mDragging)
32222 {
32223 /* TODO
32224 const double startPixel = orientation() == Qt::Horizontal ? startPos.x() : startPos.y();
32225 const double currentPixel = orientation() == Qt::Horizontal ? event->pos().x() : event->pos().y();
32226 if (mScaleType == QCPPolarAxisRadial::stLinear)
32227 {
32228 const double diff = pixelToCoord(startPixel) - pixelToCoord(currentPixel);
32229 setRange(mDragStartRange.lower+diff, mDragStartRange.upper+diff);
32230 } else if (mScaleType == QCPPolarAxisRadial::stLogarithmic)
32231 {
32232 const double diff = pixelToCoord(startPixel) / pixelToCoord(currentPixel);
32233 setRange(mDragStartRange.lower*diff, mDragStartRange.upper*diff);
32234 }
32235 */
32236
32237 if (mParentPlot->noAntialiasingOnDrag())
32239 mParentPlot->replot(QCustomPlot::rpQueuedReplot);
32240 }
32241}
32242
32243/*! \internal
32244
32245 This mouse event reimplementation provides the functionality to let the user drag individual axes
32246 exclusively, by startig the drag on top of the axis.
32247
32248 \seebaseclassmethod
32249
32250 \note The dragging of possibly multiple axes at once by starting the drag anywhere in the axis
32251 rect is handled by the axis rect's mouse event, e.g. \ref QCPAxisRect::mousePressEvent.
32252
32253 \see QCPAxis::mousePressEvent
32254*/
32256{
32257 Q_UNUSED(event)
32258 Q_UNUSED(startPos)
32259 mDragging = false;
32260 if (mParentPlot->noAntialiasingOnDrag())
32261 {
32262 mParentPlot->setAntialiasedElements(mAADragBackup);
32263 mParentPlot->setNotAntialiasedElements(mNotAADragBackup);
32264 }
32265}
32266
32267/*! \internal
32268
32269 This mouse event reimplementation provides the functionality to let the user zoom individual axes
32270 exclusively, by performing the wheel event on top of the axis.
32271
32272 For the axis to accept this event and perform the single axis zoom, the parent \ref QCPAxisRect
32273 must be configured accordingly, i.e. it must allow range zooming in the orientation of this axis
32274 (\ref QCPAxisRect::setRangeZoom) and this axis must be a zoomable axis (\ref
32275 QCPAxisRect::setRangeZoomAxes)
32276
32277 \seebaseclassmethod
32278
32279 \note The zooming of possibly multiple axes at once by performing the wheel event anywhere in the
32280 axis rect is handled by the axis rect's mouse event, e.g. \ref QCPAxisRect::wheelEvent.
32281*/
32283{
32284 // Mouse range zooming interaction:
32285 if (!mParentPlot->interactions().testFlag(QCP::iRangeZoom))
32286 {
32287 event->ignore();
32288 return;
32289 }
32290
32291 // TODO:
32292 //const double wheelSteps = event->delta()/120.0; // a single step delta is +/-120 usually
32293 //const double factor = qPow(mRangeZoomFactor, wheelSteps);
32294 //scaleRange(factor, pixelToCoord(orientation() == Qt::Horizontal ? event->pos().x() : event->pos().y()));
32295 mParentPlot->replot();
32296}
32297
32298void QCPPolarAxisRadial::updateGeometry(const QPointF &center, double radius)
32299{
32300 mCenter = center;
32301 mRadius = radius;
32302 if (mRadius < 1) mRadius = 1;
32303}
32304
32305/*! \internal
32306
32307 A convenience function to easily set the QPainter::Antialiased hint on the provided \a painter
32308 before drawing axis lines.
32309
32310 This is the antialiasing state the painter passed to the \ref draw method is in by default.
32311
32312 This function takes into account the local setting of the antialiasing flag as well as the
32313 overrides set with \ref QCustomPlot::setAntialiasedElements and \ref
32314 QCustomPlot::setNotAntialiasedElements.
32315
32316 \seebaseclassmethod
32317
32318 \see setAntialiased
32319*/
32321{
32322 applyAntialiasingHint(painter, mAntialiased, QCP::aeAxes);
32323}
32324
32325/*! \internal
32326
32327 Draws the axis with the specified \a painter, using the internal QCPAxisPainterPrivate instance.
32328
32329 \seebaseclassmethod
32330*/
32332{
32333 const double axisAngleRad = (mAngle+(mAngleReference==arAngularAxis ? mAngularAxis->angle() : 0))/180.0*M_PI;
32334 const QPointF axisVector(qCos(axisAngleRad), qSin(axisAngleRad)); // semantically should be QCPVector2D, but we save time in loops when we keep it as QPointF
32335 const QPointF tickNormal = QCPVector2D(axisVector).perpendicular().toPointF(); // semantically should be QCPVector2D, but we save time in loops when we keep it as QPointF
32336
32337 // draw baseline:
32338 painter->setPen(getBasePen());
32339 painter->drawLine(QLineF(mCenter, mCenter+axisVector*(mRadius-0.5)));
32340
32341 // draw subticks:
32342 if (!mSubTickVector.isEmpty())
32343 {
32344 painter->setPen(getSubTickPen());
32345 for (int i=0; i<mSubTickVector.size(); ++i)
32346 {
32347 const QPointF tickPosition = mCenter+axisVector*coordToRadius(mSubTickVector.at(i));
32348 painter->drawLine(QLineF(tickPosition-tickNormal*mSubTickLengthIn, tickPosition+tickNormal*mSubTickLengthOut));
32349 }
32350 }
32351
32352 // draw ticks and labels:
32353 if (!mTickVector.isEmpty())
32354 {
32355 mLabelPainter.setAnchorReference(mCenter-axisVector); // subtract (normalized) axisVector, just to prevent degenerate tangents for tick label at exact lower axis range
32356 mLabelPainter.setFont(getTickLabelFont());
32357 mLabelPainter.setColor(getTickLabelColor());
32358 const QPen ticksPen = getTickPen();
32359 painter->setPen(ticksPen);
32360 for (int i=0; i<mTickVector.size(); ++i)
32361 {
32362 const double r = coordToRadius(mTickVector.at(i));
32363 const QPointF tickPosition = mCenter+axisVector*r;
32364 painter->drawLine(QLineF(tickPosition-tickNormal*mTickLengthIn, tickPosition+tickNormal*mTickLengthOut));
32365 // possibly draw tick labels:
32366 if (!mTickVectorLabels.isEmpty())
32367 {
32368 if ((!mRangeReversed && (i < mTickVectorLabels.count()-1 || mRadius-r > 10)) ||
32369 (mRangeReversed && (i > 0 || mRadius-r > 10))) // skip last label if it's closer than 10 pixels to angular axis
32370 mLabelPainter.drawTickLabel(painter, tickPosition+tickNormal*mSubTickLengthOut, mTickVectorLabels.at(i));
32371 }
32372 }
32373 }
32374}
32375
32376/*! \internal
32377
32378 Prepares the internal tick vector, sub tick vector and tick label vector. This is done by calling
32379 QCPAxisTicker::generate on the currently installed ticker.
32380
32381 If a change in the label text/count is detected, the cached axis margin is invalidated to make
32382 sure the next margin calculation recalculates the label sizes and returns an up-to-date value.
32383*/
32385{
32386 if (!mParentPlot) return;
32387 if ((!mTicks && !mTickLabels) || mRange.size() <= 0) return;
32388
32389 mTicker->generate(mRange, mParentPlot->locale(), mNumberFormatChar, mNumberPrecision, mTickVector, mSubTicks ? &mSubTickVector : 0, mTickLabels ? &mTickVectorLabels : 0);
32390}
32391
32392/*! \internal
32393
32394 Returns the pen that is used to draw the axis base line. Depending on the selection state, this
32395 is either mSelectedBasePen or mBasePen.
32396*/
32398{
32399 return mSelectedParts.testFlag(spAxis) ? mSelectedBasePen : mBasePen;
32400}
32401
32402/*! \internal
32403
32404 Returns the pen that is used to draw the (major) ticks. Depending on the selection state, this
32405 is either mSelectedTickPen or mTickPen.
32406*/
32408{
32409 return mSelectedParts.testFlag(spAxis) ? mSelectedTickPen : mTickPen;
32410}
32411
32412/*! \internal
32413
32414 Returns the pen that is used to draw the subticks. Depending on the selection state, this
32415 is either mSelectedSubTickPen or mSubTickPen.
32416*/
32418{
32419 return mSelectedParts.testFlag(spAxis) ? mSelectedSubTickPen : mSubTickPen;
32420}
32421
32422/*! \internal
32423
32424 Returns the font that is used to draw the tick labels. Depending on the selection state, this
32425 is either mSelectedTickLabelFont or mTickLabelFont.
32426*/
32428{
32429 return mSelectedParts.testFlag(spTickLabels) ? mSelectedTickLabelFont : mTickLabelFont;
32430}
32431
32432/*! \internal
32433
32434 Returns the font that is used to draw the axis label. Depending on the selection state, this
32435 is either mSelectedLabelFont or mLabelFont.
32436*/
32438{
32439 return mSelectedParts.testFlag(spAxisLabel) ? mSelectedLabelFont : mLabelFont;
32440}
32441
32442/*! \internal
32443
32444 Returns the color that is used to draw the tick labels. Depending on the selection state, this
32445 is either mSelectedTickLabelColor or mTickLabelColor.
32446*/
32448{
32449 return mSelectedParts.testFlag(spTickLabels) ? mSelectedTickLabelColor : mTickLabelColor;
32450}
32451
32452/*! \internal
32453
32454 Returns the color that is used to draw the axis label. Depending on the selection state, this
32455 is either mSelectedLabelColor or mLabelColor.
32456*/
32458{
32459 return mSelectedParts.testFlag(spAxisLabel) ? mSelectedLabelColor : mLabelColor;
32460}
32461
32462
32463/* inherits documentation from base class */
32468/* end of 'src/polar/radialaxis.cpp' */
32469
32470
32471/* including file 'src/polar/layoutelement-angularaxis.cpp' */
32472/* modified 2022-11-06T12:45:57, size 57266 */
32473
32474
32475////////////////////////////////////////////////////////////////////////////////////////////////////
32476//////////////////// QCPPolarAxisAngular
32477////////////////////////////////////////////////////////////////////////////////////////////////////
32478
32479/*! \class QCPPolarAxisAngular
32480 \brief The main container for polar plots, representing the angular axis as a circle
32481
32482 \warning In this QCustomPlot version, polar plots are a tech preview. Expect documentation and
32483 functionality to be incomplete, as well as changing public interfaces in the future.
32484*/
32485
32486/* start documentation of inline functions */
32487
32488/*! \fn QCPLayoutInset *QCPPolarAxisAngular::insetLayout() const
32489
32490 Returns the inset layout of this axis rect. It can be used to place other layout elements (or
32491 even layouts with multiple other elements) inside/on top of an axis rect.
32492
32493 \see QCPLayoutInset
32494*/
32495
32496/*! \fn int QCPPolarAxisAngular::left() const
32497
32498 Returns the pixel position of the left border of this axis rect. Margins are not taken into
32499 account here, so the returned value is with respect to the inner \ref rect.
32500*/
32501
32502/*! \fn int QCPPolarAxisAngular::right() const
32503
32504 Returns the pixel position of the right border of this axis rect. Margins are not taken into
32505 account here, so the returned value is with respect to the inner \ref rect.
32506*/
32507
32508/*! \fn int QCPPolarAxisAngular::top() const
32509
32510 Returns the pixel position of the top border of this axis rect. Margins are not taken into
32511 account here, so the returned value is with respect to the inner \ref rect.
32512*/
32513
32514/*! \fn int QCPPolarAxisAngular::bottom() const
32515
32516 Returns the pixel position of the bottom border of this axis rect. Margins are not taken into
32517 account here, so the returned value is with respect to the inner \ref rect.
32518*/
32519
32520/*! \fn int QCPPolarAxisAngular::width() const
32521
32522 Returns the pixel width of this axis rect. Margins are not taken into account here, so the
32523 returned value is with respect to the inner \ref rect.
32524*/
32525
32526/*! \fn int QCPPolarAxisAngular::height() const
32527
32528 Returns the pixel height of this axis rect. Margins are not taken into account here, so the
32529 returned value is with respect to the inner \ref rect.
32530*/
32531
32532/*! \fn QSize QCPPolarAxisAngular::size() const
32533
32534 Returns the pixel size of this axis rect. Margins are not taken into account here, so the
32535 returned value is with respect to the inner \ref rect.
32536*/
32537
32538/*! \fn QPoint QCPPolarAxisAngular::topLeft() const
32539
32540 Returns the top left corner of this axis rect in pixels. Margins are not taken into account here,
32541 so the returned value is with respect to the inner \ref rect.
32542*/
32543
32544/*! \fn QPoint QCPPolarAxisAngular::topRight() const
32545
32546 Returns the top right corner of this axis rect in pixels. Margins are not taken into account
32547 here, so the returned value is with respect to the inner \ref rect.
32548*/
32549
32550/*! \fn QPoint QCPPolarAxisAngular::bottomLeft() const
32551
32552 Returns the bottom left corner of this axis rect in pixels. Margins are not taken into account
32553 here, so the returned value is with respect to the inner \ref rect.
32554*/
32555
32556/*! \fn QPoint QCPPolarAxisAngular::bottomRight() const
32557
32558 Returns the bottom right corner of this axis rect in pixels. Margins are not taken into account
32559 here, so the returned value is with respect to the inner \ref rect.
32560*/
32561
32562/*! \fn QPoint QCPPolarAxisAngular::center() const
32563
32564 Returns the center of this axis rect in pixels. Margins are not taken into account here, so the
32565 returned value is with respect to the inner \ref rect.
32566*/
32567
32568/* end documentation of inline functions */
32569
32570/*!
32571 Creates a QCPPolarAxis instance and sets default values. An axis is added for each of the four
32572 sides, the top and right axes are set invisible initially.
32573*/
32575 QCPLayoutElement(parentPlot),
32576 mBackgroundBrush(Qt::NoBrush),
32577 mBackgroundScaled(true),
32578 mBackgroundScaledMode(Qt::KeepAspectRatioByExpanding),
32579 mInsetLayout(new QCPLayoutInset),
32580 mRangeDrag(false),
32581 mRangeZoom(false),
32582 mRangeZoomFactor(0.85),
32583 // axis base:
32584 mAngle(-90),
32585 mAngleRad(mAngle/180.0*M_PI),
32586 mSelectableParts(spAxis | spTickLabels | spAxisLabel),
32587 mSelectedParts(spNone),
32588 mBasePen(QPen(Qt::black, 0, Qt::SolidLine, Qt::SquareCap)),
32589 mSelectedBasePen(QPen(Qt::blue, 2)),
32590 // axis label:
32591 mLabelPadding(0),
32592 mLabel(),
32593 mLabelFont(mParentPlot->font()),
32594 mSelectedLabelFont(QFont(mLabelFont.family(), mLabelFont.pointSize(), QFont::Bold)),
32595 mLabelColor(Qt::black),
32596 mSelectedLabelColor(Qt::blue),
32597 // tick labels:
32598 //mTickLabelPadding(0), in label painter
32599 mTickLabels(true),
32600 //mTickLabelRotation(0), in label painter
32601 mTickLabelFont(mParentPlot->font()),
32602 mSelectedTickLabelFont(QFont(mTickLabelFont.family(), mTickLabelFont.pointSize(), QFont::Bold)),
32603 mTickLabelColor(Qt::black),
32604 mSelectedTickLabelColor(Qt::blue),
32605 mNumberPrecision(6),
32606 mNumberFormatChar('g'),
32607 mNumberBeautifulPowers(true),
32608 mNumberMultiplyCross(false),
32609 // ticks and subticks:
32610 mTicks(true),
32611 mSubTicks(true),
32612 mTickLengthIn(5),
32613 mTickLengthOut(0),
32614 mSubTickLengthIn(2),
32615 mSubTickLengthOut(0),
32616 mTickPen(QPen(Qt::black, 0, Qt::SolidLine, Qt::SquareCap)),
32617 mSelectedTickPen(QPen(Qt::blue, 2)),
32618 mSubTickPen(QPen(Qt::black, 0, Qt::SolidLine, Qt::SquareCap)),
32619 mSelectedSubTickPen(QPen(Qt::blue, 2)),
32620 // scale and range:
32621 mRange(0, 360),
32622 mRangeReversed(false),
32623 // internal members:
32624 mRadius(1), // non-zero initial value, will be overwritten in ::update() according to inner rect
32625 mGrid(new QCPPolarGrid(this)),
32626 mTicker(new QCPAxisTickerFixed),
32627 mDragging(false),
32628 mLabelPainter(parentPlot)
32629{
32630 // TODO:
32631 //mInsetLayout->initializeParentPlot(mParentPlot);
32632 //mInsetLayout->setParentLayerable(this);
32633 //mInsetLayout->setParent(this);
32634
32635 if (QCPAxisTickerFixed *fixedTicker = mTicker.dynamicCast<QCPAxisTickerFixed>().data())
32636 {
32637 fixedTicker->setTickStep(30);
32638 }
32639 setAntialiased(true);
32640 setLayer(mParentPlot->currentLayer()); // it's actually on that layer already, but we want it in front of the grid, so we place it on there again
32641
32644 setTickLabelMode(lmUpright);
32645 mLabelPainter.setAnchorReferenceType(QCPLabelPainterPrivate::artNormal);
32646 mLabelPainter.setAbbreviateDecimalPowers(false);
32647 mLabelPainter.setCacheSize(24); // so we can cache up to 15-degree intervals, polar angular axis uses a bit larger cache than normal axes
32648
32649 setMinimumSize(50, 50);
32650 setMinimumMargins(QMargins(30, 30, 30, 30));
32651
32652 addRadialAxis();
32653 mGrid->setRadialAxis(radialAxis());
32654}
32655
32656QCPPolarAxisAngular::~QCPPolarAxisAngular()
32657{
32658 delete mGrid; // delete grid here instead of via parent ~QObject for better defined deletion order
32659 mGrid = 0;
32660
32661 delete mInsetLayout;
32662 mInsetLayout = 0;
32663
32664 QList<QCPPolarAxisRadial*> radialAxesList = radialAxes();
32665 for (int i=0; i<radialAxesList.size(); ++i)
32666 removeRadialAxis(radialAxesList.at(i));
32667}
32668
32669QCPPolarAxisAngular::LabelMode QCPPolarAxisAngular::tickLabelMode() const
32670{
32671 switch (mLabelPainter.anchorMode())
32672 {
32673 case QCPLabelPainterPrivate::amSkewedUpright: return lmUpright;
32674 case QCPLabelPainterPrivate::amSkewedRotated: return lmRotated;
32675 default: qDebug() << Q_FUNC_INFO << "invalid mode for polar axis"; break;
32676 }
32677 return lmUpright;
32678}
32679
32680/* No documentation as it is a property getter */
32681QString QCPPolarAxisAngular::numberFormat() const
32682{
32683 QString result;
32684 result.append(mNumberFormatChar);
32685 if (mNumberBeautifulPowers)
32686 {
32687 result.append(QLatin1Char('b'));
32688 if (mLabelPainter.multiplicationSymbol() == QCPLabelPainterPrivate::SymbolCross)
32689 result.append(QLatin1Char('c'));
32690 }
32691 return result;
32692}
32693
32694/*!
32695 Returns the number of axes on the axis rect side specified with \a type.
32696
32697 \see axis
32698*/
32700{
32701 return mRadialAxes.size();
32702}
32703
32704/*!
32705 Returns the axis with the given \a index on the axis rect side specified with \a type.
32706
32707 \see axisCount, axes
32708*/
32710{
32711 if (index >= 0 && index < mRadialAxes.size())
32712 {
32713 return mRadialAxes.at(index);
32714 } else
32715 {
32716 qDebug() << Q_FUNC_INFO << "Axis index out of bounds:" << index;
32717 return 0;
32718 }
32719}
32720
32721/*!
32722 Returns all axes on the axis rect sides specified with \a types.
32723
32724 \a types may be a single \ref QCPAxis::AxisType or an <tt>or</tt>-combination, to get the axes of
32725 multiple sides.
32726
32727 \see axis
32728*/
32730{
32731 return mRadialAxes;
32732}
32733
32734
32735/*!
32736 Adds a new axis to the axis rect side specified with \a type, and returns it. If \a axis is 0, a
32737 new QCPAxis instance is created internally. QCustomPlot owns the returned axis, so if you want to
32738 remove an axis, use \ref removeAxis instead of deleting it manually.
32739
32740 You may inject QCPAxis instances (or subclasses of QCPAxis) by setting \a axis to an axis that was
32741 previously created outside QCustomPlot. It is important to note that QCustomPlot takes ownership
32742 of the axis, so you may not delete it afterwards. Further, the \a axis must have been created
32743 with this axis rect as parent and with the same axis type as specified in \a type. If this is not
32744 the case, a debug output is generated, the axis is not added, and the method returns 0.
32745
32746 This method can not be used to move \a axis between axis rects. The same \a axis instance must
32747 not be added multiple times to the same or different axis rects.
32748
32749 If an axis rect side already contains one or more axes, the lower and upper endings of the new
32750 axis (\ref QCPAxis::setLowerEnding, \ref QCPAxis::setUpperEnding) are set to \ref
32751 QCPLineEnding::esHalfBar.
32752
32753 \see addAxes, setupFullAxesBox
32754*/
32756{
32757 QCPPolarAxisRadial *newAxis = axis;
32758 if (!newAxis)
32759 {
32760 newAxis = new QCPPolarAxisRadial(this);
32761 } else // user provided existing axis instance, do some sanity checks
32762 {
32763 if (newAxis->angularAxis() != this)
32764 {
32765 qDebug() << Q_FUNC_INFO << "passed radial axis doesn't have this angular axis as parent angular axis";
32766 return 0;
32767 }
32768 if (radialAxes().contains(newAxis))
32769 {
32770 qDebug() << Q_FUNC_INFO << "passed axis is already owned by this angular axis";
32771 return 0;
32772 }
32773 }
32774 mRadialAxes.append(newAxis);
32775 return newAxis;
32776}
32777
32778/*!
32779 Removes the specified \a axis from the axis rect and deletes it.
32780
32781 Returns true on success, i.e. if \a axis was a valid axis in this axis rect.
32782
32783 \see addAxis
32784*/
32786{
32787 if (mRadialAxes.contains(radialAxis))
32788 {
32789 mRadialAxes.removeOne(radialAxis);
32790 delete radialAxis;
32791 return true;
32792 } else
32793 {
32794 qDebug() << Q_FUNC_INFO << "Radial axis isn't associated with this angular axis:" << reinterpret_cast<quintptr>(radialAxis);
32795 return false;
32796 }
32797}
32798
32799QRegion QCPPolarAxisAngular::exactClipRegion() const
32800{
32801 return QRegion(mCenter.x()-mRadius, mCenter.y()-mRadius, qRound(2*mRadius), qRound(2*mRadius), QRegion::Ellipse);
32802}
32803
32804/*!
32805 If the scale type (\ref setScaleType) is \ref stLinear, \a diff is added to the lower and upper
32806 bounds of the range. The range is simply moved by \a diff.
32807
32808 If the scale type is \ref stLogarithmic, the range bounds are multiplied by \a diff. This
32809 corresponds to an apparent "linear" move in logarithmic scaling by a distance of log(diff).
32810*/
32812{
32813 QCPRange oldRange = mRange;
32814 mRange.lower += diff;
32815 mRange.upper += diff;
32816 emit rangeChanged(mRange);
32817 emit rangeChanged(mRange, oldRange);
32818}
32819
32820/*!
32821 Scales the range of this axis by \a factor around the center of the current axis range. For
32822 example, if \a factor is 2.0, then the axis range will double its size, and the point at the axis
32823 range center won't have changed its position in the QCustomPlot widget (i.e. coordinates around
32824 the center will have moved symmetrically closer).
32825
32826 If you wish to scale around a different coordinate than the current axis range center, use the
32827 overload \ref scaleRange(double factor, double center).
32828*/
32830{
32831 scaleRange(factor, range().center());
32832}
32833
32834/*! \overload
32835
32836 Scales the range of this axis by \a factor around the coordinate \a center. For example, if \a
32837 factor is 2.0, \a center is 1.0, then the axis range will double its size, and the point at
32838 coordinate 1.0 won't have changed its position in the QCustomPlot widget (i.e. coordinates
32839 around 1.0 will have moved symmetrically closer to 1.0).
32840
32841 \see scaleRange(double factor)
32842*/
32843void QCPPolarAxisAngular::scaleRange(double factor, double center)
32844{
32845 QCPRange oldRange = mRange;
32846 QCPRange newRange;
32847 newRange.lower = (mRange.lower-center)*factor + center;
32848 newRange.upper = (mRange.upper-center)*factor + center;
32849 if (QCPRange::validRange(newRange))
32850 mRange = newRange.sanitizedForLinScale();
32851 emit rangeChanged(mRange);
32852 emit rangeChanged(mRange, oldRange);
32853}
32854
32855/*!
32856 Changes the axis range such that all plottables associated with this axis are fully visible in
32857 that dimension.
32858
32859 \see QCPAbstractPlottable::rescaleAxes, QCustomPlot::rescaleAxes
32860*/
32861void QCPPolarAxisAngular::rescale(bool onlyVisiblePlottables)
32862{
32863 QCPRange newRange;
32864 bool haveRange = false;
32865 for (int i=0; i<mGraphs.size(); ++i)
32866 {
32867 if (!mGraphs.at(i)->realVisibility() && onlyVisiblePlottables)
32868 continue;
32869 QCPRange range;
32870 bool currentFoundRange;
32871 if (mGraphs.at(i)->keyAxis() == this)
32872 range = mGraphs.at(i)->getKeyRange(currentFoundRange, QCP::sdBoth);
32873 else
32874 range = mGraphs.at(i)->getValueRange(currentFoundRange, QCP::sdBoth);
32875 if (currentFoundRange)
32876 {
32877 if (!haveRange)
32878 newRange = range;
32879 else
32880 newRange.expand(range);
32881 haveRange = true;
32882 }
32883 }
32884 if (haveRange)
32885 {
32886 if (!QCPRange::validRange(newRange)) // likely due to range being zero (plottable has only constant data in this axis dimension), shift current range to at least center the plottable
32887 {
32888 double center = (newRange.lower+newRange.upper)*0.5; // upper and lower should be equal anyway, but just to make sure, incase validRange returned false for other reason
32889 newRange.lower = center-mRange.size()/2.0;
32890 newRange.upper = center+mRange.size()/2.0;
32891 }
32892 setRange(newRange);
32893 }
32894}
32895
32896/*!
32897 Transforms \a value, in pixel coordinates of the QCustomPlot widget, to axis coordinates.
32898*/
32899void QCPPolarAxisAngular::pixelToCoord(QPointF pixelPos, double &angleCoord, double &radiusCoord) const
32900{
32901 if (!mRadialAxes.isEmpty())
32902 mRadialAxes.first()->pixelToCoord(pixelPos, angleCoord, radiusCoord);
32903 else
32904 qDebug() << Q_FUNC_INFO << "no radial axis configured";
32905}
32906
32907/*!
32908 Transforms \a value, in coordinates of the axis, to pixel coordinates of the QCustomPlot widget.
32909*/
32910QPointF QCPPolarAxisAngular::coordToPixel(double angleCoord, double radiusCoord) const
32911{
32912 if (!mRadialAxes.isEmpty())
32913 {
32914 return mRadialAxes.first()->coordToPixel(angleCoord, radiusCoord);
32915 } else
32916 {
32917 qDebug() << Q_FUNC_INFO << "no radial axis configured";
32918 return QPointF();
32919 }
32920}
32921
32922/*!
32923 Returns the part of the axis that is hit by \a pos (in pixels). The return value of this function
32924 is independent of the user-selectable parts defined with \ref setSelectableParts. Further, this
32925 function does not change the current selection state of the axis.
32926
32927 If the axis is not visible (\ref setVisible), this function always returns \ref spNone.
32928
32929 \see setSelectedParts, setSelectableParts, QCustomPlot::setInteractions
32930*/
32932{
32933 Q_UNUSED(pos) // TODO remove later
32934
32935 if (!mVisible)
32936 return spNone;
32937
32938 /*
32939 TODO:
32940 if (mAxisPainter->axisSelectionBox().contains(pos.toPoint()))
32941 return spAxis;
32942 else if (mAxisPainter->tickLabelsSelectionBox().contains(pos.toPoint()))
32943 return spTickLabels;
32944 else if (mAxisPainter->labelSelectionBox().contains(pos.toPoint()))
32945 return spAxisLabel;
32946 else */
32947 return spNone;
32948}
32949
32950/* inherits documentation from base class */
32951double QCPPolarAxisAngular::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
32952{
32953 /*
32954 if (!mParentPlot) return -1;
32955 SelectablePart part = getPartAt(pos);
32956 if ((onlySelectable && !mSelectableParts.testFlag(part)) || part == spNone)
32957 return -1;
32958
32959 if (details)
32960 details->setValue(part);
32961 return mParentPlot->selectionTolerance()*0.99;
32962 */
32963
32964 Q_UNUSED(details)
32965
32966 if (onlySelectable)
32967 return -1;
32968
32969 if (QRectF(mOuterRect).contains(pos))
32970 {
32971 if (mParentPlot)
32972 return mParentPlot->selectionTolerance()*0.99;
32973 else
32974 {
32975 qDebug() << Q_FUNC_INFO << "parent plot not defined";
32976 return -1;
32977 }
32978 } else
32979 return -1;
32980}
32981
32982/*!
32983 This method is called automatically upon replot and doesn't need to be called by users of
32984 QCPPolarAxisAngular.
32985
32986 Calls the base class implementation to update the margins (see \ref QCPLayoutElement::update),
32987 and finally passes the \ref rect to the inset layout (\ref insetLayout) and calls its
32988 QCPInsetLayout::update function.
32989
32990 \seebaseclassmethod
32991*/
32993{
32995
32996 switch (phase)
32997 {
32998 case upPreparation:
32999 {
33001 for (int i=0; i<mRadialAxes.size(); ++i)
33002 mRadialAxes.at(i)->setupTickVectors();
33003 break;
33004 }
33005 case upLayout:
33006 {
33007 mCenter = mRect.center();
33008 mRadius = 0.5*qMin(qAbs(mRect.width()), qAbs(mRect.height()));
33009 if (mRadius < 1) mRadius = 1; // prevent cases where radius might become 0 which causes trouble
33010 for (int i=0; i<mRadialAxes.size(); ++i)
33011 mRadialAxes.at(i)->updateGeometry(mCenter, mRadius);
33012
33013 mInsetLayout->setOuterRect(rect());
33014 break;
33015 }
33016 default: break;
33017 }
33018
33019 // pass update call on to inset layout (doesn't happen automatically, because QCPPolarAxis doesn't derive from QCPLayout):
33020 mInsetLayout->update(phase);
33021}
33022
33023/* inherits documentation from base class */
33025{
33027 if (mInsetLayout)
33028 {
33029 result << mInsetLayout;
33030 if (recursive)
33031 result << mInsetLayout->elements(recursive);
33032 }
33033 return result;
33034}
33035
33036bool QCPPolarAxisAngular::removeGraph(QCPPolarGraph *graph)
33037{
33038 if (!mGraphs.contains(graph))
33039 {
33040 qDebug() << Q_FUNC_INFO << "graph not in list:" << reinterpret_cast<quintptr>(graph);
33041 return false;
33042 }
33043
33044 // remove plottable from legend:
33045 graph->removeFromLegend();
33046 // remove plottable:
33047 delete graph;
33048 mGraphs.removeOne(graph);
33049 return true;
33050}
33051
33052/* inherits documentation from base class */
33054{
33055 applyAntialiasingHint(painter, mAntialiased, QCP::aeAxes);
33056}
33057
33058/* inherits documentation from base class */
33060{
33061 drawBackground(painter, mCenter, mRadius);
33062
33063 // draw baseline circle:
33064 painter->setPen(getBasePen());
33065 painter->drawEllipse(mCenter, mRadius, mRadius);
33066
33067 // draw subticks:
33068 if (!mSubTickVector.isEmpty())
33069 {
33070 painter->setPen(getSubTickPen());
33071 for (int i=0; i<mSubTickVector.size(); ++i)
33072 {
33073 painter->drawLine(mCenter+mSubTickVectorCosSin.at(i)*(mRadius-mSubTickLengthIn),
33074 mCenter+mSubTickVectorCosSin.at(i)*(mRadius+mSubTickLengthOut));
33075 }
33076 }
33077
33078 // draw ticks and labels:
33079 if (!mTickVector.isEmpty())
33080 {
33081 mLabelPainter.setAnchorReference(mCenter);
33082 mLabelPainter.setFont(getTickLabelFont());
33083 mLabelPainter.setColor(getTickLabelColor());
33084 const QPen ticksPen = getTickPen();
33085 painter->setPen(ticksPen);
33086 for (int i=0; i<mTickVector.size(); ++i)
33087 {
33088 const QPointF outerTick = mCenter+mTickVectorCosSin.at(i)*(mRadius+mTickLengthOut);
33089 painter->drawLine(mCenter+mTickVectorCosSin.at(i)*(mRadius-mTickLengthIn), outerTick);
33090 // draw tick labels:
33091 if (!mTickVectorLabels.isEmpty())
33092 {
33093 if (i < mTickVectorLabels.count()-1 || (mTickVectorCosSin.at(i)-mTickVectorCosSin.first()).manhattanLength() > 5/180.0*M_PI) // skip last label if it's closer than approx 5 degrees to first
33094 mLabelPainter.drawTickLabel(painter, outerTick, mTickVectorLabels.at(i));
33095 }
33096 }
33097 }
33098}
33099
33100/* inherits documentation from base class */
33105
33106
33107/*!
33108 Sets \a pm as the axis background pixmap. The axis background pixmap will be drawn inside the
33109 axis rect. Since axis rects place themselves on the "background" layer by default, the axis rect
33110 backgrounds are usually drawn below everything else.
33111
33112 For cases where the provided pixmap doesn't have the same size as the axis rect, scaling can be
33113 enabled with \ref setBackgroundScaled and the scaling mode (i.e. whether and how the aspect ratio
33114 is preserved) can be set with \ref setBackgroundScaledMode. To set all these options in one call,
33115 consider using the overloaded version of this function.
33116
33117 Below the pixmap, the axis rect may be optionally filled with a brush, if specified with \ref
33118 setBackground(const QBrush &brush).
33119
33120 \see setBackgroundScaled, setBackgroundScaledMode, setBackground(const QBrush &brush)
33121*/
33123{
33124 mBackgroundPixmap = pm;
33125 mScaledBackgroundPixmap = QPixmap();
33126}
33127
33128/*! \overload
33129
33130 Sets \a brush as the background brush. The axis rect background will be filled with this brush.
33131 Since axis rects place themselves on the "background" layer by default, the axis rect backgrounds
33132 are usually drawn below everything else.
33133
33134 The brush will be drawn before (under) any background pixmap, which may be specified with \ref
33135 setBackground(const QPixmap &pm).
33136
33137 To disable drawing of a background brush, set \a brush to Qt::NoBrush.
33138
33139 \see setBackground(const QPixmap &pm)
33140*/
33142{
33143 mBackgroundBrush = brush;
33144}
33145
33146/*! \overload
33147
33148 Allows setting the background pixmap of the axis rect, whether it shall be scaled and how it
33149 shall be scaled in one call.
33150
33151 \see setBackground(const QPixmap &pm), setBackgroundScaled, setBackgroundScaledMode
33152*/
33154{
33155 mBackgroundPixmap = pm;
33156 mScaledBackgroundPixmap = QPixmap();
33157 mBackgroundScaled = scaled;
33158 mBackgroundScaledMode = mode;
33159}
33160
33161/*!
33162 Sets whether the axis background pixmap shall be scaled to fit the axis rect or not. If \a scaled
33163 is set to true, you may control whether and how the aspect ratio of the original pixmap is
33164 preserved with \ref setBackgroundScaledMode.
33165
33166 Note that the scaled version of the original pixmap is buffered, so there is no performance
33167 penalty on replots. (Except when the axis rect dimensions are changed continuously.)
33168
33169 \see setBackground, setBackgroundScaledMode
33170*/
33172{
33173 mBackgroundScaled = scaled;
33174}
33175
33176/*!
33177 If scaling of the axis background pixmap is enabled (\ref setBackgroundScaled), use this function to
33178 define whether and how the aspect ratio of the original pixmap passed to \ref setBackground is preserved.
33179 \see setBackground, setBackgroundScaled
33180*/
33182{
33183 mBackgroundScaledMode = mode;
33184}
33185
33186void QCPPolarAxisAngular::setRangeDrag(bool enabled)
33187{
33188 mRangeDrag = enabled;
33189}
33190
33191void QCPPolarAxisAngular::setRangeZoom(bool enabled)
33192{
33193 mRangeZoom = enabled;
33194}
33195
33196void QCPPolarAxisAngular::setRangeZoomFactor(double factor)
33197{
33198 mRangeZoomFactor = factor;
33199}
33200
33201
33202
33203
33204
33205
33206
33207/*!
33208 Sets the range of the axis.
33209
33210 This slot may be connected with the \ref rangeChanged signal of another axis so this axis
33211 is always synchronized with the other axis range, when it changes.
33212
33213 To invert the direction of an axis, use \ref setRangeReversed.
33214*/
33216{
33217 if (range.lower == mRange.lower && range.upper == mRange.upper)
33218 return;
33219
33220 if (!QCPRange::validRange(range)) return;
33221 QCPRange oldRange = mRange;
33222 mRange = range.sanitizedForLinScale();
33223 emit rangeChanged(mRange);
33224 emit rangeChanged(mRange, oldRange);
33225}
33226
33227/*!
33228 Sets whether the user can (de-)select the parts in \a selectable by clicking on the QCustomPlot surface.
33229 (When \ref QCustomPlot::setInteractions contains iSelectAxes.)
33230
33231 However, even when \a selectable is set to a value not allowing the selection of a specific part,
33232 it is still possible to set the selection of this part manually, by calling \ref setSelectedParts
33233 directly.
33234
33235 \see SelectablePart, setSelectedParts
33236*/
33238{
33239 if (mSelectableParts != selectable)
33240 {
33241 mSelectableParts = selectable;
33242 emit selectableChanged(mSelectableParts);
33243 }
33244}
33245
33246/*!
33247 Sets the selected state of the respective axis parts described by \ref SelectablePart. When a part
33248 is selected, it uses a different pen/font.
33249
33250 The entire selection mechanism for axes is handled automatically when \ref
33251 QCustomPlot::setInteractions contains iSelectAxes. You only need to call this function when you
33252 wish to change the selection state manually.
33253
33254 This function can change the selection state of a part, independent of the \ref setSelectableParts setting.
33255
33256 emits the \ref selectionChanged signal when \a selected is different from the previous selection state.
33257
33258 \see SelectablePart, setSelectableParts, selectTest, setSelectedBasePen, setSelectedTickPen, setSelectedSubTickPen,
33259 setSelectedTickLabelFont, setSelectedLabelFont, setSelectedTickLabelColor, setSelectedLabelColor
33260*/
33262{
33263 if (mSelectedParts != selected)
33264 {
33265 mSelectedParts = selected;
33266 emit selectionChanged(mSelectedParts);
33267 }
33268}
33269
33270/*!
33271 \overload
33272
33273 Sets the lower and upper bound of the axis range.
33274
33275 To invert the direction of an axis, use \ref setRangeReversed.
33276
33277 There is also a slot to set a range, see \ref setRange(const QCPRange &range).
33278*/
33279void QCPPolarAxisAngular::setRange(double lower, double upper)
33280{
33281 if (lower == mRange.lower && upper == mRange.upper)
33282 return;
33283
33284 if (!QCPRange::validRange(lower, upper)) return;
33285 QCPRange oldRange = mRange;
33286 mRange.lower = lower;
33287 mRange.upper = upper;
33288 mRange = mRange.sanitizedForLinScale();
33289 emit rangeChanged(mRange);
33290 emit rangeChanged(mRange, oldRange);
33291}
33292
33293/*!
33294 \overload
33295
33296 Sets the range of the axis.
33297
33298 The \a position coordinate indicates together with the \a alignment parameter, where the new
33299 range will be positioned. \a size defines the size of the new axis range. \a alignment may be
33300 Qt::AlignLeft, Qt::AlignRight or Qt::AlignCenter. This will cause the left border, right border,
33301 or center of the range to be aligned with \a position. Any other values of \a alignment will
33302 default to Qt::AlignCenter.
33303*/
33304void QCPPolarAxisAngular::setRange(double position, double size, Qt::AlignmentFlag alignment)
33305{
33306 if (alignment == Qt::AlignLeft)
33307 setRange(position, position+size);
33308 else if (alignment == Qt::AlignRight)
33309 setRange(position-size, position);
33310 else // alignment == Qt::AlignCenter
33311 setRange(position-size/2.0, position+size/2.0);
33312}
33313
33314/*!
33315 Sets the lower bound of the axis range. The upper bound is not changed.
33316 \see setRange
33317*/
33319{
33320 if (mRange.lower == lower)
33321 return;
33322
33323 QCPRange oldRange = mRange;
33324 mRange.lower = lower;
33325 mRange = mRange.sanitizedForLinScale();
33326 emit rangeChanged(mRange);
33327 emit rangeChanged(mRange, oldRange);
33328}
33329
33330/*!
33331 Sets the upper bound of the axis range. The lower bound is not changed.
33332 \see setRange
33333*/
33335{
33336 if (mRange.upper == upper)
33337 return;
33338
33339 QCPRange oldRange = mRange;
33340 mRange.upper = upper;
33341 mRange = mRange.sanitizedForLinScale();
33342 emit rangeChanged(mRange);
33343 emit rangeChanged(mRange, oldRange);
33344}
33345
33346/*!
33347 Sets whether the axis range (direction) is displayed reversed. Normally, the values on horizontal
33348 axes increase left to right, on vertical axes bottom to top. When \a reversed is set to true, the
33349 direction of increasing values is inverted.
33350
33351 Note that the range and data interface stays the same for reversed axes, e.g. the \a lower part
33352 of the \ref setRange interface will still reference the mathematically smaller number than the \a
33353 upper part.
33354*/
33356{
33357 mRangeReversed = reversed;
33358}
33359
33360void QCPPolarAxisAngular::setAngle(double degrees)
33361{
33362 mAngle = degrees;
33363 mAngleRad = mAngle/180.0*M_PI;
33364}
33365
33366/*!
33367 The axis ticker is responsible for generating the tick positions and tick labels. See the
33368 documentation of QCPAxisTicker for details on how to work with axis tickers.
33369
33370 You can change the tick positioning/labeling behaviour of this axis by setting a different
33371 QCPAxisTicker subclass using this method. If you only wish to modify the currently installed axis
33372 ticker, access it via \ref ticker.
33373
33374 Since the ticker is stored in the axis as a shared pointer, multiple axes may share the same axis
33375 ticker simply by passing the same shared pointer to multiple axes.
33376
33377 \see ticker
33378*/
33380{
33381 if (ticker)
33382 mTicker = ticker;
33383 else
33384 qDebug() << Q_FUNC_INFO << "can not set 0 as axis ticker";
33385 // no need to invalidate margin cache here because produced tick labels are checked for changes in setupTickVector
33386}
33387
33388/*!
33389 Sets whether tick marks are displayed.
33390
33391 Note that setting \a show to false does not imply that tick labels are invisible, too. To achieve
33392 that, see \ref setTickLabels.
33393
33394 \see setSubTicks
33395*/
33397{
33398 if (mTicks != show)
33399 {
33400 mTicks = show;
33401 //mCachedMarginValid = false;
33402 }
33403}
33404
33405/*!
33406 Sets whether tick labels are displayed. Tick labels are the numbers drawn next to tick marks.
33407*/
33409{
33410 if (mTickLabels != show)
33411 {
33412 mTickLabels = show;
33413 //mCachedMarginValid = false;
33414 if (!mTickLabels)
33415 mTickVectorLabels.clear();
33416 }
33417}
33418
33419/*!
33420 Sets the distance between the axis base line (including any outward ticks) and the tick labels.
33421 \see setLabelPadding, setPadding
33422*/
33424{
33425 mLabelPainter.setPadding(padding);
33426}
33427
33428/*!
33429 Sets the font of the tick labels.
33430
33431 \see setTickLabels, setTickLabelColor
33432*/
33434{
33435 mTickLabelFont = font;
33436}
33437
33438/*!
33439 Sets the color of the tick labels.
33440
33441 \see setTickLabels, setTickLabelFont
33442*/
33444{
33445 mTickLabelColor = color;
33446}
33447
33448/*!
33449 Sets the rotation of the tick labels. If \a degrees is zero, the labels are drawn normally. Else,
33450 the tick labels are drawn rotated by \a degrees clockwise. The specified angle is bound to values
33451 from -90 to 90 degrees.
33452
33453 If \a degrees is exactly -90, 0 or 90, the tick labels are centered on the tick coordinate. For
33454 other angles, the label is drawn with an offset such that it seems to point toward or away from
33455 the tick mark.
33456*/
33458{
33459 mLabelPainter.setRotation(degrees);
33460}
33461
33462void QCPPolarAxisAngular::setTickLabelMode(LabelMode mode)
33463{
33464 switch (mode)
33465 {
33466 case lmUpright: mLabelPainter.setAnchorMode(QCPLabelPainterPrivate::amSkewedUpright); break;
33467 case lmRotated: mLabelPainter.setAnchorMode(QCPLabelPainterPrivate::amSkewedRotated); break;
33468 }
33469}
33470
33471/*!
33472 Sets the number format for the numbers in tick labels. This \a formatCode is an extended version
33473 of the format code used e.g. by QString::number() and QLocale::toString(). For reference about
33474 that, see the "Argument Formats" section in the detailed description of the QString class.
33475
33476 \a formatCode is a string of one, two or three characters. The first character is identical to
33477 the normal format code used by Qt. In short, this means: 'e'/'E' scientific format, 'f' fixed
33478 format, 'g'/'G' scientific or fixed, whichever is shorter.
33479
33480 The second and third characters are optional and specific to QCustomPlot:\n If the first char was
33481 'e' or 'g', numbers are/might be displayed in the scientific format, e.g. "5.5e9", which might be
33482 visually unappealing in a plot. So when the second char of \a formatCode is set to 'b' (for
33483 "beautiful"), those exponential numbers are formatted in a more natural way, i.e. "5.5
33484 [multiplication sign] 10 [superscript] 9". By default, the multiplication sign is a centered dot.
33485 If instead a cross should be shown (as is usual in the USA), the third char of \a formatCode can
33486 be set to 'c'. The inserted multiplication signs are the UTF-8 characters 215 (0xD7) for the
33487 cross and 183 (0xB7) for the dot.
33488
33489 Examples for \a formatCode:
33490 \li \c g normal format code behaviour. If number is small, fixed format is used, if number is large,
33491 normal scientific format is used
33492 \li \c gb If number is small, fixed format is used, if number is large, scientific format is used with
33493 beautifully typeset decimal powers and a dot as multiplication sign
33494 \li \c ebc All numbers are in scientific format with beautifully typeset decimal power and a cross as
33495 multiplication sign
33496 \li \c fb illegal format code, since fixed format doesn't support (or need) beautifully typeset decimal
33497 powers. Format code will be reduced to 'f'.
33498 \li \c hello illegal format code, since first char is not 'e', 'E', 'f', 'g' or 'G'. Current format
33499 code will not be changed.
33500*/
33502{
33503 if (formatCode.isEmpty())
33504 {
33505 qDebug() << Q_FUNC_INFO << "Passed formatCode is empty";
33506 return;
33507 }
33508 //mCachedMarginValid = false;
33509
33510 // interpret first char as number format char:
33511 QString allowedFormatChars(QLatin1String("eEfgG"));
33512 if (allowedFormatChars.contains(formatCode.at(0)))
33513 {
33514 mNumberFormatChar = QLatin1Char(formatCode.at(0).toLatin1());
33515 } else
33516 {
33517 qDebug() << Q_FUNC_INFO << "Invalid number format code (first char not in 'eEfgG'):" << formatCode;
33518 return;
33519 }
33520
33521 if (formatCode.length() < 2)
33522 {
33523 mNumberBeautifulPowers = false;
33524 mNumberMultiplyCross = false;
33525 } else
33526 {
33527 // interpret second char as indicator for beautiful decimal powers:
33528 if (formatCode.at(1) == QLatin1Char('b') && (mNumberFormatChar == QLatin1Char('e') || mNumberFormatChar == QLatin1Char('g')))
33529 mNumberBeautifulPowers = true;
33530 else
33531 qDebug() << Q_FUNC_INFO << "Invalid number format code (second char not 'b' or first char neither 'e' nor 'g'):" << formatCode;
33532
33533 if (formatCode.length() < 3)
33534 {
33535 mNumberMultiplyCross = false;
33536 } else
33537 {
33538 // interpret third char as indicator for dot or cross multiplication symbol:
33539 if (formatCode.at(2) == QLatin1Char('c'))
33540 mNumberMultiplyCross = true;
33541 else if (formatCode.at(2) == QLatin1Char('d'))
33542 mNumberMultiplyCross = false;
33543 else
33544 qDebug() << Q_FUNC_INFO << "Invalid number format code (third char neither 'c' nor 'd'):" << formatCode;
33545 }
33546 }
33547 mLabelPainter.setSubstituteExponent(mNumberBeautifulPowers);
33548 mLabelPainter.setMultiplicationSymbol(mNumberMultiplyCross ? QCPLabelPainterPrivate::SymbolCross : QCPLabelPainterPrivate::SymbolDot);
33549}
33550
33551/*!
33552 Sets the precision of the tick label numbers. See QLocale::toString(double i, char f, int prec)
33553 for details. The effect of precisions are most notably for number Formats starting with 'e', see
33554 \ref setNumberFormat
33555*/
33557{
33558 if (mNumberPrecision != precision)
33559 {
33560 mNumberPrecision = precision;
33561 //mCachedMarginValid = false;
33562 }
33563}
33564
33565/*!
33566 Sets the length of the ticks in pixels. \a inside is the length the ticks will reach inside the
33567 plot and \a outside is the length they will reach outside the plot. If \a outside is greater than
33568 zero, the tick labels and axis label will increase their distance to the axis accordingly, so
33569 they won't collide with the ticks.
33570
33571 \see setSubTickLength, setTickLengthIn, setTickLengthOut
33572*/
33573void QCPPolarAxisAngular::setTickLength(int inside, int outside)
33574{
33575 setTickLengthIn(inside);
33576 setTickLengthOut(outside);
33577}
33578
33579/*!
33580 Sets the length of the inward ticks in pixels. \a inside is the length the ticks will reach
33581 inside the plot.
33582
33583 \see setTickLengthOut, setTickLength, setSubTickLength
33584*/
33586{
33587 if (mTickLengthIn != inside)
33588 {
33589 mTickLengthIn = inside;
33590 }
33591}
33592
33593/*!
33594 Sets the length of the outward ticks in pixels. \a outside is the length the ticks will reach
33595 outside the plot. If \a outside is greater than zero, the tick labels and axis label will
33596 increase their distance to the axis accordingly, so they won't collide with the ticks.
33597
33598 \see setTickLengthIn, setTickLength, setSubTickLength
33599*/
33601{
33602 if (mTickLengthOut != outside)
33603 {
33604 mTickLengthOut = outside;
33605 //mCachedMarginValid = false; // only outside tick length can change margin
33606 }
33607}
33608
33609/*!
33610 Sets whether sub tick marks are displayed.
33611
33612 Sub ticks are only potentially visible if (major) ticks are also visible (see \ref setTicks)
33613
33614 \see setTicks
33615*/
33617{
33618 if (mSubTicks != show)
33619 {
33620 mSubTicks = show;
33621 //mCachedMarginValid = false;
33622 }
33623}
33624
33625/*!
33626 Sets the length of the subticks in pixels. \a inside is the length the subticks will reach inside
33627 the plot and \a outside is the length they will reach outside the plot. If \a outside is greater
33628 than zero, the tick labels and axis label will increase their distance to the axis accordingly,
33629 so they won't collide with the ticks.
33630
33631 \see setTickLength, setSubTickLengthIn, setSubTickLengthOut
33632*/
33633void QCPPolarAxisAngular::setSubTickLength(int inside, int outside)
33634{
33635 setSubTickLengthIn(inside);
33636 setSubTickLengthOut(outside);
33637}
33638
33639/*!
33640 Sets the length of the inward subticks in pixels. \a inside is the length the subticks will reach inside
33641 the plot.
33642
33643 \see setSubTickLengthOut, setSubTickLength, setTickLength
33644*/
33646{
33647 if (mSubTickLengthIn != inside)
33648 {
33649 mSubTickLengthIn = inside;
33650 }
33651}
33652
33653/*!
33654 Sets the length of the outward subticks in pixels. \a outside is the length the subticks will reach
33655 outside the plot. If \a outside is greater than zero, the tick labels will increase their
33656 distance to the axis accordingly, so they won't collide with the ticks.
33657
33658 \see setSubTickLengthIn, setSubTickLength, setTickLength
33659*/
33661{
33662 if (mSubTickLengthOut != outside)
33663 {
33664 mSubTickLengthOut = outside;
33665 //mCachedMarginValid = false; // only outside tick length can change margin
33666 }
33667}
33668
33669/*!
33670 Sets the pen, the axis base line is drawn with.
33671
33672 \see setTickPen, setSubTickPen
33673*/
33675{
33676 mBasePen = pen;
33677}
33678
33679/*!
33680 Sets the pen, tick marks will be drawn with.
33681
33682 \see setTickLength, setBasePen
33683*/
33685{
33686 mTickPen = pen;
33687}
33688
33689/*!
33690 Sets the pen, subtick marks will be drawn with.
33691
33692 \see setSubTickCount, setSubTickLength, setBasePen
33693*/
33695{
33696 mSubTickPen = pen;
33697}
33698
33699/*!
33700 Sets the font of the axis label.
33701
33702 \see setLabelColor
33703*/
33705{
33706 if (mLabelFont != font)
33707 {
33708 mLabelFont = font;
33709 //mCachedMarginValid = false;
33710 }
33711}
33712
33713/*!
33714 Sets the color of the axis label.
33715
33716 \see setLabelFont
33717*/
33719{
33720 mLabelColor = color;
33721}
33722
33723/*!
33724 Sets the text of the axis label that will be shown below/above or next to the axis, depending on
33725 its orientation. To disable axis labels, pass an empty string as \a str.
33726*/
33728{
33729 if (mLabel != str)
33730 {
33731 mLabel = str;
33732 //mCachedMarginValid = false;
33733 }
33734}
33735
33736/*!
33737 Sets the distance between the tick labels and the axis label.
33738
33739 \see setTickLabelPadding, setPadding
33740*/
33742{
33743 if (mLabelPadding != padding)
33744 {
33745 mLabelPadding = padding;
33746 //mCachedMarginValid = false;
33747 }
33748}
33749
33750/*!
33751 Sets the font that is used for tick labels when they are selected.
33752
33753 \see setTickLabelFont, setSelectableParts, setSelectedParts, QCustomPlot::setInteractions
33754*/
33756{
33757 if (font != mSelectedTickLabelFont)
33758 {
33759 mSelectedTickLabelFont = font;
33760 // don't set mCachedMarginValid to false here because margin calculation is always done with non-selected fonts
33761 }
33762}
33763
33764/*!
33765 Sets the font that is used for the axis label when it is selected.
33766
33767 \see setLabelFont, setSelectableParts, setSelectedParts, QCustomPlot::setInteractions
33768*/
33770{
33771 mSelectedLabelFont = font;
33772 // don't set mCachedMarginValid to false here because margin calculation is always done with non-selected fonts
33773}
33774
33775/*!
33776 Sets the color that is used for tick labels when they are selected.
33777
33778 \see setTickLabelColor, setSelectableParts, setSelectedParts, QCustomPlot::setInteractions
33779*/
33781{
33782 if (color != mSelectedTickLabelColor)
33783 {
33784 mSelectedTickLabelColor = color;
33785 }
33786}
33787
33788/*!
33789 Sets the color that is used for the axis label when it is selected.
33790
33791 \see setLabelColor, setSelectableParts, setSelectedParts, QCustomPlot::setInteractions
33792*/
33794{
33795 mSelectedLabelColor = color;
33796}
33797
33798/*!
33799 Sets the pen that is used to draw the axis base line when selected.
33800
33801 \see setBasePen, setSelectableParts, setSelectedParts, QCustomPlot::setInteractions
33802*/
33804{
33805 mSelectedBasePen = pen;
33806}
33807
33808/*!
33809 Sets the pen that is used to draw the (major) ticks when selected.
33810
33811 \see setTickPen, setSelectableParts, setSelectedParts, QCustomPlot::setInteractions
33812*/
33814{
33815 mSelectedTickPen = pen;
33816}
33817
33818/*!
33819 Sets the pen that is used to draw the subticks when selected.
33820
33821 \see setSubTickPen, setSelectableParts, setSelectedParts, QCustomPlot::setInteractions
33822*/
33824{
33825 mSelectedSubTickPen = pen;
33826}
33827
33828/*! \internal
33829
33830 Draws the background of this axis rect. It may consist of a background fill (a QBrush) and a
33831 pixmap.
33832
33833 If a brush was given via \ref setBackground(const QBrush &brush), this function first draws an
33834 according filling inside the axis rect with the provided \a painter.
33835
33836 Then, if a pixmap was provided via \ref setBackground, this function buffers the scaled version
33837 depending on \ref setBackgroundScaled and \ref setBackgroundScaledMode and then draws it inside
33838 the axis rect with the provided \a painter. The scaled version is buffered in
33839 mScaledBackgroundPixmap to prevent expensive rescaling at every redraw. It is only updated, when
33840 the axis rect has changed in a way that requires a rescale of the background pixmap (this is
33841 dependent on the \ref setBackgroundScaledMode), or when a differend axis background pixmap was
33842 set.
33843
33844 \see setBackground, setBackgroundScaled, setBackgroundScaledMode
33845*/
33846void QCPPolarAxisAngular::drawBackground(QCPPainter *painter, const QPointF &center, double radius)
33847{
33848 // draw background fill (don't use circular clip, looks bad):
33849 if (mBackgroundBrush != Qt::NoBrush)
33850 {
33851 QPainterPath ellipsePath;
33852 ellipsePath.addEllipse(center, radius, radius);
33853 painter->fillPath(ellipsePath, mBackgroundBrush);
33854 }
33855
33856 // draw background pixmap (on top of fill, if brush specified):
33857 if (!mBackgroundPixmap.isNull())
33858 {
33859 QRegion clipCircle(center.x()-radius, center.y()-radius, qRound(2*radius), qRound(2*radius), QRegion::Ellipse);
33860 QRegion originalClip = painter->clipRegion();
33861 painter->setClipRegion(clipCircle);
33862 if (mBackgroundScaled)
33863 {
33864 // check whether mScaledBackground needs to be updated:
33865 QSize scaledSize(mBackgroundPixmap.size());
33866 scaledSize.scale(mRect.size(), mBackgroundScaledMode);
33867 if (mScaledBackgroundPixmap.size() != scaledSize)
33868 mScaledBackgroundPixmap = mBackgroundPixmap.scaled(mRect.size(), mBackgroundScaledMode, Qt::SmoothTransformation);
33869 painter->drawPixmap(mRect.topLeft()+QPoint(0, -1), mScaledBackgroundPixmap, QRect(0, 0, mRect.width(), mRect.height()) & mScaledBackgroundPixmap.rect());
33870 } else
33871 {
33872 painter->drawPixmap(mRect.topLeft()+QPoint(0, -1), mBackgroundPixmap, QRect(0, 0, mRect.width(), mRect.height()));
33873 }
33874 painter->setClipRegion(originalClip);
33875 }
33876}
33877
33878/*! \internal
33879
33880 Prepares the internal tick vector, sub tick vector and tick label vector. This is done by calling
33881 QCPAxisTicker::generate on the currently installed ticker.
33882
33883 If a change in the label text/count is detected, the cached axis margin is invalidated to make
33884 sure the next margin calculation recalculates the label sizes and returns an up-to-date value.
33885*/
33887{
33888 if (!mParentPlot) return;
33889 if ((!mTicks && !mTickLabels && !mGrid->visible()) || mRange.size() <= 0) return;
33890
33891 mSubTickVector.clear(); // since we might not pass it to mTicker->generate(), and we don't want old data in there
33892 mTicker->generate(mRange, mParentPlot->locale(), mNumberFormatChar, mNumberPrecision, mTickVector, mSubTicks ? &mSubTickVector : 0, mTickLabels ? &mTickVectorLabels : 0);
33893
33894 // fill cos/sin buffers which will be used by draw() and QCPPolarGrid::draw(), so we don't have to calculate it twice:
33895 mTickVectorCosSin.resize(mTickVector.size());
33896 for (int i=0; i<mTickVector.size(); ++i)
33897 {
33898 const double theta = coordToAngleRad(mTickVector.at(i));
33899 mTickVectorCosSin[i] = QPointF(qCos(theta), qSin(theta));
33900 }
33901 mSubTickVectorCosSin.resize(mSubTickVector.size());
33902 for (int i=0; i<mSubTickVector.size(); ++i)
33903 {
33904 const double theta = coordToAngleRad(mSubTickVector.at(i));
33905 mSubTickVectorCosSin[i] = QPointF(qCos(theta), qSin(theta));
33906 }
33907}
33908
33909/*! \internal
33910
33911 Returns the pen that is used to draw the axis base line. Depending on the selection state, this
33912 is either mSelectedBasePen or mBasePen.
33913*/
33915{
33916 return mSelectedParts.testFlag(spAxis) ? mSelectedBasePen : mBasePen;
33917}
33918
33919/*! \internal
33920
33921 Returns the pen that is used to draw the (major) ticks. Depending on the selection state, this
33922 is either mSelectedTickPen or mTickPen.
33923*/
33925{
33926 return mSelectedParts.testFlag(spAxis) ? mSelectedTickPen : mTickPen;
33927}
33928
33929/*! \internal
33930
33931 Returns the pen that is used to draw the subticks. Depending on the selection state, this
33932 is either mSelectedSubTickPen or mSubTickPen.
33933*/
33935{
33936 return mSelectedParts.testFlag(spAxis) ? mSelectedSubTickPen : mSubTickPen;
33937}
33938
33939/*! \internal
33940
33941 Returns the font that is used to draw the tick labels. Depending on the selection state, this
33942 is either mSelectedTickLabelFont or mTickLabelFont.
33943*/
33945{
33946 return mSelectedParts.testFlag(spTickLabels) ? mSelectedTickLabelFont : mTickLabelFont;
33947}
33948
33949/*! \internal
33950
33951 Returns the font that is used to draw the axis label. Depending on the selection state, this
33952 is either mSelectedLabelFont or mLabelFont.
33953*/
33955{
33956 return mSelectedParts.testFlag(spAxisLabel) ? mSelectedLabelFont : mLabelFont;
33957}
33958
33959/*! \internal
33960
33961 Returns the color that is used to draw the tick labels. Depending on the selection state, this
33962 is either mSelectedTickLabelColor or mTickLabelColor.
33963*/
33965{
33966 return mSelectedParts.testFlag(spTickLabels) ? mSelectedTickLabelColor : mTickLabelColor;
33967}
33968
33969/*! \internal
33970
33971 Returns the color that is used to draw the axis label. Depending on the selection state, this
33972 is either mSelectedLabelColor or mLabelColor.
33973*/
33975{
33976 return mSelectedParts.testFlag(spAxisLabel) ? mSelectedLabelColor : mLabelColor;
33977}
33978
33979/*! \internal
33980
33981 Event handler for when a mouse button is pressed on the axis rect. If the left mouse button is
33982 pressed, the range dragging interaction is initialized (the actual range manipulation happens in
33983 the \ref mouseMoveEvent).
33984
33985 The mDragging flag is set to true and some anchor points are set that are needed to determine the
33986 distance the mouse was dragged in the mouse move/release events later.
33987
33988 \see mouseMoveEvent, mouseReleaseEvent
33989*/
33991{
33992 Q_UNUSED(details)
33993 if (event->buttons() & Qt::LeftButton)
33994 {
33995 mDragging = true;
33996 // initialize antialiasing backup in case we start dragging:
33997 if (mParentPlot->noAntialiasingOnDrag())
33998 {
33999 mAADragBackup = mParentPlot->antialiasedElements();
34000 mNotAADragBackup = mParentPlot->notAntialiasedElements();
34001 }
34002 // Mouse range dragging interaction:
34003 if (mParentPlot->interactions().testFlag(QCP::iRangeDrag))
34004 {
34005 mDragAngularStart = range();
34006 mDragRadialStart.clear();
34007 for (int i=0; i<mRadialAxes.size(); ++i)
34008 mDragRadialStart.append(mRadialAxes.at(i)->range());
34009 }
34010 }
34011}
34012
34013/*! \internal
34014
34015 Event handler for when the mouse is moved on the axis rect. If range dragging was activated in a
34016 preceding \ref mousePressEvent, the range is moved accordingly.
34017
34018 \see mousePressEvent, mouseReleaseEvent
34019*/
34021{
34022 Q_UNUSED(startPos)
34023 bool doReplot = false;
34024 // Mouse range dragging interaction:
34025 if (mDragging && mParentPlot->interactions().testFlag(QCP::iRangeDrag))
34026 {
34027 if (mRangeDrag)
34028 {
34029 doReplot = true;
34030 double angleCoordStart, radiusCoordStart;
34031 double angleCoord, radiusCoord;
34032 pixelToCoord(startPos, angleCoordStart, radiusCoordStart);
34033 pixelToCoord(event->pos(), angleCoord, radiusCoord);
34034 double diff = angleCoordStart - angleCoord;
34035 setRange(mDragAngularStart.lower+diff, mDragAngularStart.upper+diff);
34036 }
34037
34038 for (int i=0; i<mRadialAxes.size(); ++i)
34039 {
34040 QCPPolarAxisRadial *ax = mRadialAxes.at(i);
34041 if (!ax->rangeDrag())
34042 continue;
34043 doReplot = true;
34044 double angleCoordStart, radiusCoordStart;
34045 double angleCoord, radiusCoord;
34046 ax->pixelToCoord(startPos, angleCoordStart, radiusCoordStart);
34047 ax->pixelToCoord(event->pos(), angleCoord, radiusCoord);
34048 if (ax->scaleType() == QCPPolarAxisRadial::stLinear)
34049 {
34050 double diff = radiusCoordStart - radiusCoord;
34051 ax->setRange(mDragRadialStart.at(i).lower+diff, mDragRadialStart.at(i).upper+diff);
34052 } else if (ax->scaleType() == QCPPolarAxisRadial::stLogarithmic)
34053 {
34054 if (radiusCoord != 0)
34055 {
34056 double diff = radiusCoordStart/radiusCoord;
34057 ax->setRange(mDragRadialStart.at(i).lower*diff, mDragRadialStart.at(i).upper*diff);
34058 }
34059 }
34060 }
34061
34062 if (doReplot) // if either vertical or horizontal drag was enabled, do a replot
34063 {
34064 if (mParentPlot->noAntialiasingOnDrag())
34066 mParentPlot->replot(QCustomPlot::rpQueuedReplot);
34067 }
34068 }
34069}
34070
34071/* inherits documentation from base class */
34073{
34074 Q_UNUSED(event)
34075 Q_UNUSED(startPos)
34076 mDragging = false;
34077 if (mParentPlot->noAntialiasingOnDrag())
34078 {
34079 mParentPlot->setAntialiasedElements(mAADragBackup);
34080 mParentPlot->setNotAntialiasedElements(mNotAADragBackup);
34081 }
34082}
34083
34084/*! \internal
34085
34086 Event handler for mouse wheel events. If rangeZoom is Qt::Horizontal, Qt::Vertical or both, the
34087 ranges of the axes defined as rangeZoomHorzAxis and rangeZoomVertAxis are scaled. The center of
34088 the scaling operation is the current cursor position inside the axis rect. The scaling factor is
34089 dependent on the mouse wheel delta (which direction the wheel was rotated) to provide a natural
34090 zooming feel. The Strength of the zoom can be controlled via \ref setRangeZoomFactor.
34091
34092 Note, that event->delta() is usually +/-120 for single rotation steps. However, if the mouse
34093 wheel is turned rapidly, many steps may bunch up to one event, so the event->delta() may then be
34094 multiples of 120. This is taken into account here, by calculating \a wheelSteps and using it as
34095 exponent of the range zoom factor. This takes care of the wheel direction automatically, by
34096 inverting the factor, when the wheel step is negative (f^-1 = 1/f).
34097*/
34099{
34100 bool doReplot = false;
34101 // Mouse range zooming interaction:
34102 if (mParentPlot->interactions().testFlag(QCP::iRangeZoom))
34103 {
34104#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
34105 const double delta = event->delta();
34106#else
34107 const double delta = event->angleDelta().y();
34108#endif
34109
34110#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
34111 const QPointF pos = event->pos();
34112#else
34113 const QPointF pos = event->position();
34114#endif
34115 const double wheelSteps = delta/120.0; // a single step delta is +/-120 usually
34116 if (mRangeZoom)
34117 {
34118 double angleCoord, radiusCoord;
34119 pixelToCoord(pos, angleCoord, radiusCoord);
34120 scaleRange(qPow(mRangeZoomFactor, wheelSteps), angleCoord);
34121 }
34122
34123 for (int i=0; i<mRadialAxes.size(); ++i)
34124 {
34125 QCPPolarAxisRadial *ax = mRadialAxes.at(i);
34126 if (!ax->rangeZoom())
34127 continue;
34128 doReplot = true;
34129 double angleCoord, radiusCoord;
34130 ax->pixelToCoord(pos, angleCoord, radiusCoord);
34131 ax->scaleRange(qPow(ax->rangeZoomFactor(), wheelSteps), radiusCoord);
34132 }
34133 }
34134 if (doReplot)
34135 mParentPlot->replot();
34136}
34137
34138bool QCPPolarAxisAngular::registerPolarGraph(QCPPolarGraph *graph)
34139{
34140 if (mGraphs.contains(graph))
34141 {
34142 qDebug() << Q_FUNC_INFO << "plottable already added:" << reinterpret_cast<quintptr>(graph);
34143 return false;
34144 }
34145 if (graph->keyAxis() != this)
34146 {
34147 qDebug() << Q_FUNC_INFO << "plottable not created with this as axis:" << reinterpret_cast<quintptr>(graph);
34148 return false;
34149 }
34150
34151 mGraphs.append(graph);
34152 // possibly add plottable to legend:
34153 if (mParentPlot->autoAddPlottableToLegend())
34154 graph->addToLegend();
34155 if (!graph->layer()) // usually the layer is already set in the constructor of the plottable (via QCPLayerable constructor)
34156 graph->setLayer(mParentPlot->currentLayer());
34157 return true;
34158}
34159/* end of 'src/polar/layoutelement-angularaxis.cpp' */
34160
34161
34162/* including file 'src/polar/polargrid.cpp' */
34163/* modified 2022-11-06T12:45:57, size 7493 */
34164
34165
34166////////////////////////////////////////////////////////////////////////////////////////////////////
34167//////////////////// QCPPolarGrid
34168////////////////////////////////////////////////////////////////////////////////////////////////////
34169
34170/*! \class QCPPolarGrid
34171 \brief The grid in both angular and radial dimensions for polar plots
34172
34173 \warning In this QCustomPlot version, polar plots are a tech preview. Expect documentation and
34174 functionality to be incomplete, as well as changing public interfaces in the future.
34175*/
34176
34177/*!
34178 Creates a QCPPolarGrid instance and sets default values.
34179
34180 You shouldn't instantiate grids on their own, since every axis brings its own grid.
34181*/
34183 QCPLayerable(parentAxis->parentPlot(), QString(), parentAxis),
34184 mType(gtNone),
34185 mSubGridType(gtNone),
34186 mAntialiasedSubGrid(true),
34187 mAntialiasedZeroLine(true),
34188 mParentAxis(parentAxis)
34189{
34190 // warning: this is called in QCPPolarAxisAngular constructor, so parentAxis members should not be accessed/called
34191 setParent(parentAxis);
34192 setType(gtAll);
34193 setSubGridType(gtNone);
34194
34195 setAngularPen(QPen(QColor(200,200,200), 0, Qt::DotLine));
34196 setAngularSubGridPen(QPen(QColor(220,220,220), 0, Qt::DotLine));
34197
34198 setRadialPen(QPen(QColor(200,200,200), 0, Qt::DotLine));
34199 setRadialSubGridPen(QPen(QColor(220,220,220), 0, Qt::DotLine));
34200 setRadialZeroLinePen(QPen(QColor(200,200,200), 0, Qt::SolidLine));
34201
34202 setAntialiased(true);
34203}
34204
34205void QCPPolarGrid::setRadialAxis(QCPPolarAxisRadial *axis)
34206{
34207 mRadialAxis = axis;
34208}
34209
34210void QCPPolarGrid::setType(GridTypes type)
34211{
34212 mType = type;
34213}
34214
34215void QCPPolarGrid::setSubGridType(GridTypes type)
34216{
34217 mSubGridType = type;
34218}
34219
34220/*!
34221 Sets whether sub grid lines are drawn antialiased.
34222*/
34224{
34225 mAntialiasedSubGrid = enabled;
34226}
34227
34228/*!
34229 Sets whether zero lines are drawn antialiased.
34230*/
34232{
34233 mAntialiasedZeroLine = enabled;
34234}
34235
34236/*!
34237 Sets the pen with which (major) grid lines are drawn.
34238*/
34240{
34241 mAngularPen = pen;
34242}
34243
34244/*!
34245 Sets the pen with which sub grid lines are drawn.
34246*/
34248{
34249 mAngularSubGridPen = pen;
34250}
34251
34252void QCPPolarGrid::setRadialPen(const QPen &pen)
34253{
34254 mRadialPen = pen;
34255}
34256
34257void QCPPolarGrid::setRadialSubGridPen(const QPen &pen)
34258{
34259 mRadialSubGridPen = pen;
34260}
34261
34262void QCPPolarGrid::setRadialZeroLinePen(const QPen &pen)
34263{
34264 mRadialZeroLinePen = pen;
34265}
34266
34267/*! \internal
34268
34269 A convenience function to easily set the QPainter::Antialiased hint on the provided \a painter
34270 before drawing the major grid lines.
34271
34272 This is the antialiasing state the painter passed to the \ref draw method is in by default.
34273
34274 This function takes into account the local setting of the antialiasing flag as well as the
34275 overrides set with \ref QCustomPlot::setAntialiasedElements and \ref
34276 QCustomPlot::setNotAntialiasedElements.
34277
34278 \see setAntialiased
34279*/
34281{
34282 applyAntialiasingHint(painter, mAntialiased, QCP::aeGrid);
34283}
34284
34285/*! \internal
34286
34287 Draws grid lines and sub grid lines at the positions of (sub) ticks of the parent axis, spanning
34288 over the complete axis rect. Also draws the zero line, if appropriate (\ref setZeroLinePen).
34289*/
34291{
34292 if (!mParentAxis) { qDebug() << Q_FUNC_INFO << "invalid parent axis"; return; }
34293
34294 const QPointF center = mParentAxis->mCenter;
34295 const double radius = mParentAxis->mRadius;
34296
34297 painter->setBrush(Qt::NoBrush);
34298 // draw main angular grid:
34299 if (mType.testFlag(gtAngular))
34300 drawAngularGrid(painter, center, radius, mParentAxis->mTickVectorCosSin, mAngularPen);
34301 // draw main radial grid:
34302 if (mType.testFlag(gtRadial) && mRadialAxis)
34303 drawRadialGrid(painter, center, mRadialAxis->tickVector(), mRadialPen, mRadialZeroLinePen);
34304
34305 applyAntialiasingHint(painter, mAntialiasedSubGrid, QCP::aeGrid);
34306 // draw sub angular grid:
34307 if (mSubGridType.testFlag(gtAngular))
34308 drawAngularGrid(painter, center, radius, mParentAxis->mSubTickVectorCosSin, mAngularSubGridPen);
34309 // draw sub radial grid:
34310 if (mSubGridType.testFlag(gtRadial) && mRadialAxis)
34311 drawRadialGrid(painter, center, mRadialAxis->subTickVector(), mRadialSubGridPen);
34312}
34313
34314void QCPPolarGrid::drawRadialGrid(QCPPainter *painter, const QPointF &center, const QVector<double> &coords, const QPen &pen, const QPen &zeroPen)
34315{
34316 if (!mRadialAxis) return;
34317 if (coords.isEmpty()) return;
34318 const bool drawZeroLine = zeroPen != Qt::NoPen;
34319 const double zeroLineEpsilon = qAbs(coords.last()-coords.first())*1e-6;
34320
34321 painter->setPen(pen);
34322 for (int i=0; i<coords.size(); ++i)
34323 {
34324 const double r = mRadialAxis->coordToRadius(coords.at(i));
34325 if (drawZeroLine && qAbs(coords.at(i)) < zeroLineEpsilon)
34326 {
34327 applyAntialiasingHint(painter, mAntialiasedZeroLine, QCP::aeZeroLine);
34328 painter->setPen(zeroPen);
34329 painter->drawEllipse(center, r, r);
34330 painter->setPen(pen);
34332 } else
34333 {
34334 painter->drawEllipse(center, r, r);
34335 }
34336 }
34337}
34338
34339void QCPPolarGrid::drawAngularGrid(QCPPainter *painter, const QPointF &center, double radius, const QVector<QPointF> &ticksCosSin, const QPen &pen)
34340{
34341 if (ticksCosSin.isEmpty()) return;
34342
34343 painter->setPen(pen);
34344 for (int i=0; i<ticksCosSin.size(); ++i)
34345 painter->drawLine(center, center+ticksCosSin.at(i)*radius);
34346}
34347/* end of 'src/polar/polargrid.cpp' */
34348
34349
34350/* including file 'src/polar/polargraph.cpp' */
34351/* modified 2022-11-06T12:45:57, size 44035 */
34352
34353
34354////////////////////////////////////////////////////////////////////////////////////////////////////
34355//////////////////// QCPPolarLegendItem
34356////////////////////////////////////////////////////////////////////////////////////////////////////
34357
34358/*! \class QCPPolarLegendItem
34359 \brief A legend item for polar plots
34360
34361 \warning In this QCustomPlot version, polar plots are a tech preview. Expect documentation and
34362 functionality to be incomplete, as well as changing public interfaces in the future.
34363*/
34364QCPPolarLegendItem::QCPPolarLegendItem(QCPLegend *parent, QCPPolarGraph *graph) :
34365 QCPAbstractLegendItem(parent),
34366 mPolarGraph(graph)
34367{
34368 setAntialiased(false);
34369}
34370
34372{
34373 if (!mPolarGraph) return;
34374 painter->setFont(getFont());
34375 painter->setPen(QPen(getTextColor()));
34376 QSizeF iconSize = mParentLegend->iconSize();
34377 QRectF textRect = painter->fontMetrics().boundingRect(0, 0, 0, iconSize.height(), Qt::TextDontClip, mPolarGraph->name());
34378 QRectF iconRect(mRect.topLeft(), iconSize);
34379 int textHeight = qMax(textRect.height(), iconSize.height()); // if text has smaller height than icon, center text vertically in icon height, else align tops
34380 painter->drawText(mRect.x()+iconSize.width()+mParentLegend->iconTextPadding(), mRect.y(), textRect.width(), textHeight, Qt::TextDontClip, mPolarGraph->name());
34381 // draw icon:
34382 painter->save();
34383 painter->setClipRect(iconRect, Qt::IntersectClip);
34384 mPolarGraph->drawLegendIcon(painter, iconRect);
34385 painter->restore();
34386 // draw icon border:
34387 if (getIconBorderPen().style() != Qt::NoPen)
34388 {
34389 painter->setPen(getIconBorderPen());
34390 painter->setBrush(Qt::NoBrush);
34391 int halfPen = qCeil(painter->pen().widthF()*0.5)+1;
34392 painter->setClipRect(mOuterRect.adjusted(-halfPen, -halfPen, halfPen, halfPen)); // extend default clip rect so thicker pens (especially during selection) are not clipped
34393 painter->drawRect(iconRect);
34394 }
34395}
34396
34398{
34399 if (!mPolarGraph) return QSize();
34400 QSize result(0, 0);
34401 QRect textRect;
34402 QFontMetrics fontMetrics(getFont());
34403 QSize iconSize = mParentLegend->iconSize();
34404 textRect = fontMetrics.boundingRect(0, 0, 0, iconSize.height(), Qt::TextDontClip, mPolarGraph->name());
34405 result.setWidth(iconSize.width() + mParentLegend->iconTextPadding() + textRect.width());
34406 result.setHeight(qMax(textRect.height(), iconSize.height()));
34407 result.rwidth() += mMargins.left()+mMargins.right();
34408 result.rheight() += mMargins.top()+mMargins.bottom();
34409 return result;
34410}
34411
34412QPen QCPPolarLegendItem::getIconBorderPen() const
34413{
34414 return mSelected ? mParentLegend->selectedIconBorderPen() : mParentLegend->iconBorderPen();
34415}
34416
34417QColor QCPPolarLegendItem::getTextColor() const
34418{
34419 return mSelected ? mSelectedTextColor : mTextColor;
34420}
34421
34422QFont QCPPolarLegendItem::getFont() const
34423{
34424 return mSelected ? mSelectedFont : mFont;
34425}
34426
34427
34428////////////////////////////////////////////////////////////////////////////////////////////////////
34429//////////////////// QCPPolarGraph
34430////////////////////////////////////////////////////////////////////////////////////////////////////
34431
34432/*! \class QCPPolarGraph
34433 \brief A radial graph used to display data in polar plots
34434
34435 \warning In this QCustomPlot version, polar plots are a tech preview. Expect documentation and
34436 functionality to be incomplete, as well as changing public interfaces in the future.
34437*/
34438
34439/* start of documentation of inline functions */
34440
34441// TODO
34442
34443/* end of documentation of inline functions */
34444
34445/*!
34446 Constructs a graph which uses \a keyAxis as its angular and \a valueAxis as its radial axis. \a
34447 keyAxis and \a valueAxis must reside in the same QCustomPlot, and the radial axis must be
34448 associated with the angular axis. If either of these restrictions is violated, a corresponding
34449 message is printed to the debug output (qDebug), the construction is not aborted, though.
34450
34451 The created QCPPolarGraph is automatically registered with the QCustomPlot instance inferred from
34452 \a keyAxis. This QCustomPlot instance takes ownership of the QCPPolarGraph, so do not delete it
34453 manually but use QCPPolarAxisAngular::removeGraph() instead.
34454
34455 To directly create a QCPPolarGraph inside a plot, you shoud use the QCPPolarAxisAngular::addGraph
34456 method.
34457*/
34459 QCPLayerable(keyAxis->parentPlot(), QString(), keyAxis),
34460 mDataContainer(new QCPGraphDataContainer),
34461 mName(),
34462 mAntialiasedFill(true),
34463 mAntialiasedScatters(true),
34464 mPen(Qt::black),
34465 mBrush(Qt::NoBrush),
34466 mPeriodic(true),
34467 mKeyAxis(keyAxis),
34468 mValueAxis(valueAxis),
34469 mSelectable(QCP::stWhole)
34470 //mSelectionDecorator(0) // TODO
34471{
34472 if (keyAxis->parentPlot() != valueAxis->parentPlot())
34473 qDebug() << Q_FUNC_INFO << "Parent plot of keyAxis is not the same as that of valueAxis.";
34474
34475 mKeyAxis->registerPolarGraph(this);
34476
34477 //setSelectionDecorator(new QCPSelectionDecorator); // TODO
34478
34479 setPen(QPen(Qt::blue, 0));
34482}
34483
34484QCPPolarGraph::~QCPPolarGraph()
34485{
34486 /* TODO
34487 if (mSelectionDecorator)
34488 {
34489 delete mSelectionDecorator;
34490 mSelectionDecorator = 0;
34491 }
34492 */
34493}
34494
34495/*!
34496 The name is the textual representation of this plottable as it is displayed in the legend
34497 (\ref QCPLegend). It may contain any UTF-8 characters, including newlines.
34498*/
34500{
34501 mName = name;
34502}
34503
34504/*!
34505 Sets whether fills of this plottable are drawn antialiased or not.
34506
34507 Note that this setting may be overridden by \ref QCustomPlot::setAntialiasedElements and \ref
34508 QCustomPlot::setNotAntialiasedElements.
34509*/
34511{
34512 mAntialiasedFill = enabled;
34513}
34514
34515/*!
34516 Sets whether the scatter symbols of this plottable are drawn antialiased or not.
34517
34518 Note that this setting may be overridden by \ref QCustomPlot::setAntialiasedElements and \ref
34519 QCustomPlot::setNotAntialiasedElements.
34520*/
34522{
34523 mAntialiasedScatters = enabled;
34524}
34525
34526/*!
34527 The pen is used to draw basic lines that make up the plottable representation in the
34528 plot.
34529
34530 For example, the \ref QCPGraph subclass draws its graph lines with this pen.
34531
34532 \see setBrush
34533*/
34535{
34536 mPen = pen;
34537}
34538
34539/*!
34540 The brush is used to draw basic fills of the plottable representation in the
34541 plot. The Fill can be a color, gradient or texture, see the usage of QBrush.
34542
34543 For example, the \ref QCPGraph subclass draws the fill under the graph with this brush, when
34544 it's not set to Qt::NoBrush.
34545
34546 \see setPen
34547*/
34549{
34550 mBrush = brush;
34551}
34552
34553void QCPPolarGraph::setPeriodic(bool enabled)
34554{
34555 mPeriodic = enabled;
34556}
34557
34558/*!
34559 The key axis of a plottable can be set to any axis of a QCustomPlot, as long as it is orthogonal
34560 to the plottable's value axis. This function performs no checks to make sure this is the case.
34561 The typical mathematical choice is to use the x-axis (QCustomPlot::xAxis) as key axis and the
34562 y-axis (QCustomPlot::yAxis) as value axis.
34563
34564 Normally, the key and value axes are set in the constructor of the plottable (or \ref
34565 QCustomPlot::addGraph when working with QCPGraphs through the dedicated graph interface).
34566
34567 \see setValueAxis
34568*/
34570{
34571 mKeyAxis = axis;
34572}
34573
34574/*!
34575 The value axis of a plottable can be set to any axis of a QCustomPlot, as long as it is
34576 orthogonal to the plottable's key axis. This function performs no checks to make sure this is the
34577 case. The typical mathematical choice is to use the x-axis (QCustomPlot::xAxis) as key axis and
34578 the y-axis (QCustomPlot::yAxis) as value axis.
34579
34580 Normally, the key and value axes are set in the constructor of the plottable (or \ref
34581 QCustomPlot::addGraph when working with QCPGraphs through the dedicated graph interface).
34582
34583 \see setKeyAxis
34584*/
34586{
34587 mValueAxis = axis;
34588}
34589
34590/*!
34591 Sets whether and to which granularity this plottable can be selected.
34592
34593 A selection can happen by clicking on the QCustomPlot surface (When \ref
34594 QCustomPlot::setInteractions contains \ref QCP::iSelectPlottables), by dragging a selection rect
34595 (When \ref QCustomPlot::setSelectionRectMode is \ref QCP::srmSelect), or programmatically by
34596 calling \ref setSelection.
34597
34598 \see setSelection, QCP::SelectionType
34599*/
34601{
34602 if (mSelectable != selectable)
34603 {
34604 mSelectable = selectable;
34605 QCPDataSelection oldSelection = mSelection;
34606 mSelection.enforceType(mSelectable);
34607 emit selectableChanged(mSelectable);
34608 if (mSelection != oldSelection)
34609 {
34610 emit selectionChanged(selected());
34611 emit selectionChanged(mSelection);
34612 }
34613 }
34614}
34615
34616/*!
34617 Sets which data ranges of this plottable are selected. Selected data ranges are drawn differently
34618 (e.g. color) in the plot. This can be controlled via the selection decorator (see \ref
34619 selectionDecorator).
34620
34621 The entire selection mechanism for plottables is handled automatically when \ref
34622 QCustomPlot::setInteractions contains iSelectPlottables. You only need to call this function when
34623 you wish to change the selection state programmatically.
34624
34625 Using \ref setSelectable you can further specify for each plottable whether and to which
34626 granularity it is selectable. If \a selection is not compatible with the current \ref
34627 QCP::SelectionType set via \ref setSelectable, the resulting selection will be adjusted
34628 accordingly (see \ref QCPDataSelection::enforceType).
34629
34630 emits the \ref selectionChanged signal when \a selected is different from the previous selection state.
34631
34632 \see setSelectable, selectTest
34633*/
34635{
34636 selection.enforceType(mSelectable);
34637 if (mSelection != selection)
34638 {
34639 mSelection = selection;
34640 emit selectionChanged(selected());
34641 emit selectionChanged(mSelection);
34642 }
34643}
34644
34645/*! \overload
34646
34647 Replaces the current data container with the provided \a data container.
34648
34649 Since a QSharedPointer is used, multiple QCPPolarGraphs may share the same data container safely.
34650 Modifying the data in the container will then affect all graphs that share the container. Sharing
34651 can be achieved by simply exchanging the data containers wrapped in shared pointers:
34652 \snippet documentation/doc-code-snippets/mainwindow.cpp QCPPolarGraph-datasharing-1
34653
34654 If you do not wish to share containers, but create a copy from an existing container, rather use
34655 the \ref QCPDataContainer<DataType>::set method on the graph's data container directly:
34656 \snippet documentation/doc-code-snippets/mainwindow.cpp QCPPolarGraph-datasharing-2
34657
34658 \see addData
34659*/
34661{
34662 mDataContainer = data;
34663}
34664
34665/*! \overload
34666
34667 Replaces the current data with the provided points in \a keys and \a values. The provided
34668 vectors should have equal length. Else, the number of added points will be the size of the
34669 smallest vector.
34670
34671 If you can guarantee that the passed data points are sorted by \a keys in ascending order, you
34672 can set \a alreadySorted to true, to improve performance by saving a sorting run.
34673
34674 \see addData
34675*/
34676void QCPPolarGraph::setData(const QVector<double> &keys, const QVector<double> &values, bool alreadySorted)
34677{
34678 mDataContainer->clear();
34679 addData(keys, values, alreadySorted);
34680}
34681
34682/*!
34683 Sets how the single data points are connected in the plot. For scatter-only plots, set \a ls to
34684 \ref lsNone and \ref setScatterStyle to the desired scatter style.
34685
34686 \see setScatterStyle
34687*/
34689{
34690 mLineStyle = ls;
34691}
34692
34693/*!
34694 Sets the visual appearance of single data points in the plot. If set to \ref QCPScatterStyle::ssNone, no scatter points
34695 are drawn (e.g. for line-only-plots with appropriate line style).
34696
34697 \see QCPScatterStyle, setLineStyle
34698*/
34700{
34701 mScatterStyle = style;
34702}
34703
34704void QCPPolarGraph::addData(const QVector<double> &keys, const QVector<double> &values, bool alreadySorted)
34705{
34706 if (keys.size() != values.size())
34707 qDebug() << Q_FUNC_INFO << "keys and values have different sizes:" << keys.size() << values.size();
34708 const int n = qMin(keys.size(), values.size());
34709 QVector<QCPGraphData> tempData(n);
34710 QVector<QCPGraphData>::iterator it = tempData.begin();
34711 const QVector<QCPGraphData>::iterator itEnd = tempData.end();
34712 int i = 0;
34713 while (it != itEnd)
34714 {
34715 it->key = keys[i];
34716 it->value = values[i];
34717 ++it;
34718 ++i;
34719 }
34720 mDataContainer->add(tempData, alreadySorted); // don't modify tempData beyond this to prevent copy on write
34721}
34722
34723void QCPPolarGraph::addData(double key, double value)
34724{
34725 mDataContainer->add(QCPGraphData(key, value));
34726}
34727
34728/*!
34729 Use this method to set an own QCPSelectionDecorator (subclass) instance. This allows you to
34730 customize the visual representation of selected data ranges further than by using the default
34731 QCPSelectionDecorator.
34732
34733 The plottable takes ownership of the \a decorator.
34734
34735 The currently set decorator can be accessed via \ref selectionDecorator.
34736*/
34737/*
34738void QCPPolarGraph::setSelectionDecorator(QCPSelectionDecorator *decorator)
34739{
34740 if (decorator)
34741 {
34742 if (decorator->registerWithPlottable(this))
34743 {
34744 if (mSelectionDecorator) // delete old decorator if necessary
34745 delete mSelectionDecorator;
34746 mSelectionDecorator = decorator;
34747 }
34748 } else if (mSelectionDecorator) // just clear decorator
34749 {
34750 delete mSelectionDecorator;
34751 mSelectionDecorator = 0;
34752 }
34753}
34754*/
34755
34756void QCPPolarGraph::coordsToPixels(double key, double value, double &x, double &y) const
34757{
34758 if (mValueAxis)
34759 {
34760 const QPointF point = mValueAxis->coordToPixel(key, value);
34761 x = point.x();
34762 y = point.y();
34763 } else
34764 {
34765 qDebug() << Q_FUNC_INFO << "invalid key or value axis";
34766 }
34767}
34768
34769const QPointF QCPPolarGraph::coordsToPixels(double key, double value) const
34770{
34771 if (mValueAxis)
34772 {
34773 return mValueAxis->coordToPixel(key, value);
34774 } else
34775 {
34776 qDebug() << Q_FUNC_INFO << "invalid key or value axis";
34777 return QPointF();
34778 }
34779}
34780
34781void QCPPolarGraph::pixelsToCoords(double x, double y, double &key, double &value) const
34782{
34783 if (mValueAxis)
34784 {
34785 mValueAxis->pixelToCoord(QPointF(x, y), key, value);
34786 } else
34787 {
34788 qDebug() << Q_FUNC_INFO << "invalid key or value axis";
34789 }
34790}
34791
34792void QCPPolarGraph::pixelsToCoords(const QPointF &pixelPos, double &key, double &value) const
34793{
34794 if (mValueAxis)
34795 {
34796 mValueAxis->pixelToCoord(pixelPos, key, value);
34797 } else
34798 {
34799 qDebug() << Q_FUNC_INFO << "invalid key or value axis";
34800 }
34801}
34802
34803void QCPPolarGraph::rescaleAxes(bool onlyEnlarge) const
34804{
34805 rescaleKeyAxis(onlyEnlarge);
34806 rescaleValueAxis(onlyEnlarge);
34807}
34808
34809void QCPPolarGraph::rescaleKeyAxis(bool onlyEnlarge) const
34810{
34811 QCPPolarAxisAngular *keyAxis = mKeyAxis.data();
34812 if (!keyAxis) { qDebug() << Q_FUNC_INFO << "invalid key axis"; return; }
34813
34814 bool foundRange;
34815 QCPRange newRange = getKeyRange(foundRange, QCP::sdBoth);
34816 if (foundRange)
34817 {
34818 if (onlyEnlarge)
34819 newRange.expand(keyAxis->range());
34820 if (!QCPRange::validRange(newRange)) // likely due to range being zero (plottable has only constant data in this axis dimension), shift current range to at least center the plottable
34821 {
34822 double center = (newRange.lower+newRange.upper)*0.5; // upper and lower should be equal anyway, but just to make sure, incase validRange returned false for other reason
34823 newRange.lower = center-keyAxis->range().size()/2.0;
34824 newRange.upper = center+keyAxis->range().size()/2.0;
34825 }
34826 keyAxis->setRange(newRange);
34827 }
34828}
34829
34830void QCPPolarGraph::rescaleValueAxis(bool onlyEnlarge, bool inKeyRange) const
34831{
34832 QCPPolarAxisAngular *keyAxis = mKeyAxis.data();
34833 QCPPolarAxisRadial *valueAxis = mValueAxis.data();
34834 if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
34835
34836 QCP::SignDomain signDomain = QCP::sdBoth;
34837 if (valueAxis->scaleType() == QCPPolarAxisRadial::stLogarithmic)
34838 signDomain = (valueAxis->range().upper < 0 ? QCP::sdNegative : QCP::sdPositive);
34839
34840 bool foundRange;
34841 QCPRange newRange = getValueRange(foundRange, signDomain, inKeyRange ? keyAxis->range() : QCPRange());
34842 if (foundRange)
34843 {
34844 if (onlyEnlarge)
34845 newRange.expand(valueAxis->range());
34846 if (!QCPRange::validRange(newRange)) // likely due to range being zero (plottable has only constant data in this axis dimension), shift current range to at least center the plottable
34847 {
34848 double center = (newRange.lower+newRange.upper)*0.5; // upper and lower should be equal anyway, but just to make sure, incase validRange returned false for other reason
34849 if (valueAxis->scaleType() == QCPPolarAxisRadial::stLinear)
34850 {
34851 newRange.lower = center-valueAxis->range().size()/2.0;
34852 newRange.upper = center+valueAxis->range().size()/2.0;
34853 } else // scaleType() == stLogarithmic
34854 {
34855 newRange.lower = center/qSqrt(valueAxis->range().upper/valueAxis->range().lower);
34856 newRange.upper = center*qSqrt(valueAxis->range().upper/valueAxis->range().lower);
34857 }
34858 }
34859 valueAxis->setRange(newRange);
34860 }
34861}
34862
34863bool QCPPolarGraph::addToLegend(QCPLegend *legend)
34864{
34865 if (!legend)
34866 {
34867 qDebug() << Q_FUNC_INFO << "passed legend is null";
34868 return false;
34869 }
34870 if (legend->parentPlot() != mParentPlot)
34871 {
34872 qDebug() << Q_FUNC_INFO << "passed legend isn't in the same QCustomPlot as this plottable";
34873 return false;
34874 }
34875
34876 //if (!legend->hasItemWithPlottable(this)) // TODO
34877 //{
34878 legend->addItem(new QCPPolarLegendItem(legend, this));
34879 return true;
34880 //} else
34881 // return false;
34882}
34883
34884bool QCPPolarGraph::addToLegend()
34885{
34886 if (!mParentPlot || !mParentPlot->legend)
34887 return false;
34888 else
34889 return addToLegend(mParentPlot->legend);
34890}
34891
34892bool QCPPolarGraph::removeFromLegend(QCPLegend *legend) const
34893{
34894 if (!legend)
34895 {
34896 qDebug() << Q_FUNC_INFO << "passed legend is null";
34897 return false;
34898 }
34899
34900
34901 QCPPolarLegendItem *removableItem = 0;
34902 for (int i=0; i<legend->itemCount(); ++i) // TODO: reduce this to code in QCPAbstractPlottable::removeFromLegend once unified
34903 {
34905 {
34906 if (pli->polarGraph() == this)
34907 {
34908 removableItem = pli;
34909 break;
34910 }
34911 }
34912 }
34913
34914 if (removableItem)
34915 return legend->removeItem(removableItem);
34916 else
34917 return false;
34918}
34919
34920bool QCPPolarGraph::removeFromLegend() const
34921{
34922 if (!mParentPlot || !mParentPlot->legend)
34923 return false;
34924 else
34925 return removeFromLegend(mParentPlot->legend);
34926}
34927
34928double QCPPolarGraph::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
34929{
34930 if ((onlySelectable && mSelectable == QCP::stNone) || mDataContainer->isEmpty())
34931 return -1;
34932 if (!mKeyAxis || !mValueAxis)
34933 return -1;
34934
34935 if (mKeyAxis->rect().contains(pos.toPoint()))
34936 {
34937 QCPGraphDataContainer::const_iterator closestDataPoint = mDataContainer->constEnd();
34938 double result = pointDistance(pos, closestDataPoint);
34939 if (details)
34940 {
34941 int pointIndex = closestDataPoint-mDataContainer->constBegin();
34942 details->setValue(QCPDataSelection(QCPDataRange(pointIndex, pointIndex+1)));
34943 }
34944 return result;
34945 } else
34946 return -1;
34947}
34948
34949/* inherits documentation from base class */
34950QCPRange QCPPolarGraph::getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain) const
34951{
34952 return mDataContainer->keyRange(foundRange, inSignDomain);
34953}
34954
34955/* inherits documentation from base class */
34956QCPRange QCPPolarGraph::getValueRange(bool &foundRange, QCP::SignDomain inSignDomain, const QCPRange &inKeyRange) const
34957{
34958 return mDataContainer->valueRange(foundRange, inSignDomain, inKeyRange);
34959}
34960
34961/* inherits documentation from base class */
34963{
34964 if (mKeyAxis)
34965 return mKeyAxis.data()->rect();
34966 else
34967 return QRect();
34968}
34969
34971{
34972 if (!mKeyAxis || !mValueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
34973 if (mKeyAxis.data()->range().size() <= 0 || mDataContainer->isEmpty()) return;
34974 if (mLineStyle == lsNone && mScatterStyle.isNone()) return;
34975
34976 painter->setClipRegion(mKeyAxis->exactClipRegion());
34977
34978 QVector<QPointF> lines, scatters; // line and (if necessary) scatter pixel coordinates will be stored here while iterating over segments
34979
34980 // loop over and draw segments of unselected/selected data:
34981 QList<QCPDataRange> selectedSegments, unselectedSegments, allSegments;
34982 getDataSegments(selectedSegments, unselectedSegments);
34983 allSegments << unselectedSegments << selectedSegments;
34984 for (int i=0; i<allSegments.size(); ++i)
34985 {
34986 bool isSelectedSegment = i >= unselectedSegments.size();
34987 // get line pixel points appropriate to line style:
34988 QCPDataRange lineDataRange = isSelectedSegment ? allSegments.at(i) : allSegments.at(i).adjusted(-1, 1); // unselected segments extend lines to bordering selected data point (safe to exceed total data bounds in first/last segment, getLines takes care)
34989 getLines(&lines, lineDataRange);
34990
34991 // check data validity if flag set:
34992#ifdef QCUSTOMPLOT_CHECK_DATA
34994 for (it = mDataContainer->constBegin(); it != mDataContainer->constEnd(); ++it)
34995 {
34996 if (QCP::isInvalidData(it->key, it->value))
34997 qDebug() << Q_FUNC_INFO << "Data point at" << it->key << "invalid." << "Plottable name:" << name();
34998 }
34999#endif
35000
35001 // draw fill of graph:
35002 //if (isSelectedSegment && mSelectionDecorator)
35003 // mSelectionDecorator->applyBrush(painter);
35004 //else
35005 painter->setBrush(mBrush);
35006 painter->setPen(Qt::NoPen);
35007 drawFill(painter, &lines);
35008
35009
35010 // draw line:
35011 if (mLineStyle != lsNone)
35012 {
35013 //if (isSelectedSegment && mSelectionDecorator)
35014 // mSelectionDecorator->applyPen(painter);
35015 //else
35016 painter->setPen(mPen);
35017 painter->setBrush(Qt::NoBrush);
35018 drawLinePlot(painter, lines);
35019 }
35020
35021 // draw scatters:
35022
35023 QCPScatterStyle finalScatterStyle = mScatterStyle;
35024 //if (isSelectedSegment && mSelectionDecorator)
35025 // finalScatterStyle = mSelectionDecorator->getFinalScatterStyle(mScatterStyle);
35026 if (!finalScatterStyle.isNone())
35027 {
35028 getScatters(&scatters, allSegments.at(i));
35029 drawScatterPlot(painter, scatters, finalScatterStyle);
35030 }
35031 }
35032
35033 // draw other selection decoration that isn't just line/scatter pens and brushes:
35034 //if (mSelectionDecorator)
35035 // mSelectionDecorator->drawDecoration(painter, selection());
35036}
35037
35042
35044{
35045 applyAntialiasingHint(painter, mAntialiased, QCP::aePlottables);
35046}
35047
35048/* inherits documentation from base class */
35049void QCPPolarGraph::selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged)
35050{
35051 Q_UNUSED(event)
35052
35053 if (mSelectable != QCP::stNone)
35054 {
35055 QCPDataSelection newSelection = details.value<QCPDataSelection>();
35056 QCPDataSelection selectionBefore = mSelection;
35057 if (additive)
35058 {
35059 if (mSelectable == QCP::stWhole) // in whole selection mode, we toggle to no selection even if currently unselected point was hit
35060 {
35061 if (selected())
35063 else
35064 setSelection(newSelection);
35065 } else // in all other selection modes we toggle selections of homogeneously selected/unselected segments
35066 {
35067 if (mSelection.contains(newSelection)) // if entire newSelection is already selected, toggle selection
35068 setSelection(mSelection-newSelection);
35069 else
35070 setSelection(mSelection+newSelection);
35071 }
35072 } else
35073 setSelection(newSelection);
35074 if (selectionStateChanged)
35075 *selectionStateChanged = mSelection != selectionBefore;
35076 }
35077}
35078
35079/* inherits documentation from base class */
35080void QCPPolarGraph::deselectEvent(bool *selectionStateChanged)
35081{
35082 if (mSelectable != QCP::stNone)
35083 {
35084 QCPDataSelection selectionBefore = mSelection;
35086 if (selectionStateChanged)
35087 *selectionStateChanged = mSelection != selectionBefore;
35088 }
35089}
35090
35091/*! \internal
35092
35093 Draws lines between the points in \a lines, given in pixel coordinates.
35094
35095 \see drawScatterPlot, drawImpulsePlot, QCPAbstractPlottable1D::drawPolyline
35096*/
35098{
35099 if (painter->pen().style() != Qt::NoPen && painter->pen().color().alpha() != 0)
35100 {
35102 drawPolyline(painter, lines);
35103 }
35104}
35105
35106/*! \internal
35107
35108 Draws the fill of the graph using the specified \a painter, with the currently set brush.
35109
35110 Depending on whether a normal fill or a channel fill (\ref setChannelFillGraph) is needed, \ref
35111 getFillPolygon or \ref getChannelFillPolygon are used to find the according fill polygons.
35112
35113 In order to handle NaN Data points correctly (the fill needs to be split into disjoint areas),
35114 this method first determines a list of non-NaN segments with \ref getNonNanSegments, on which to
35115 operate. In the channel fill case, \ref getOverlappingSegments is used to consolidate the non-NaN
35116 segments of the two involved graphs, before passing the overlapping pairs to \ref
35117 getChannelFillPolygon.
35118
35119 Pass the points of this graph's line as \a lines, in pixel coordinates.
35120
35121 \see drawLinePlot, drawImpulsePlot, drawScatterPlot
35122*/
35124{
35125 applyFillAntialiasingHint(painter);
35126 if (painter->brush().style() != Qt::NoBrush && painter->brush().color().alpha() != 0)
35127 painter->drawPolygon(QPolygonF(*lines));
35128}
35129
35130/*! \internal
35131
35132 Draws scatter symbols at every point passed in \a scatters, given in pixel coordinates. The
35133 scatters will be drawn with \a painter and have the appearance as specified in \a style.
35134
35135 \see drawLinePlot, drawImpulsePlot
35136*/
35137void QCPPolarGraph::drawScatterPlot(QCPPainter *painter, const QVector<QPointF> &scatters, const QCPScatterStyle &style) const
35138{
35139 applyScattersAntialiasingHint(painter);
35140 style.applyTo(painter, mPen);
35141 for (int i=0; i<scatters.size(); ++i)
35142 style.drawShape(painter, scatters.at(i).x(), scatters.at(i).y());
35143}
35144
35145void QCPPolarGraph::drawLegendIcon(QCPPainter *painter, const QRectF &rect) const
35146{
35147 // draw fill:
35148 if (mBrush.style() != Qt::NoBrush)
35149 {
35150 applyFillAntialiasingHint(painter);
35151 painter->fillRect(QRectF(rect.left(), rect.top()+rect.height()/2.0, rect.width(), rect.height()/3.0), mBrush);
35152 }
35153 // draw line vertically centered:
35154 if (mLineStyle != lsNone)
35155 {
35157 painter->setPen(mPen);
35158 painter->drawLine(QLineF(rect.left(), rect.top()+rect.height()/2.0, rect.right()+5, rect.top()+rect.height()/2.0)); // +5 on x2 else last segment is missing from dashed/dotted pens
35159 }
35160 // draw scatter symbol:
35161 if (!mScatterStyle.isNone())
35162 {
35163 applyScattersAntialiasingHint(painter);
35164 // scale scatter pixmap if it's too large to fit in legend icon rect:
35165 if (mScatterStyle.shape() == QCPScatterStyle::ssPixmap && (mScatterStyle.pixmap().size().width() > rect.width() || mScatterStyle.pixmap().size().height() > rect.height()))
35166 {
35167 QCPScatterStyle scaledStyle(mScatterStyle);
35168 scaledStyle.setPixmap(scaledStyle.pixmap().scaled(rect.size().toSize(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
35169 scaledStyle.applyTo(painter, mPen);
35170 scaledStyle.drawShape(painter, QRectF(rect).center());
35171 } else
35172 {
35173 mScatterStyle.applyTo(painter, mPen);
35174 mScatterStyle.drawShape(painter, QRectF(rect).center());
35175 }
35176 }
35177}
35178
35179void QCPPolarGraph::applyFillAntialiasingHint(QCPPainter *painter) const
35180{
35181 applyAntialiasingHint(painter, mAntialiasedFill, QCP::aeFills);
35182}
35183
35184void QCPPolarGraph::applyScattersAntialiasingHint(QCPPainter *painter) const
35185{
35186 applyAntialiasingHint(painter, mAntialiasedScatters, QCP::aeScatters);
35187}
35188
35189double QCPPolarGraph::pointDistance(const QPointF &pixelPoint, QCPGraphDataContainer::const_iterator &closestData) const
35190{
35191 closestData = mDataContainer->constEnd();
35192 if (mDataContainer->isEmpty())
35193 return -1.0;
35194 if (mLineStyle == lsNone && mScatterStyle.isNone())
35195 return -1.0;
35196
35197 // calculate minimum distances to graph data points and find closestData iterator:
35198 double minDistSqr = (std::numeric_limits<double>::max)();
35199 // determine which key range comes into question, taking selection tolerance around pos into account:
35200 double posKeyMin, posKeyMax, dummy;
35201 pixelsToCoords(pixelPoint-QPointF(mParentPlot->selectionTolerance(), mParentPlot->selectionTolerance()), posKeyMin, dummy);
35202 pixelsToCoords(pixelPoint+QPointF(mParentPlot->selectionTolerance(), mParentPlot->selectionTolerance()), posKeyMax, dummy);
35203 if (posKeyMin > posKeyMax)
35204 qSwap(posKeyMin, posKeyMax);
35205 // iterate over found data points and then choose the one with the shortest distance to pos:
35206 QCPGraphDataContainer::const_iterator begin = mDataContainer->findBegin(posKeyMin, true);
35207 QCPGraphDataContainer::const_iterator end = mDataContainer->findEnd(posKeyMax, true);
35208 for (QCPGraphDataContainer::const_iterator it=begin; it!=end; ++it)
35209 {
35210 const double currentDistSqr = QCPVector2D(coordsToPixels(it->key, it->value)-pixelPoint).lengthSquared();
35211 if (currentDistSqr < minDistSqr)
35212 {
35213 minDistSqr = currentDistSqr;
35214 closestData = it;
35215 }
35216 }
35217
35218 // calculate distance to graph line if there is one (if so, will probably be smaller than distance to closest data point):
35219 if (mLineStyle != lsNone)
35220 {
35221 // line displayed, calculate distance to line segments:
35222 QVector<QPointF> lineData;
35223 getLines(&lineData, QCPDataRange(0, dataCount()));
35224 QCPVector2D p(pixelPoint);
35225 for (int i=0; i<lineData.size()-1; ++i)
35226 {
35227 const double currentDistSqr = p.distanceSquaredToLine(lineData.at(i), lineData.at(i+1));
35228 if (currentDistSqr < minDistSqr)
35229 minDistSqr = currentDistSqr;
35230 }
35231 }
35232
35233 return qSqrt(minDistSqr);
35234}
35235
35236int QCPPolarGraph::dataCount() const
35237{
35238 return mDataContainer->size();
35239}
35240
35241void QCPPolarGraph::getDataSegments(QList<QCPDataRange> &selectedSegments, QList<QCPDataRange> &unselectedSegments) const
35242{
35243 selectedSegments.clear();
35244 unselectedSegments.clear();
35245 if (mSelectable == QCP::stWhole) // stWhole selection type draws the entire plottable with selected style if mSelection isn't empty
35246 {
35247 if (selected())
35248 selectedSegments << QCPDataRange(0, dataCount());
35249 else
35250 unselectedSegments << QCPDataRange(0, dataCount());
35251 } else
35252 {
35253 QCPDataSelection sel(selection());
35254 sel.simplify();
35255 selectedSegments = sel.dataRanges();
35256 unselectedSegments = sel.inverse(QCPDataRange(0, dataCount())).dataRanges();
35257 }
35258}
35259
35260void QCPPolarGraph::drawPolyline(QCPPainter *painter, const QVector<QPointF> &lineData) const
35261{
35262 // if drawing solid line and not in PDF, use much faster line drawing instead of polyline:
35263 if (mParentPlot->plottingHints().testFlag(QCP::phFastPolylines) &&
35264 painter->pen().style() == Qt::SolidLine &&
35265 !painter->modes().testFlag(QCPPainter::pmVectorized) &&
35266 !painter->modes().testFlag(QCPPainter::pmNoCaching))
35267 {
35268 int i = 0;
35269 bool lastIsNan = false;
35270 const int lineDataSize = lineData.size();
35271 while (i < lineDataSize && (qIsNaN(lineData.at(i).y()) || qIsNaN(lineData.at(i).x()))) // make sure first point is not NaN
35272 ++i;
35273 ++i; // because drawing works in 1 point retrospect
35274 while (i < lineDataSize)
35275 {
35276 if (!qIsNaN(lineData.at(i).y()) && !qIsNaN(lineData.at(i).x())) // NaNs create a gap in the line
35277 {
35278 if (!lastIsNan)
35279 painter->drawLine(lineData.at(i-1), lineData.at(i));
35280 else
35281 lastIsNan = false;
35282 } else
35283 lastIsNan = true;
35284 ++i;
35285 }
35286 } else
35287 {
35288 int segmentStart = 0;
35289 int i = 0;
35290 const int lineDataSize = lineData.size();
35291 while (i < lineDataSize)
35292 {
35293 if (qIsNaN(lineData.at(i).y()) || qIsNaN(lineData.at(i).x()) || qIsInf(lineData.at(i).y())) // NaNs create a gap in the line. Also filter Infs which make drawPolyline block
35294 {
35295 painter->drawPolyline(lineData.constData()+segmentStart, i-segmentStart); // i, because we don't want to include the current NaN point
35296 segmentStart = i+1;
35297 }
35298 ++i;
35299 }
35300 // draw last segment:
35301 painter->drawPolyline(lineData.constData()+segmentStart, lineDataSize-segmentStart);
35302 }
35303}
35304
35305void QCPPolarGraph::getVisibleDataBounds(QCPGraphDataContainer::const_iterator &begin, QCPGraphDataContainer::const_iterator &end, const QCPDataRange &rangeRestriction) const
35306{
35307 if (rangeRestriction.isEmpty())
35308 {
35309 end = mDataContainer->constEnd();
35310 begin = end;
35311 } else
35312 {
35313 QCPPolarAxisAngular *keyAxis = mKeyAxis.data();
35314 QCPPolarAxisRadial *valueAxis = mValueAxis.data();
35315 if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
35316 // get visible data range:
35317 if (mPeriodic)
35318 {
35319 begin = mDataContainer->constBegin();
35320 end = mDataContainer->constEnd();
35321 } else
35322 {
35323 begin = mDataContainer->findBegin(keyAxis->range().lower);
35324 end = mDataContainer->findEnd(keyAxis->range().upper);
35325 }
35326 // limit lower/upperEnd to rangeRestriction:
35327 mDataContainer->limitIteratorsToDataRange(begin, end, rangeRestriction); // this also ensures rangeRestriction outside data bounds doesn't break anything
35328 }
35329}
35330
35331/*! \internal
35332
35333 This method retrieves an optimized set of data points via \ref getOptimizedLineData, an branches
35334 out to the line style specific functions such as \ref dataToLines, \ref dataToStepLeftLines, etc.
35335 according to the line style of the graph.
35336
35337 \a lines will be filled with points in pixel coordinates, that can be drawn with the according
35338 draw functions like \ref drawLinePlot and \ref drawImpulsePlot. The points returned in \a lines
35339 aren't necessarily the original data points. For example, step line styles require additional
35340 points to form the steps when drawn. If the line style of the graph is \ref lsNone, the \a
35341 lines vector will be empty.
35342
35343 \a dataRange specifies the beginning and ending data indices that will be taken into account for
35344 conversion. In this function, the specified range may exceed the total data bounds without harm:
35345 a correspondingly trimmed data range will be used. This takes the burden off the user of this
35346 function to check for valid indices in \a dataRange, e.g. when extending ranges coming from \ref
35347 getDataSegments.
35348
35349 \see getScatters
35350*/
35351void QCPPolarGraph::getLines(QVector<QPointF> *lines, const QCPDataRange &dataRange) const
35352{
35353 if (!lines) return;
35355 getVisibleDataBounds(begin, end, dataRange);
35356 if (begin == end)
35357 {
35358 lines->clear();
35359 return;
35360 }
35361
35362 QVector<QCPGraphData> lineData;
35363 if (mLineStyle != lsNone)
35364 getOptimizedLineData(&lineData, begin, end);
35365
35366 switch (mLineStyle)
35367 {
35368 case lsNone: lines->clear(); break;
35369 case lsLine: *lines = dataToLines(lineData); break;
35370 }
35371}
35372
35373void QCPPolarGraph::getScatters(QVector<QPointF> *scatters, const QCPDataRange &dataRange) const
35374{
35375 QCPPolarAxisAngular *keyAxis = mKeyAxis.data();
35376 QCPPolarAxisRadial *valueAxis = mValueAxis.data();
35377 if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
35378
35379 if (!scatters) return;
35381 getVisibleDataBounds(begin, end, dataRange);
35382 if (begin == end)
35383 {
35384 scatters->clear();
35385 return;
35386 }
35387
35389 getOptimizedScatterData(&data, begin, end);
35390
35391 scatters->resize(data.size());
35392 for (int i=0; i<data.size(); ++i)
35393 {
35394 if (!qIsNaN(data.at(i).value))
35395 (*scatters)[i] = valueAxis->coordToPixel(data.at(i).key, data.at(i).value);
35396 }
35397}
35398
35399void QCPPolarGraph::getOptimizedLineData(QVector<QCPGraphData> *lineData, const QCPGraphDataContainer::const_iterator &begin, const QCPGraphDataContainer::const_iterator &end) const
35400{
35401 lineData->clear();
35402
35403 // TODO: fix for log axes and thick line style
35404
35405 const QCPRange range = mValueAxis->range();
35406 bool reversed = mValueAxis->rangeReversed();
35407 const double clipMargin = range.size()*0.05; // extra distance from visible circle, so optimized outside lines can cover more angle before having to place a dummy point to prevent tangents
35408 const double upperClipValue = range.upper + (reversed ? 0 : range.size()*0.05+clipMargin); // clip slightly outside of actual range to avoid line thicknesses to peek into visible circle
35409 const double lowerClipValue = range.lower - (reversed ? range.size()*0.05+clipMargin : 0); // clip slightly outside of actual range to avoid line thicknesses to peek into visible circle
35410 const double maxKeySkip = qAsin(qSqrt(clipMargin*(clipMargin+2*range.size()))/(range.size()+clipMargin))/M_PI*mKeyAxis->range().size(); // the maximum angle between two points on outer circle (r=clipValue+clipMargin) before connecting line becomes tangent to inner circle (r=clipValue)
35411 double skipBegin = 0;
35412 bool belowRange = false;
35413 bool aboveRange = false;
35415 while (it != end)
35416 {
35417 if (it->value < lowerClipValue)
35418 {
35419 if (aboveRange) // jumped directly from above to below visible range, draw previous point so entry angle is correct
35420 {
35421 aboveRange = false;
35422 if (!reversed) // TODO: with inner radius, we'll need else case here with projected border point
35423 lineData->append(*(it-1));
35424 }
35425 if (!belowRange)
35426 {
35427 skipBegin = it->key;
35428 lineData->append(QCPGraphData(it->key, lowerClipValue));
35429 belowRange = true;
35430 }
35431 if (it->key-skipBegin > maxKeySkip) // add dummy point if we're exceeding the maximum skippable angle (to prevent unintentional intersections with visible circle)
35432 {
35433 skipBegin += maxKeySkip;
35434 lineData->append(QCPGraphData(skipBegin, lowerClipValue));
35435 }
35436 } else if (it->value > upperClipValue)
35437 {
35438 if (belowRange) // jumped directly from below to above visible range, draw previous point so entry angle is correct (if lower means outer, so if reversed axis)
35439 {
35440 belowRange = false;
35441 if (reversed)
35442 lineData->append(*(it-1));
35443 }
35444 if (!aboveRange)
35445 {
35446 skipBegin = it->key;
35447 lineData->append(QCPGraphData(it->key, upperClipValue));
35448 aboveRange = true;
35449 }
35450 if (it->key-skipBegin > maxKeySkip) // add dummy point if we're exceeding the maximum skippable angle (to prevent unintentional intersections with visible circle)
35451 {
35452 skipBegin += maxKeySkip;
35453 lineData->append(QCPGraphData(skipBegin, upperClipValue));
35454 }
35455 } else // value within bounds where we don't optimize away points
35456 {
35457 if (aboveRange)
35458 {
35459 aboveRange = false;
35460 if (!reversed)
35461 lineData->append(*(it-1)); // just entered from above, draw previous point so entry angle is correct (if above means outer, so if not reversed axis)
35462 }
35463 if (belowRange)
35464 {
35465 belowRange = false;
35466 if (reversed)
35467 lineData->append(*(it-1)); // just entered from below, draw previous point so entry angle is correct (if below means outer, so if reversed axis)
35468 }
35469 lineData->append(*it); // inside visible circle, add point normally
35470 }
35471 ++it;
35472 }
35473 // to make fill not erratic, add last point normally if it was outside visible circle:
35474 if (aboveRange)
35475 {
35476 aboveRange = false;
35477 if (!reversed)
35478 lineData->append(*(it-1)); // just entered from above, draw previous point so entry angle is correct (if above means outer, so if not reversed axis)
35479 }
35480 if (belowRange)
35481 {
35482 belowRange = false;
35483 if (reversed)
35484 lineData->append(*(it-1)); // just entered from below, draw previous point so entry angle is correct (if below means outer, so if reversed axis)
35485 }
35486}
35487
35488void QCPPolarGraph::getOptimizedScatterData(QVector<QCPGraphData> *scatterData, QCPGraphDataContainer::const_iterator begin, QCPGraphDataContainer::const_iterator end) const
35489{
35490 scatterData->clear();
35491
35492 const QCPRange range = mValueAxis->range();
35493 bool reversed = mValueAxis->rangeReversed();
35494 const double clipMargin = range.size()*0.05;
35495 const double upperClipValue = range.upper + (reversed ? 0 : clipMargin); // clip slightly outside of actual range to avoid scatter size to peek into visible circle
35496 const double lowerClipValue = range.lower - (reversed ? clipMargin : 0); // clip slightly outside of actual range to avoid scatter size to peek into visible circle
35498 while (it != end)
35499 {
35500 if (it->value > lowerClipValue && it->value < upperClipValue)
35501 scatterData->append(*it);
35502 ++it;
35503 }
35504}
35505
35506/*! \internal
35507
35508 Takes raw data points in plot coordinates as \a data, and returns a vector containing pixel
35509 coordinate points which are suitable for drawing the line style \ref lsLine.
35510
35511 The source of \a data is usually \ref getOptimizedLineData, and this method is called in \a
35512 getLines if the line style is set accordingly.
35513
35514 \see dataToStepLeftLines, dataToStepRightLines, dataToStepCenterLines, dataToImpulseLines, getLines, drawLinePlot
35515*/
35517{
35518 QVector<QPointF> result;
35519 QCPPolarAxisAngular *keyAxis = mKeyAxis.data();
35520 QCPPolarAxisRadial *valueAxis = mValueAxis.data();
35521 if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return result; }
35522
35523 // transform data points to pixels:
35524 result.resize(data.size());
35525 for (int i=0; i<data.size(); ++i)
35526 result[i] = mValueAxis->coordToPixel(data.at(i).key, data.at(i).value);
35527 return result;
35528}
35529/* end of 'src/polar/polargraph.cpp' */
35530
35531
The abstract base class for all items in a plot.
virtual void applyDefaultAntialiasingHint(QCPPainter *painter) const override
QCPItemAnchor * anchor(const QString &name) const
Q_SLOT void setSelected(bool selected)
virtual void deselectEvent(bool *selectionStateChanged) override
QCPItemPosition * position(const QString &name) const
virtual QRect clipRect() const override
void setClipToAxisRect(bool clip)
QList< QCPItemPosition * > positions() const
QCPItemPosition * createPosition(const QString &name)
void setClipAxisRect(QCPAxisRect *rect)
double rectDistance(const QRectF &rect, const QPointF &pos, bool filledRect) const
bool hasAnchor(const QString &name) const
Q_SLOT void setSelectable(bool selectable)
QCPAbstractItem(QCustomPlot *parentPlot)
void selectionChanged(bool selected)
virtual QCP::Interaction selectionCategory() const override
virtual void selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged) override
virtual QPointF anchorPixelPosition(int anchorId) const
QCPItemAnchor * createAnchor(const QString &name, int anchorId)
The abstract base class for all entries in a QCPLegend.
virtual QCP::Interaction selectionCategory() const override
void setFont(const QFont &font)
void setSelectedTextColor(const QColor &color)
void setTextColor(const QColor &color)
Q_SLOT void setSelected(bool selected)
void selectionChanged(bool selected)
void setSelectedFont(const QFont &font)
Q_SLOT void setSelectable(bool selectable)
virtual QRect clipRect() const override
virtual void deselectEvent(bool *selectionStateChanged) override
virtual void applyDefaultAntialiasingHint(QCPPainter *painter) const override
virtual void selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged) override
virtual double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details=nullptr) const override
QCPAbstractLegendItem(QCPLegend *parent)
The abstract base class for paint buffers, which define the rendering backend.
QCPAbstractPaintBuffer(const QSize &size, double devicePixelRatio)
void setDevicePixelRatio(double ratio)
void setSize(const QSize &size)
virtual void reallocateBuffer()=0
void setInvalidated(bool invalidated=true)
A template base class for plottables with one-dimensional data.
virtual int dataCount() const override
void drawPolyline(QCPPainter *painter, const QVector< QPointF > &lineData) const
void getDataSegments(QList< QCPDataRange > &selectedSegments, QList< QCPDataRange > &unselectedSegments) const
The abstract base class for all data representing objects in a plot.
QCPDataSelection selection() const
void setAntialiasedFill(bool enabled)
bool selected() const
void applyDefaultAntialiasingHint(QCPPainter *painter) const override
void rescaleAxes(bool onlyEnlarge=false) const
void setSelectionDecorator(QCPSelectionDecorator *decorator)
Q_SLOT void setSelection(QCPDataSelection selection)
void setAntialiasedScatters(bool enabled)
void pixelsToCoords(double x, double y, double &key, double &value) const
void selectionChanged(bool selected)
bool removeFromLegend(QCPLegend *legend) const
virtual QCPPlottableInterface1D * interface1D()
virtual QCPRange getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain=QCP::sdBoth) const =0
virtual void deselectEvent(bool *selectionStateChanged) override
void selectableChanged(QCP::SelectionType selectable)
void rescaleValueAxis(bool onlyEnlarge=false, bool inKeyRange=false) const
void setValueAxis(QCPAxis *axis)
virtual void drawLegendIcon(QCPPainter *painter, const QRectF &rect) const =0
void setBrush(const QBrush &brush)
void coordsToPixels(double key, double value, double &x, double &y) const
void setKeyAxis(QCPAxis *axis)
void applyFillAntialiasingHint(QCPPainter *painter) const
bool addToLegend(QCPLegend *legend)
virtual QCPRange getValueRange(bool &foundRange, QCP::SignDomain inSignDomain=QCP::sdBoth, const QCPRange &inKeyRange=QCPRange()) const =0
virtual void selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged) override
void setPen(const QPen &pen)
void setName(const QString &name)
Q_SLOT void setSelectable(QCP::SelectionType selectable)
virtual QRect clipRect() const override
void applyScattersAntialiasingHint(QCPPainter *painter) const
bool removeFromLegend() const
virtual QCP::Interaction selectionCategory() const override
void rescaleKeyAxis(bool onlyEnlarge=false) const
QCPAbstractPlottable(QCPAxis *keyAxis, QCPAxis *valueAxis)
virtual void draw(QCPPainter *painter)
QCPAxisPainterPrivate(QCustomPlot *parentPlot)
virtual TickLabelData getTickLabelData(const QFont &font, const QString &text) const
virtual QPointF getTickLabelDrawOffset(const TickLabelData &labelData) const
virtual void getMaxTickLabelSize(const QFont &font, const QString &text, QSize *tickLabelsSize) const
virtual void drawTickLabel(QCPPainter *painter, double x, double y, const TickLabelData &labelData) const
virtual QByteArray generateLabelParameterHash() const
virtual void placeTickLabel(QCPPainter *painter, double position, int distanceToAxis, const QString &text, QSize *tickLabelsSize)
Holds multiple axes and arranges them in a rectangular shape.
QList< QCPAbstractItem * > items() const
bool removeAxis(QCPAxis *axis)
QList< QCPAxis * > axes() const
int width() const
virtual void mouseMoveEvent(QMouseEvent *event, const QPointF &startPos) override
QCPAxis * addAxis(QCPAxis::AxisType type, QCPAxis *axis=nullptr)
QList< QCPGraph * > graphs() const
int right() const
virtual void update(UpdatePhase phase) override
int top() const
QCPAxis * axis(QCPAxis::AxisType type, int index=0) const
QList< QCPAbstractPlottable * > plottables() const
void setBackgroundScaledMode(Qt::AspectRatioMode mode)
void setupFullAxesBox(bool connectRanges=false)
void zoom(const QRectF &pixelRect)
void updateAxesOffset(QCPAxis::AxisType type)
QCPAxisRect(QCustomPlot *parentPlot, bool setupDefaultAxes=true)
void setRangeDragAxes(QCPAxis *horizontal, QCPAxis *vertical)
virtual int calculateAutoMargin(QCP::MarginSide side) override
QCPAxis * rangeZoomAxis(Qt::Orientation orientation)
QCPAxis * rangeDragAxis(Qt::Orientation orientation)
QList< QCPAxis * > addAxes(QCPAxis::AxisTypes types)
void setRangeZoom(Qt::Orientations orientations)
virtual QList< QCPLayoutElement * > elements(bool recursive) const override
virtual void layoutChanged() override
int axisCount(QCPAxis::AxisType type) const
QList< QCPAxis * > rangeZoomAxes(Qt::Orientation orientation)
void setRangeZoomFactor(double horizontalFactor, double verticalFactor)
void setRangeZoomAxes(QCPAxis *horizontal, QCPAxis *vertical)
QCPLayoutInset * insetLayout() const
virtual void wheelEvent(QWheelEvent *event) override
virtual void mousePressEvent(QMouseEvent *event, const QVariant &details) override
QList< QCPAxis * > rangeDragAxes(Qt::Orientation orientation)
void drawBackground(QCPPainter *painter)
virtual void mouseReleaseEvent(QMouseEvent *event, const QPointF &startPos) override
int height() const
int bottom() const
double rangeZoomFactor(Qt::Orientation orientation)
void setRangeDrag(Qt::Orientations orientations)
void setBackgroundScaled(bool scaled)
virtual void draw(QCPPainter *painter) override
virtual void applyDefaultAntialiasingHint(QCPPainter *painter) const override
void setBackground(const QPixmap &pm)
int left() const
static double dateTimeToKey(const QDateTime &dateTime)
void setTimeZone(const QTimeZone &zone)
virtual QVector< double > createTickVector(double tickStep, const QCPRange &range) override
static QDateTime keyToDateTime(double key)
virtual int getSubTickCount(double tickStep) override
void setTickOrigin(double origin)
virtual double getTickStep(const QCPRange &range) override
virtual QString getTickLabel(double tick, const QLocale &locale, QChar formatChar, int precision) override
void setDateTimeFormat(const QString &format)
void setDateTimeSpec(Qt::TimeSpec spec)
Specialized axis ticker with a fixed tick step.
@ ssMultiples
An integer multiple of the specified tick step is allowed. The used factor follows the base class pro...
@ ssNone
Modifications are not allowed, the specified tick step is absolutely fixed. This might cause a high t...
@ ssPowers
An integer power of the specified tick step is allowed.
void setTickStep(double step)
virtual double getTickStep(const QCPRange &range) override
void setScaleStrategy(ScaleStrategy strategy)
virtual int getSubTickCount(double tickStep) override
virtual QVector< double > createTickVector(double tickStep, const QCPRange &range) override
void setLogBase(double base)
void setSubTickCount(int subTicks)
void simplifyFraction(int &numerator, int &denominator) const
@ fsFloatingPoint
Fractions are displayed as regular decimal floating point numbers, e.g. "0.25" or "0....
@ fsAsciiFractions
Fractions are written as rationals using ASCII characters only, e.g. "1/4" or "1/8".
@ fsUnicodeFractions
Fractions are written using sub- and superscript UTF-8 digits and the fraction symbol.
QString unicodeSuperscript(int number) const
void setPiValue(double pi)
void setPeriodicity(int multiplesOfPi)
QString unicodeSubscript(int number) const
void setFractionStyle(FractionStyle style)
virtual double getTickStep(const QCPRange &range) override
virtual QString getTickLabel(double tick, const QLocale &locale, QChar formatChar, int precision) override
void setPiSymbol(QString symbol)
QString unicodeFraction(int numerator, int denominator) const
virtual int getSubTickCount(double tickStep) override
QString fractionToString(int numerator, int denominator) const
virtual QVector< double > createTickVector(double tickStep, const QCPRange &range) override
virtual double getTickStep(const QCPRange &range) override
void addTick(double position, const QString &label)
virtual QString getTickLabel(double tick, const QLocale &locale, QChar formatChar, int precision) override
void setTicks(const QMap< double, QString > &ticks)
void setSubTickCount(int subTicks)
void addTicks(const QMap< double, QString > &ticks)
QMap< double, QString > & ticks()
virtual int getSubTickCount(double tickStep) override
void replaceUnit(QString &text, TimeUnit unit, int value) const
void setTimeFormat(const QString &format)
virtual double getTickStep(const QCPRange &range) override
@ tuSeconds
Seconds (%s in setTimeFormat)
@ tuMinutes
Minutes (%m in setTimeFormat)
@ tuMilliseconds
Milliseconds, one thousandth of a second (%z in setTimeFormat)
@ tuHours
Hours (%h in setTimeFormat)
@ tuDays
Days (%d in setTimeFormat)
void setFieldWidth(TimeUnit unit, int width)
virtual QString getTickLabel(double tick, const QLocale &locale, QChar formatChar, int precision) override
virtual int getSubTickCount(double tickStep) override
The base class tick generator used by QCPAxis to create tick positions and tick labels.
void setTickCount(int count)
virtual int getSubTickCount(double tickStep)
double pickClosest(double target, const QVector< double > &candidates) const
void setTickStepStrategy(TickStepStrategy strategy)
virtual QVector< QString > createLabelVector(const QVector< double > &ticks, const QLocale &locale, QChar formatChar, int precision)
virtual QString getTickLabel(double tick, const QLocale &locale, QChar formatChar, int precision)
virtual double getTickStep(const QCPRange &range)
virtual QVector< double > createSubTickVector(int subTickCount, const QVector< double > &ticks)
void trimTicks(const QCPRange &range, QVector< double > &ticks, bool keepOneOutlier) const
void setTickOrigin(double origin)
@ tssMeetTickCount
Less readable tick steps are allowed which in turn facilitates getting closer to the requested tick c...
@ tssReadability
A nicely readable tick step is prioritized over matching the requested number of ticks (see setTickCo...
double getMantissa(double input, double *magnitude=nullptr) const
double cleanMantissa(double input) const
virtual void generate(const QCPRange &range, const QLocale &locale, QChar formatChar, int precision, QVector< double > &ticks, QVector< double > *subTicks, QVector< QString > *tickLabels)
virtual QVector< double > createTickVector(double tickStep, const QCPRange &range)
Manages a single axis inside a QCustomPlot.
virtual void wheelEvent(QWheelEvent *event) override
void setSelectedLabelFont(const QFont &font)
void setOffset(int offset)
void setTickLabels(bool show)
void rangeChanged(const QCPRange &newRange)
void setLowerEnding(const QCPLineEnding &ending)
void setTickLabelSide(LabelSide side)
virtual void draw(QCPPainter *painter) override
void moveRange(double diff)
void setTickLabelRotation(double degrees)
virtual void mouseReleaseEvent(QMouseEvent *event, const QPointF &startPos) override
void setRangeReversed(bool reversed)
void setNumberPrecision(int precision)
SelectablePart getPartAt(const QPointF &pos) const
@ lsOutside
Tick labels will be displayed outside the axis rect.
@ lsInside
Tick labels will be displayed inside the axis rect and clipped to the inner axis rect.
void setSelectedSubTickPen(const QPen &pen)
void setTickLabelFont(const QFont &font)
void scaleRange(double factor)
void setLabel(const QString &str)
void scaleTypeChanged(QCPAxis::ScaleType scaleType)
@ stLogarithmic
Logarithmic scaling with correspondingly transformed axis coordinates (possibly also setTicker to a Q...
@ stLinear
Linear scaling.
void setTickLabelColor(const QColor &color)
virtual void applyDefaultAntialiasingHint(QCPPainter *painter) const override
void setTickLengthOut(int outside)
QList< QCPAbstractItem * > items() const
void setLabelPadding(int padding)
int pixelOrientation() const
virtual int calculateMargin()
void rescale(bool onlyVisiblePlottables=false)
void setSubTickLengthOut(int outside)
void setTicker(QSharedPointer< QCPAxisTicker > ticker)
Q_SLOT void setSelectableParts(const QCPAxis::SelectableParts &selectableParts)
double pixelToCoord(double value) const
void setPadding(int padding)
void setupTickVectors()
virtual double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details=nullptr) const override
void setSelectedLabelColor(const QColor &color)
void selectionChanged(const QCPAxis::SelectableParts &parts)
void setTickLength(int inside, int outside=0)
QCPGrid * grid() const
void setUpperEnding(const QCPLineEnding &ending)
QFont getTickLabelFont() const
void setLabelColor(const QColor &color)
void setLabelFont(const QFont &font)
virtual QCP::Interaction selectionCategory() const override
void setBasePen(const QPen &pen)
QSharedPointer< QCPAxisTicker > ticker() const
void setSelectedTickPen(const QPen &pen)
void setSelectedTickLabelFont(const QFont &font)
QPen getBasePen() const
QColor getTickLabelColor() const
virtual void mousePressEvent(QMouseEvent *event, const QVariant &details) override
void setSelectedTickLabelColor(const QColor &color)
void selectableChanged(const QCPAxis::SelectableParts &parts)
static AxisType opposite(AxisType type)
virtual void selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged) override
QPen getSubTickPen() const
virtual void deselectEvent(bool *selectionStateChanged) override
void setSubTickLength(int inside, int outside=0)
Qt::Orientation orientation() const
Q_SLOT void setSelectedParts(const QCPAxis::SelectableParts &selectedParts)
@ spTickLabels
Tick labels (numbers) of this axis (as a whole, not individually)
@ spAxisLabel
The axis label.
@ spAxis
The axis backbone and tick marks.
@ spNone
None of the selectable parts.
static AxisType marginSideToAxisType(QCP::MarginSide side)
void setSubTickLengthIn(int inside)
QList< QCPAbstractPlottable * > plottables() const
QCPAxis(QCPAxisRect *parent, AxisType type)
virtual void mouseMoveEvent(QMouseEvent *event, const QPointF &startPos) override
void setTicks(bool show)
void setRangeUpper(double upper)
QList< QCPGraph * > graphs() const
void setTickPen(const QPen &pen)
Q_SLOT void setScaleType(QCPAxis::ScaleType type)
@ atBottom
0x08 Axis is horizontal and on the bottom side of the axis rect
@ atTop
0x04 Axis is horizontal and on the top side of the axis rect
@ atRight
0x02 Axis is vertical and on the right side of the axis rect
@ atLeft
0x01 Axis is vertical and on the left side of the axis rect
void setNumberFormat(const QString &formatCode)
QColor getLabelColor() const
QFont getLabelFont() const
void setSelectedBasePen(const QPen &pen)
Q_SLOT void setRange(const QCPRange &range)
void setSubTickPen(const QPen &pen)
double coordToPixel(double value) const
void setTickLabelPadding(int padding)
void setScaleRatio(const QCPAxis *otherAxis, double ratio=1.0)
void setSubTicks(bool show)
void setTickLengthIn(int inside)
void setRangeLower(double lower)
QPen getTickPen() const
Holds the data of one single data point (one bar) for QCPBars.
Groups multiple QCPBars together so they appear side by side.
double getPixelSpacing(const QCPBars *bars, double keyCoord)
void remove(QCPBars *bars)
void setSpacingType(SpacingType spacingType)
void insert(int i, QCPBars *bars)
@ stAbsolute
Bar spacing is in absolute pixels.
@ stPlotCoords
Bar spacing is in key coordinates and thus scales with the key axis range.
@ stAxisRectRatio
Bar spacing is given by a fraction of the axis rect size.
QList< QCPBars * > bars() const
void registerBars(QCPBars *bars)
void append(QCPBars *bars)
double keyPixelOffset(const QCPBars *bars, double keyCoord)
QCPBarsGroup(QCustomPlot *parentPlot)
void setSpacing(double spacing)
void unregisterBars(QCPBars *bars)
A plottable representing a bar chart in a plot.
QRectF getBarRect(double key, double value) const
double getStackedBaseValue(double key, bool positive) const
QCPBars * barBelow() const
void addData(const QVector< double > &keys, const QVector< double > &values, bool alreadySorted=false)
virtual QCPRange getValueRange(bool &foundRange, QCP::SignDomain inSignDomain=QCP::sdBoth, const QCPRange &inKeyRange=QCPRange()) const override
void setBaseValue(double baseValue)
QCPBars(QCPAxis *keyAxis, QCPAxis *valueAxis)
@ wtAxisRectRatio
Bar width is given by a fraction of the axis rect size.
@ wtPlotCoords
Bar width is in key coordinates and thus scales with the key axis range.
@ wtAbsolute
Bar width is in absolute pixels.
void moveBelow(QCPBars *bars)
void setData(QSharedPointer< QCPBarsDataContainer > data)
static void connectBars(QCPBars *lower, QCPBars *upper)
virtual void draw(QCPPainter *painter) override
virtual double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details=nullptr) const override
QSharedPointer< QCPBarsDataContainer > data() const
virtual QPointF dataPixelPosition(int index) const override
virtual QCPRange getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain=QCP::sdBoth) const override
void moveAbove(QCPBars *bars)
void getVisibleDataBounds(QCPBarsDataContainer::const_iterator &begin, QCPBarsDataContainer::const_iterator &end) const
virtual void drawLegendIcon(QCPPainter *painter, const QRectF &rect) const override
void getPixelWidth(double key, double &lower, double &upper) const
void setWidthType(WidthType widthType)
void setStackingGap(double pixels)
void setBarsGroup(QCPBarsGroup *barsGroup)
virtual QCPDataSelection selectTestRect(const QRectF &rect, bool onlySelectable) const override
void setWidth(double width)
Defines a color gradient for use with e.g. QCPColorMap.
QRgb color(double position, const QCPRange &range, bool logarithmic=false)
void setNanHandling(NanHandling handling)
bool stopsUseAlpha() const
void setLevelCount(int n)
void setPeriodic(bool enabled)
void setColorStopAt(double position, const QColor &color)
void setColorStops(const QMap< double, QColor > &colorStops)
QCPColorGradient inverted() const
void loadPreset(GradientPreset preset)
void setColorInterpolation(ColorInterpolation interpolation)
void colorize(const double *data, const QCPRange &range, QRgb *scanLine, int n, int dataIndexFactor=1, bool logarithmic=false)
void setNanColor(const QColor &color)
@ ciRGB
Color channels red, green and blue are linearly interpolated.
@ ciHSV
Color channels hue, saturation and value are linearly interpolated (The hue is interpolated over the ...
@ gpNight
Continuous lightness from black over weak blueish colors to white (suited for non-biased data represe...
@ gpHues
Full hue cycle, with highest and lowest color red (suitable for periodic data, such as angles and pha...
@ gpGeography
Colors suitable to represent different elevations on geographical maps.
@ gpIon
Half hue spectrum from black over purple to blue and finally green (creates banding illusion but allo...
@ gpHot
Continuous lightness from black over firey colors to white (suited for non-biased data representation...
@ gpJet
Hue variation similar to a spectrum, often used in numerical visualization (creates banding illusion ...
@ gpCandy
Blue over pink to white.
@ gpPolar
Colors suitable to emphasize polarity around the center, with blue for negative, black in the middle ...
@ gpSpectrum
An approximation of the visible light spectrum (creates banding illusion but allows more precise magn...
@ gpGrayscale
Continuous lightness from black to white (suited for non-biased data representation)
@ gpCold
Continuous lightness from black over icey colors to white (suited for non-biased data representation)
@ gpThermal
Colors suitable for thermal imaging, ranging from dark blue over purple to orange,...
@ nhHighestColor
NaN data points appear as the highest color defined in this QCPColorGradient.
@ nhTransparent
NaN data points appear transparent.
@ nhLowestColor
NaN data points appear as the lowest color defined in this QCPColorGradient.
@ nhNone
NaN data points are not explicitly handled and shouldn't occur in the data (this gives slight perform...
@ nhNanColor
NaN data points appear as the color defined with setNanColor.
Holds the two-dimensional data of a QCPColorMap plottable.
void setKeyRange(const QCPRange &keyRange)
void setValueSize(int valueSize)
void setSize(int keySize, int valueSize)
void fill(double z)
bool createAlpha(bool initializeOpaque=true)
unsigned char alpha(int keyIndex, int valueIndex)
void setCell(int keyIndex, int valueIndex, double z)
void fillAlpha(unsigned char alpha)
QCPColorMapData(int keySize, int valueSize, const QCPRange &keyRange, const QCPRange &valueRange)
void setRange(const QCPRange &keyRange, const QCPRange &valueRange)
void setAlpha(int keyIndex, int valueIndex, unsigned char alpha)
void setKeySize(int keySize)
void coordToCell(double key, double value, int *keyIndex, int *valueIndex) const
void setValueRange(const QCPRange &valueRange)
bool isEmpty() const
void cellToCoord(int keyIndex, int valueIndex, double *key, double *value) const
void setData(double key, double value, double z)
QCPColorMapData & operator=(const QCPColorMapData &other)
A plottable representing a two-dimensional color map in a plot.
QCPColorMapData * data() const
virtual double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details=nullptr) const override
void gradientChanged(const QCPColorGradient &newGradient)
virtual void draw(QCPPainter *painter) override
void setInterpolate(bool enabled)
virtual QCPRange getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain=QCP::sdBoth) const override
void setData(QCPColorMapData *data, bool copy=false)
Q_SLOT void updateLegendIcon(Qt::TransformationMode transformMode=Qt::SmoothTransformation, const QSize &thumbSize=QSize(32, 18))
virtual void updateMapImage()
Q_SLOT void setGradient(const QCPColorGradient &gradient)
void dataRangeChanged(const QCPRange &newRange)
void rescaleDataRange(bool recalculateDataBounds=false)
virtual void drawLegendIcon(QCPPainter *painter, const QRectF &rect) const override
void dataScaleTypeChanged(QCPAxis::ScaleType scaleType)
Q_SLOT void setDataRange(const QCPRange &dataRange)
Q_SLOT void setDataScaleType(QCPAxis::ScaleType scaleType)
QCPColorMap(QCPAxis *keyAxis, QCPAxis *valueAxis)
void setColorScale(QCPColorScale *colorScale)
void setTightBoundary(bool enabled)
virtual QCPRange getValueRange(bool &foundRange, QCP::SignDomain inSignDomain=QCP::sdBoth, const QCPRange &inKeyRange=QCPRange()) const override
An axis rect subclass for use in a QCPColorScale.
Q_SLOT void axisSelectionChanged(QCPAxis::SelectableParts selectedParts)
Q_SLOT void axisSelectableChanged(QCPAxis::SelectableParts selectableParts)
QCPColorScaleAxisRectPrivate(QCPColorScale *parentColorScale)
virtual void draw(QCPPainter *painter) override
A color scale for use with color coding data such as QCPColorMap.
void setType(QCPAxis::AxisType type)
Q_SLOT void setGradient(const QCPColorGradient &gradient)
void setRangeDrag(bool enabled)
virtual void mouseReleaseEvent(QMouseEvent *event, const QPointF &startPos) override
virtual void mouseMoveEvent(QMouseEvent *event, const QPointF &startPos) override
virtual void wheelEvent(QWheelEvent *event) override
void rescaleDataRange(bool onlyVisibleMaps)
QList< QCPColorMap * > colorMaps() const
void gradientChanged(const QCPColorGradient &newGradient)
void dataScaleTypeChanged(QCPAxis::ScaleType scaleType)
void dataRangeChanged(const QCPRange &newRange)
virtual void mousePressEvent(QMouseEvent *event, const QVariant &details) override
void setRangeZoom(bool enabled)
QCPColorScale(QCustomPlot *parentPlot)
void setBarWidth(int width)
Q_SLOT void setDataRange(const QCPRange &dataRange)
virtual void update(UpdatePhase phase) override
virtual void applyDefaultAntialiasingHint(QCPPainter *painter) const override
Q_SLOT void setDataScaleType(QCPAxis::ScaleType scaleType)
void setLabel(const QString &str)
Holds the data of one single data point for QCPCurve.
virtual void drawLegendIcon(QCPPainter *painter, const QRectF &rect) const override
virtual void drawScatterPlot(QCPPainter *painter, const QVector< QPointF > &points, const QCPScatterStyle &style) const
@ lsLine
Data points are connected with a straight line.
@ lsNone
No line is drawn between data points (e.g. only scatters)
virtual QCPRange getValueRange(bool &foundRange, QCP::SignDomain inSignDomain=QCP::sdBoth, const QCPRange &inKeyRange=QCPRange()) const override
QCPCurve(QCPAxis *keyAxis, QCPAxis *valueAxis)
void setData(QSharedPointer< QCPCurveDataContainer > data)
void setLineStyle(LineStyle style)
void getTraverseCornerPoints(int prevRegion, int currentRegion, double keyMin, double valueMax, double keyMax, double valueMin, QVector< QPointF > &beforeTraverse, QVector< QPointF > &afterTraverse) const
void setScatterStyle(const QCPScatterStyle &style)
void getScatters(QVector< QPointF > *scatters, const QCPDataRange &dataRange, double scatterWidth) const
virtual QCPRange getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain=QCP::sdBoth) const override
QVector< QPointF > getOptimizedCornerPoints(int prevRegion, int currentRegion, double prevKey, double prevValue, double key, double value, double keyMin, double valueMax, double keyMax, double valueMin) const
QPointF getOptimizedPoint(int otherRegion, double otherKey, double otherValue, double key, double value, double keyMin, double valueMax, double keyMax, double valueMin) const
void addData(const QVector< double > &t, const QVector< double > &keys, const QVector< double > &values, bool alreadySorted=false)
QSharedPointer< QCPCurveDataContainer > data() const
int getRegion(double key, double value, double keyMin, double valueMax, double keyMax, double valueMin) const
void setScatterSkip(int skip)
double pointDistance(const QPointF &pixelPoint, QCPCurveDataContainer::const_iterator &closestData) const
virtual void drawCurveLine(QCPPainter *painter, const QVector< QPointF > &lines) const
virtual void draw(QCPPainter *painter) override
void getCurveLines(QVector< QPointF > *lines, const QCPDataRange &dataRange, double penWidth) const
bool mayTraverse(int prevRegion, int currentRegion) const
virtual double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details=nullptr) const override
bool getTraverse(double prevKey, double prevValue, double key, double value, double keyMin, double valueMax, double keyMax, double valueMin, QPointF &crossA, QPointF &crossB) const
The generic data container for one-dimensional plottables.
Describes a data range given by begin and end index.
bool contains(const QCPDataRange &other) const
int length() const
void setEnd(int end)
QCPDataRange expanded(const QCPDataRange &other) const
void setBegin(int begin)
QCPDataRange intersection(const QCPDataRange &other) const
bool intersects(const QCPDataRange &other) const
QCPDataRange bounded(const QCPDataRange &other) const
bool isValid() const
bool isEmpty() const
int size() const
Describes a data set by holding multiple QCPDataRange instances.
void enforceType(QCP::SelectionType type)
QCPDataSelection & operator+=(const QCPDataSelection &other)
void addDataRange(const QCPDataRange &dataRange, bool simplify=true)
bool operator==(const QCPDataSelection &other) const
QCPDataSelection & operator-=(const QCPDataSelection &other)
QCPDataRange dataRange(int index=0) const
bool isEmpty() const
QCPDataRange span() const
bool contains(const QCPDataSelection &other) const
int dataRangeCount() const
QList< QCPDataRange > dataRanges() const
int dataPointCount() const
QCPDataSelection inverse(const QCPDataRange &outerRange) const
QCPDataSelection intersection(const QCPDataRange &other) const
Holds the data of one single error bar for QCPErrorBars.
virtual bool sortKeyIsMainKey() const override
virtual void drawLegendIcon(QCPPainter *painter, const QRectF &rect) const override
void getDataSegments(QList< QCPDataRange > &selectedSegments, QList< QCPDataRange > &unselectedSegments) const
void setSymbolGap(double pixels)
virtual QCPRange dataValueRange(int index) const override
virtual QCPDataSelection selectTestRect(const QRectF &rect, bool onlySelectable) const override
virtual QCPRange getValueRange(bool &foundRange, QCP::SignDomain inSignDomain=QCP::sdBoth, const QCPRange &inKeyRange=QCPRange()) const override
virtual double dataMainKey(int index) const override
bool errorBarVisible(int index) const
QCPErrorBars(QCPAxis *keyAxis, QCPAxis *valueAxis)
virtual double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details=nullptr) const override
virtual int dataCount() const override
virtual QCPRange getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain=QCP::sdBoth) const override
double pointDistance(const QPointF &pixelPoint, QCPErrorBarsDataContainer::const_iterator &closestData) const
void setData(QSharedPointer< QCPErrorBarsDataContainer > data)
@ etValueError
The errors are for the value dimension (bars appear parallel to the value axis)
@ etKeyError
The errors are for the key dimension (bars appear parallel to the key axis)
bool rectIntersectsLine(const QRectF &pixelRect, const QLineF &line) const
virtual double dataMainValue(int index) const override
void setDataPlottable(QCPAbstractPlottable *plottable)
void getVisibleDataBounds(QCPErrorBarsDataContainer::const_iterator &begin, QCPErrorBarsDataContainer::const_iterator &end, const QCPDataRange &rangeRestriction) const
void addData(const QVector< double > &error)
virtual int findEnd(double sortKey, bool expandedRange=true) const override
void getErrorBarLines(QCPErrorBarsDataContainer::const_iterator it, QVector< QLineF > &backbones, QVector< QLineF > &whiskers) const
virtual double dataSortKey(int index) const override
void setWhiskerWidth(double pixels)
virtual void draw(QCPPainter *painter) override
virtual QPointF dataPixelPosition(int index) const override
QSharedPointer< QCPErrorBarsDataContainer > data() const
void setErrorType(ErrorType type)
virtual int findBegin(double sortKey, bool expandedRange=true) const override
Holds the data of one single data point for QCPFinancial.
@ csOhlc
Open-High-Low-Close bar representation.
@ csCandlestick
Candlestick representation.
void setTwoColored(bool twoColored)
void setWidthType(WidthType widthType)
double ohlcSelectTest(const QPointF &pos, const QCPFinancialDataContainer::const_iterator &begin, const QCPFinancialDataContainer::const_iterator &end, QCPFinancialDataContainer::const_iterator &closestDataPoint) const
void drawOhlcPlot(QCPPainter *painter, const QCPFinancialDataContainer::const_iterator &begin, const QCPFinancialDataContainer::const_iterator &end, bool isSelected)
void getVisibleDataBounds(QCPFinancialDataContainer::const_iterator &begin, QCPFinancialDataContainer::const_iterator &end) const
void addData(const QVector< double > &keys, const QVector< double > &open, const QVector< double > &high, const QVector< double > &low, const QVector< double > &close, bool alreadySorted=false)
virtual void draw(QCPPainter *painter) override
double getPixelWidth(double key, double keyPixel) const
QCPFinancial(QCPAxis *keyAxis, QCPAxis *valueAxis)
virtual void drawLegendIcon(QCPPainter *painter, const QRectF &rect) const override
void setChartStyle(ChartStyle style)
void setBrushPositive(const QBrush &brush)
void setData(QSharedPointer< QCPFinancialDataContainer > data)
virtual QCPRange getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain=QCP::sdBoth) const override
void setBrushNegative(const QBrush &brush)
virtual QCPRange getValueRange(bool &foundRange, QCP::SignDomain inSignDomain=QCP::sdBoth, const QCPRange &inKeyRange=QCPRange()) const override
double candlestickSelectTest(const QPointF &pos, const QCPFinancialDataContainer::const_iterator &begin, const QCPFinancialDataContainer::const_iterator &end, QCPFinancialDataContainer::const_iterator &closestDataPoint) const
void setWidth(double width)
static QCPFinancialDataContainer timeSeriesToOhlc(const QVector< double > &time, const QVector< double > &value, double timeBinSize, double timeBinOffset=0)
QSharedPointer< QCPFinancialDataContainer > data() const
virtual double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details=nullptr) const override
void setPenPositive(const QPen &pen)
void drawCandlestickPlot(QCPPainter *painter, const QCPFinancialDataContainer::const_iterator &begin, const QCPFinancialDataContainer::const_iterator &end, bool isSelected)
@ wtAbsolute
width is in absolute pixels
@ wtAxisRectRatio
width is given by a fraction of the axis rect size
@ wtPlotCoords
width is in key coordinates and thus scales with the key axis range
virtual QCPDataSelection selectTestRect(const QRectF &rect, bool onlySelectable) const override
QRectF selectionHitBox(QCPFinancialDataContainer::const_iterator it) const
void setPenNegative(const QPen &pen)
Holds the data of one single data point for QCPGraph.
A plottable representing a graph in a plot.
QVector< QPointF > dataToLines(const QVector< QCPGraphData > &data) const
virtual void draw(QCPPainter *painter) override
QCPGraph(QCPAxis *keyAxis, QCPAxis *valueAxis)
QVector< QCPDataRange > getNonNanSegments(const QVector< QPointF > *lineData, Qt::Orientation keyOrientation) const
void setScatterStyle(const QCPScatterStyle &style)
QPointF getFillBasePoint(QPointF matchingDataPoint) const
QSharedPointer< QCPGraphDataContainer > data() const
void setScatterSkip(int skip)
void setData(QSharedPointer< QCPGraphDataContainer > data)
QVector< QPointF > dataToStepLeftLines(const QVector< QCPGraphData > &data) const
virtual void getOptimizedLineData(QVector< QCPGraphData > *lineData, const QCPGraphDataContainer::const_iterator &begin, const QCPGraphDataContainer::const_iterator &end) const
virtual QCPRange getValueRange(bool &foundRange, QCP::SignDomain inSignDomain=QCP::sdBoth, const QCPRange &inKeyRange=QCPRange()) const override
virtual void drawImpulsePlot(QCPPainter *painter, const QVector< QPointF > &lines) const
QVector< QPointF > dataToStepCenterLines(const QVector< QCPGraphData > &data) const
const QPolygonF getChannelFillPolygon(const QVector< QPointF > *thisData, QCPDataRange thisSegment, const QVector< QPointF > *otherData, QCPDataRange otherSegment) const
QVector< QPointF > dataToImpulseLines(const QVector< QCPGraphData > &data) const
void setChannelFillGraph(QCPGraph *targetGraph)
QVector< QPair< QCPDataRange, QCPDataRange > > getOverlappingSegments(QVector< QCPDataRange > thisSegments, const QVector< QPointF > *thisData, QVector< QCPDataRange > otherSegments, const QVector< QPointF > *otherData) const
virtual void drawLinePlot(QCPPainter *painter, const QVector< QPointF > &lines) const
void setLineStyle(LineStyle ls)
virtual void getOptimizedScatterData(QVector< QCPGraphData > *scatterData, QCPGraphDataContainer::const_iterator begin, QCPGraphDataContainer::const_iterator end) const
void getLines(QVector< QPointF > *lines, const QCPDataRange &dataRange) const
int findIndexBelowY(const QVector< QPointF > *data, double y) const
virtual void drawFill(QCPPainter *painter, QVector< QPointF > *lines) const
virtual void drawLegendIcon(QCPPainter *painter, const QRectF &rect) const override
void getVisibleDataBounds(QCPGraphDataContainer::const_iterator &begin, QCPGraphDataContainer::const_iterator &end, const QCPDataRange &rangeRestriction) const
double pointDistance(const QPointF &pixelPoint, QCPGraphDataContainer::const_iterator &closestData) const
int findIndexAboveY(const QVector< QPointF > *data, double y) const
int findIndexBelowX(const QVector< QPointF > *data, double x) const
virtual QCPRange getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain=QCP::sdBoth) const override
void getScatters(QVector< QPointF > *scatters, const QCPDataRange &dataRange) const
int findIndexAboveX(const QVector< QPointF > *data, double x) const
QVector< QPointF > dataToStepRightLines(const QVector< QCPGraphData > &data) const
void setAdaptiveSampling(bool enabled)
bool segmentsIntersect(double aLower, double aUpper, double bLower, double bUpper, int &bPrecedence) const
virtual void drawScatterPlot(QCPPainter *painter, const QVector< QPointF > &scatters, const QCPScatterStyle &style) const
@ lsLine
data points are connected by a straight line
@ lsStepCenter
line is drawn as steps where the step is in between two data points
@ lsStepRight
line is drawn as steps where the step height is the value of the right data point
@ lsImpulse
each data point is represented by a line parallel to the value axis, which reaches from the data poin...
@ lsStepLeft
line is drawn as steps where the step height is the value of the left data point
@ lsNone
data points are not connected with any lines (e.g.
virtual double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details=nullptr) const override
void addData(const QVector< double > &keys, const QVector< double > &values, bool alreadySorted=false)
const QPolygonF getFillPolygon(const QVector< QPointF > *lineData, QCPDataRange segment) const
Responsible for drawing the grid of a QCPAxis.
void setZeroLinePen(const QPen &pen)
void setAntialiasedZeroLine(bool enabled)
void setAntialiasedSubGrid(bool enabled)
virtual void draw(QCPPainter *painter) override
void drawSubGridLines(QCPPainter *painter) const
virtual void applyDefaultAntialiasingHint(QCPPainter *painter) const override
void setSubGridPen(const QPen &pen)
void setPen(const QPen &pen)
QCPGrid(QCPAxis *parentAxis)
void setSubGridVisible(bool visible)
void drawGridLines(QCPPainter *painter) const
An anchor of an item to which positions can be attached to.
virtual QPointF pixelPosition() const
void removeChildX(QCPItemPosition *pos)
QCPItemAnchor(QCustomPlot *parentPlot, QCPAbstractItem *parentItem, const QString &name, int anchorId=-1)
void removeChildY(QCPItemPosition *pos)
virtual QCPItemPosition * toQCPItemPosition()
void addChildX(QCPItemPosition *pos)
void addChildY(QCPItemPosition *pos)
void setSelectedPen(const QPen &pen)
QCPItemBracket(QCustomPlot *parentPlot)
void setStyle(BracketStyle style)
virtual double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details=nullptr) const override
@ bsRound
A brace with round edges.
@ bsCurly
A curly brace.
@ bsSquare
A brace with angled edges.
@ bsCalligraphic
A curly brace with varying stroke width giving a calligraphic impression.
virtual void draw(QCPPainter *painter) override
virtual QPointF anchorPixelPosition(int anchorId) const override
void setPen(const QPen &pen)
void setLength(double length)
QPen mainPen() const
void setPen(const QPen &pen)
void setHead(const QCPLineEnding &head)
void setSelectedPen(const QPen &pen)
QPen mainPen() const
virtual double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details=nullptr) const override
virtual void draw(QCPPainter *painter) override
void setTail(const QCPLineEnding &tail)
QCPItemCurve(QCustomPlot *parentPlot)
virtual void draw(QCPPainter *painter) override
virtual QPointF anchorPixelPosition(int anchorId) const override
void setBrush(const QBrush &brush)
QBrush mainBrush() const
void setSelectedPen(const QPen &pen)
QCPItemEllipse(QCustomPlot *parentPlot)
void setSelectedBrush(const QBrush &brush)
QPen mainPen() const
virtual double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details=nullptr) const override
void setPen(const QPen &pen)
QCPItemLine(QCustomPlot *parentPlot)
void setSelectedPen(const QPen &pen)
virtual double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details=nullptr) const override
void setPen(const QPen &pen)
virtual void draw(QCPPainter *painter) override
QLineF getRectClippedLine(const QCPVector2D &start, const QCPVector2D &end, const QRect &rect) const
void setTail(const QCPLineEnding &tail)
void setHead(const QCPLineEnding &head)
QPen mainPen() const
virtual void draw(QCPPainter *painter) override
virtual double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details=nullptr) const override
void setPixmap(const QPixmap &pixmap)
void updateScaledPixmap(QRect finalRect=QRect(), bool flipHorz=false, bool flipVert=false)
QRect getFinalRect(bool *flippedHorz=nullptr, bool *flippedVert=nullptr) const
QCPItemPixmap(QCustomPlot *parentPlot)
QPen mainPen() const
virtual QPointF anchorPixelPosition(int anchorId) const override
void setScaled(bool scaled, Qt::AspectRatioMode aspectRatioMode=Qt::KeepAspectRatio, Qt::TransformationMode transformationMode=Qt::SmoothTransformation)
void setPen(const QPen &pen)
void setSelectedPen(const QPen &pen)
Manages the position of an item.
QCPItemAnchor * parentAnchor() const
void setAxisRect(QCPAxisRect *axisRect)
void setTypeX(PositionType type)
void setAxes(QCPAxis *keyAxis, QCPAxis *valueAxis)
QCPItemPosition(QCustomPlot *parentPlot, QCPAbstractItem *parentItem, const QString &name)
virtual QPointF pixelPosition() const override
void setPixelPosition(const QPointF &pixelPosition)
void setType(PositionType type)
void setCoords(double key, double value)
@ ptAxisRectRatio
Static positioning given by a fraction of the axis rect size (see setAxisRect).
@ ptAbsolute
Static positioning in pixels, starting from the top left corner of the viewport/widget.
@ ptViewportRatio
Static positioning given by a fraction of the viewport size.
@ ptPlotCoords
Dynamic positioning at a plot coordinate defined by two axes (see setAxes).
PositionType type() const
bool setParentAnchor(QCPItemAnchor *parentAnchor, bool keepPixelPosition=false)
void setTypeY(PositionType type)
bool setParentAnchorY(QCPItemAnchor *parentAnchor, bool keepPixelPosition=false)
bool setParentAnchorX(QCPItemAnchor *parentAnchor, bool keepPixelPosition=false)
virtual void draw(QCPPainter *painter) override
QCPItemRect(QCustomPlot *parentPlot)
void setPen(const QPen &pen)
void setSelectedPen(const QPen &pen)
virtual double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details=nullptr) const override
virtual QPointF anchorPixelPosition(int anchorId) const override
QBrush mainBrush() const
void setBrush(const QBrush &brush)
void setSelectedBrush(const QBrush &brush)
QPen mainPen() const
QCPItemStraightLine(QCustomPlot *parentPlot)
QLineF getRectClippedStraightLine(const QCPVector2D &base, const QCPVector2D &vec, const QRect &rect) const
void setSelectedPen(const QPen &pen)
virtual void draw(QCPPainter *painter) override
void setPen(const QPen &pen)
virtual double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details=nullptr) const override
void setSelectedFont(const QFont &font)
void setBrush(const QBrush &brush)
void setSelectedPen(const QPen &pen)
QPen mainPen() const
void setText(const QString &text)
void setRotation(double degrees)
QPointF getTextDrawPoint(const QPointF &pos, const QRectF &rect, Qt::Alignment positionAlignment) const
virtual double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details=nullptr) const override
void setSelectedBrush(const QBrush &brush)
QCPItemText(QCustomPlot *parentPlot)
void setPositionAlignment(Qt::Alignment alignment)
void setFont(const QFont &font)
virtual QPointF anchorPixelPosition(int anchorId) const override
void setPen(const QPen &pen)
void setColor(const QColor &color)
void setTextAlignment(Qt::Alignment alignment)
QColor mainColor() const
virtual void draw(QCPPainter *painter) override
QBrush mainBrush() const
void setSelectedColor(const QColor &color)
void setPadding(const QMargins &padding)
QFont mainFont() const
void setSelectedBrush(const QBrush &brush)
void setBrush(const QBrush &brush)
@ tsPlus
A plus shaped crosshair with limited size.
@ tsSquare
A square.
@ tsNone
The tracer is not visible.
@ tsCircle
A circle.
@ tsCrosshair
A plus shaped crosshair which spans the complete axis rect.
void setStyle(TracerStyle style)
void setGraphKey(double key)
void setInterpolating(bool enabled)
QBrush mainBrush() const
QPen mainPen() const
QCPItemTracer(QCustomPlot *parentPlot)
void setSelectedPen(const QPen &pen)
void setSize(double size)
virtual double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details=nullptr) const override
virtual void draw(QCPPainter *painter) override
void setGraph(QCPGraph *graph)
void setPen(const QPen &pen)
QCPLabelPainterPrivate(QCustomPlot *parentPlot)
virtual QByteArray generateLabelParameterHash() const
CachedLabel * createCachedLabel(const LabelData &labelData) const
void drawText(QCPPainter *painter, const QPointF &pos, const LabelData &labelData) const
virtual void drawLabelMaybeCached(QCPPainter *painter, const QFont &font, const QColor &color, const QPointF &pos, AnchorSide side, double rotation, const QString &text)
LabelData getTickLabelData(const QFont &font, const QColor &color, double rotation, AnchorSide side, const QString &text) const
A layer that may contain objects, to control the rendering order.
QList< QCPLayerable * > children() const
void drawToPaintBuffer()
void addChild(QCPLayerable *layerable, bool prepend)
QCPLayer(QCustomPlot *parentPlot, const QString &layerName)
@ lmLogical
Layer is used only for rendering order, and shares paint buffer with all other adjacent logical layer...
@ lmBuffered
Layer has its own paint buffer and may be replotted individually (see replot).
void setMode(LayerMode mode)
void draw(QCPPainter *painter)
void setVisible(bool visible)
void removeChild(QCPLayerable *layerable)
int index() const
void replot()
Base class for all drawable objects.
virtual void applyDefaultAntialiasingHint(QCPPainter *painter) const =0
virtual double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details=nullptr) const
void setVisible(bool on)
virtual void mouseDoubleClickEvent(QMouseEvent *event, const QVariant &details)
virtual void wheelEvent(QWheelEvent *event)
QCPLayerable(QCustomPlot *plot, QString targetLayer=QString(), QCPLayerable *parentLayerable=nullptr)
void setAntialiased(bool enabled)
virtual void selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged)
void initializeParentPlot(QCustomPlot *parentPlot)
virtual QCP::Interaction selectionCategory() const
virtual void mouseMoveEvent(QMouseEvent *event, const QPointF &startPos)
virtual void mouseReleaseEvent(QMouseEvent *event, const QPointF &startPos)
void setParentLayerable(QCPLayerable *parentLayerable)
QCPLayerable * parentLayerable() const
bool realVisibility() const
Q_SLOT bool setLayer(QCPLayer *layer)
virtual void parentPlotInitialized(QCustomPlot *parentPlot)
void layerChanged(QCPLayer *newLayer)
void applyAntialiasingHint(QCPPainter *painter, bool localAntialiased, QCP::AntialiasedElement overrideElement) const
virtual QRect clipRect() const
virtual void draw(QCPPainter *painter)=0
virtual void deselectEvent(bool *selectionStateChanged)
virtual void mousePressEvent(QMouseEvent *event, const QVariant &details)
bool moveToLayer(QCPLayer *layer, bool prepend)
The abstract base class for all objects that form the layout system.
virtual int calculateAutoMargin(QCP::MarginSide side)
void setMinimumMargins(const QMargins &margins)
@ scrInnerRect
Minimum/Maximum size constraints apply to inner rect.
@ upMargins
Phase in which the margins are calculated and set.
@ upLayout
Final phase in which the layout system places the rects of the elements.
@ upPreparation
Phase used for any type of preparation that needs to be done before margin calculation and layout.
QRect rect() const
QCPLayoutElement(QCustomPlot *parentPlot=nullptr)
void setSizeConstraintRect(SizeConstraintRect constraintRect)
void setOuterRect(const QRect &rect)
virtual QSize minimumOuterSizeHint() const
QCPLayout * layout() const
void setMarginGroup(QCP::MarginSides sides, QCPMarginGroup *group)
void setMinimumSize(const QSize &size)
void setMaximumSize(const QSize &size)
virtual void layoutChanged()
virtual QList< QCPLayoutElement * > elements(bool recursive) const
void setMargins(const QMargins &margins)
virtual void update(UpdatePhase phase)
void setAutoMargins(QCP::MarginSides sides)
virtual QSize maximumOuterSizeHint() const
virtual double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details=nullptr) const override
virtual void parentPlotInitialized(QCustomPlot *parentPlot) override
A layout that arranges child elements in a grid.
virtual QCPLayoutElement * takeAt(int index) override
int rowCount() const
int columnCount() const
void insertColumn(int newIndex)
void setRowStretchFactors(const QList< double > &factors)
virtual QCPLayoutElement * elementAt(int index) const override
virtual void updateLayout() override
void setColumnSpacing(int pixels)
void insertRow(int newIndex)
void getMinimumRowColSizes(QVector< int > *minColWidths, QVector< int > *minRowHeights) const
void indexToRowCol(int index, int &row, int &column) const
QCPLayoutElement * element(int row, int column) const
int rowColToIndex(int row, int column) const
void setColumnStretchFactors(const QList< double > &factors)
void setRowStretchFactor(int row, double factor)
@ foRowsFirst
Rows are filled first, and a new element is wrapped to the next column if the row count would exceed ...
@ foColumnsFirst
Columns are filled first, and a new element is wrapped to the next row if the column count would exce...
void expandTo(int newRowCount, int newColumnCount)
virtual void simplify() override
virtual QList< QCPLayoutElement * > elements(bool recursive) const override
void getMaximumRowColSizes(QVector< int > *maxColWidths, QVector< int > *maxRowHeights) const
virtual int elementCount() const override
void setRowSpacing(int pixels)
bool hasElement(int row, int column)
virtual bool take(QCPLayoutElement *element) override
void setWrap(int count)
virtual QSize minimumOuterSizeHint() const override
virtual QSize maximumOuterSizeHint() const override
bool addElement(int row, int column, QCPLayoutElement *element)
void setColumnStretchFactor(int column, double factor)
void setFillOrder(FillOrder order, bool rearrange=true)
A layout that places child elements aligned to the border or arbitrarily positioned.
virtual int elementCount() const override
virtual double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details=nullptr) const override
virtual void updateLayout() override
Qt::Alignment insetAlignment(int index) const
void setInsetAlignment(int index, Qt::Alignment alignment)
void setInsetPlacement(int index, InsetPlacement placement)
InsetPlacement insetPlacement(int index) const
@ ipFree
The element may be positioned/sized arbitrarily, see setInsetRect.
@ ipBorderAligned
The element is aligned to one of the layout sides, see setInsetAlignment.
virtual QCPLayoutElement * elementAt(int index) const override
void setInsetRect(int index, const QRectF &rect)
QRectF insetRect(int index) const
virtual QCPLayoutElement * takeAt(int index) override
void addElement(QCPLayoutElement *element, Qt::Alignment alignment)
virtual bool take(QCPLayoutElement *element) override
The abstract base class for layouts.
virtual void updateLayout()
bool removeAt(int index)
QVector< int > getSectionSizes(QVector< int > maxSizes, QVector< int > minSizes, QVector< double > stretchFactors, int totalSize) const
virtual void simplify()
void releaseElement(QCPLayoutElement *el)
virtual QList< QCPLayoutElement * > elements(bool recursive) const override
bool remove(QCPLayoutElement *element)
static QSize getFinalMinimumOuterSize(const QCPLayoutElement *el)
virtual QCPLayoutElement * takeAt(int index)=0
virtual QCPLayoutElement * elementAt(int index) const =0
virtual void update(UpdatePhase phase) override
virtual int elementCount() const =0
virtual bool take(QCPLayoutElement *element)=0
static QSize getFinalMaximumOuterSize(const QCPLayoutElement *el)
void sizeConstraintsChanged() const
void adoptElement(QCPLayoutElement *el)
Manages a legend inside a QCustomPlot.
virtual QCP::Interaction selectionCategory() const override
QPen getBorderPen() const
void clearItems()
Q_SLOT void setSelectedParts(const SelectableParts &selectedParts)
void setSelectedBorderPen(const QPen &pen)
void setIconBorderPen(const QPen &pen)
virtual void deselectEvent(bool *selectionStateChanged) override
bool addItem(QCPAbstractLegendItem *item)
void setBrush(const QBrush &brush)
bool hasItemWithPlottable(const QCPAbstractPlottable *plottable) const
@ spLegendBox
0x001 The legend box (frame)
@ spNone
0x000 None
@ spItems
0x002 Legend items individually (see selectedItems)
int itemCount() const
virtual void applyDefaultAntialiasingHint(QCPPainter *painter) const override
virtual void selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged) override
void setIconTextPadding(int padding)
void setSelectedTextColor(const QColor &color)
void selectionChanged(QCPLegend::SelectableParts parts)
void setBorderPen(const QPen &pen)
void setSelectedBrush(const QBrush &brush)
void setIconSize(const QSize &size)
QCPPlottableLegendItem * itemWithPlottable(const QCPAbstractPlottable *plottable) const
virtual void parentPlotInitialized(QCustomPlot *parentPlot) override
Q_SLOT void setSelectableParts(const SelectableParts &selectableParts)
void setFont(const QFont &font)
QBrush getBrush() const
virtual void draw(QCPPainter *painter) override
void setSelectedFont(const QFont &font)
virtual double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details=nullptr) const override
QList< QCPAbstractLegendItem * > selectedItems() const
bool removeItem(int index)
QCPAbstractLegendItem * item(int index) const
bool hasItem(QCPAbstractLegendItem *item) const
void setSelectedIconBorderPen(const QPen &pen)
void setTextColor(const QColor &color)
Handles the different ending decorations for line-like items.
double boundingDistance() const
void setWidth(double width)
void draw(QCPPainter *painter, const QCPVector2D &pos, const QCPVector2D &dir) const
void setStyle(EndingStyle style)
void setInverted(bool inverted)
@ esHalfBar
A bar perpendicular to the line, pointing out to only one side (to which side can be changed with set...
@ esSkewedBar
A bar that is skewed (skew controllable via setLength)
@ esBar
A bar perpendicular to the line.
@ esDiamond
A filled diamond (45 degrees rotated square)
@ esFlatArrow
A filled arrow head with a straight/flat back (a triangle)
@ esLineArrow
A non-filled arrow head with open back.
@ esSpikeArrow
A filled arrow head with an indented back.
@ esNone
No ending decoration.
@ esSquare
A filled square.
@ esDisc
A filled circle.
double realLength() const
void setLength(double length)
A margin group allows synchronization of margin sides if working with multiple layout elements.
void removeChild(QCP::MarginSide side, QCPLayoutElement *element)
QCPMarginGroup(QCustomPlot *parentPlot)
QList< QCPLayoutElement * > elements(QCP::MarginSide side) const
void addChild(QCP::MarginSide side, QCPLayoutElement *element)
bool isEmpty() const
virtual int commonMargin(QCP::MarginSide side) const
A paint buffer based on QPixmap, using software raster rendering.
void clear(const QColor &color) override
virtual QCPPainter * startPainting() override
virtual void draw(QCPPainter *painter) const override
virtual void reallocateBuffer() override
QCPPaintBufferPixmap(const QSize &size, double devicePixelRatio)
QPainter subclass used internally.
bool begin(QPaintDevice *device)
void drawLine(const QLineF &line)
@ pmNonCosmetic
0x04 Turns pen widths 0 to 1, i.e. disables cosmetic pens. (A cosmetic pen is always drawn with width...
@ pmNoCaching
0x02 Mode for all sorts of exports (e.g. PNG, PDF,...). For example, this prevents using cached pixma...
@ pmVectorized
0x01 Mode for vectorized painting (e.g. PDF export). For example, this prevents some antialiasing fix...
void setModes(PainterModes modes)
void makeNonCosmetic()
void setAntialiasing(bool enabled)
void setMode(PainterMode mode, bool enabled=true)
void setPen(const QPen &pen)
Defines an abstract interface for one-dimensional plottables.
virtual double dataMainValue(int index) const =0
virtual double dataMainKey(int index) const =0
virtual int dataCount() const =0
A legend item representing a plottable with an icon and the plottable name.
virtual QSize minimumOuterSizeHint() const override
QColor getTextColor() const
QCPPlottableLegendItem(QCPLegend *parent, QCPAbstractPlottable *plottable)
virtual void draw(QCPPainter *painter) override
The main container for polar plots, representing the angular axis as a circle.
virtual void update(UpdatePhase phase) override
QFont getLabelFont() const
void setTickPen(const QPen &pen)
void setBackground(const QPixmap &pm)
QSize size() const
virtual void applyDefaultAntialiasingHint(QCPPainter *painter) const override
void setNumberPrecision(int precision)
virtual QList< QCPLayoutElement * > elements(bool recursive) const override
QPointF center() const
void setTickLabels(bool show)
void setSelectedBasePen(const QPen &pen)
void setSubTickPen(const QPen &pen)
@ spTickLabels
Tick labels (numbers) of this axis (as a whole, not individually)
@ spNone
None of the selectable parts.
@ spAxis
The axis backbone and tick marks.
@ spAxisLabel
The axis label.
void setTicker(QSharedPointer< QCPAxisTicker > ticker)
void setSelectedSubTickPen(const QPen &pen)
void setNumberFormat(const QString &formatCode)
virtual void draw(QCPPainter *painter) override
QFont getTickLabelFont() const
void setSelectedTickLabelFont(const QFont &font)
void setTickLength(int inside, int outside=0)
QColor getLabelColor() const
Q_SLOT void setSelectedParts(const QCPPolarAxisAngular::SelectableParts &selectedParts)
void drawBackground(QCPPainter *painter, const QPointF &center, double radius)
void setSubTickLength(int inside, int outside=0)
void setSubTicks(bool show)
virtual void mousePressEvent(QMouseEvent *event, const QVariant &details) override
bool removeRadialAxis(QCPPolarAxisRadial *axis)
void setRangeUpper(double upper)
QPen getSubTickPen() const
void pixelToCoord(QPointF pixelPos, double &angleCoord, double &radiusCoord) const
void setBackgroundScaledMode(Qt::AspectRatioMode mode)
virtual QCP::Interaction selectionCategory() const override
void setBasePen(const QPen &pen)
void setLabel(const QString &str)
SelectablePart getPartAt(const QPointF &pos) const
void setLabelColor(const QColor &color)
void setTickLabelFont(const QFont &font)
void setRangeLower(double lower)
void setTickLabelColor(const QColor &color)
void setSubTickLengthIn(int inside)
void setLabelPadding(int padding)
void setSubTickLengthOut(int outside)
void setSelectedLabelColor(const QColor &color)
void setLabelFont(const QFont &font)
void rescale(bool onlyVisiblePlottables=false)
QCPPolarAxisRadial * radialAxis(int index=0) const
void setTickLengthIn(int inside)
virtual void wheelEvent(QWheelEvent *event) override
void setSelectedTickPen(const QPen &pen)
virtual void mouseMoveEvent(QMouseEvent *event, const QPointF &startPos) override
QList< QCPPolarAxisRadial * > radialAxes() const
virtual void mouseReleaseEvent(QMouseEvent *event, const QPointF &startPos) override
virtual double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details=0) const override
QColor getTickLabelColor() const
void setSelectedLabelFont(const QFont &font)
QCPPolarAxisRadial * addRadialAxis(QCPPolarAxisRadial *axis=0)
void setTickLengthOut(int outside)
void setTicks(bool show)
void setTickLabelPadding(int padding)
void setRangeReversed(bool reversed)
void scaleRange(double factor)
int radialAxisCount() const
void setSelectedTickLabelColor(const QColor &color)
void setTickLabelRotation(double degrees)
void setBackgroundScaled(bool scaled)
QPointF coordToPixel(double angleCoord, double radiusCoord) const
Q_SLOT void setSelectableParts(const QCPPolarAxisAngular::SelectableParts &selectableParts)
void moveRange(double diff)
QCPPolarAxisAngular(QCustomPlot *parentPlot)
Q_SLOT void setRange(const QCPRange &range)
The radial axis inside a radial plot.
void setSubTickLengthIn(int inside)
QSharedPointer< QCPAxisTicker > ticker() const
virtual void deselectEvent(bool *selectionStateChanged) override
void setNumberFormat(const QString &formatCode)
SelectablePart getPartAt(const QPointF &pos) const
void setSubTickPen(const QPen &pen)
void setSelectedTickLabelColor(const QColor &color)
@ stLogarithmic
Logarithmic scaling with correspondingly transformed axis coordinates (possibly also setTicker to a Q...
@ stLinear
Linear scaling.
void setSubTicks(bool show)
void scaleRange(double factor)
virtual QCP::Interaction selectionCategory() const override
virtual double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details=0) const override
void setTickLengthIn(int inside)
virtual void mouseReleaseEvent(QMouseEvent *event, const QPointF &startPos) override
@ arAngularAxis
The axis tilt is measured in the angular coordinate system given by the parent angular axis.
void selectionChanged(const QCPPolarAxisRadial::SelectableParts &parts)
void scaleTypeChanged(QCPPolarAxisRadial::ScaleType scaleType)
QPen getTickPen() const
QPen getSubTickPen() const
void setTickPen(const QPen &pen)
QFont getLabelFont() const
void rangeChanged(const QCPRange &newRange)
QFont getTickLabelFont() const
void setSelectedLabelColor(const QColor &color)
void setTickLabelRotation(double degrees)
Q_SLOT void setRange(const QCPRange &range)
void setTickLabelColor(const QColor &color)
void setTickLength(int inside, int outside=0)
virtual void draw(QCPPainter *painter) override
void setSelectedLabelFont(const QFont &font)
void setSubTickLengthOut(int outside)
void setTicks(bool show)
void setLabel(const QString &str)
void setBasePen(const QPen &pen)
void setLabelFont(const QFont &font)
void setTickLabels(bool show)
QColor getLabelColor() const
void setTickLabelPadding(int padding)
void setSelectedSubTickPen(const QPen &pen)
Q_SLOT void setSelectableParts(const QCPPolarAxisRadial::SelectableParts &selectableParts)
void setLabelColor(const QColor &color)
void setSelectedTickPen(const QPen &pen)
void setNumberPrecision(int precision)
void setSelectedTickLabelFont(const QFont &font)
QColor getTickLabelColor() const
void pixelToCoord(QPointF pixelPos, double &angleCoord, double &radiusCoord) const
void setTicker(QSharedPointer< QCPAxisTicker > ticker)
void setLabelPadding(int padding)
void moveRange(double diff)
QCPPolarAxisRadial(QCPPolarAxisAngular *parent)
void selectableChanged(const QCPPolarAxisRadial::SelectableParts &parts)
@ spTickLabels
Tick labels (numbers) of this axis (as a whole, not individually)
@ spAxis
The axis backbone and tick marks.
@ spNone
None of the selectable parts.
@ spAxisLabel
The axis label.
virtual void mouseMoveEvent(QMouseEvent *event, const QPointF &startPos) override
void setTickLabelFont(const QFont &font)
void setRangeLower(double lower)
void setRangeReversed(bool reversed)
void setRangeUpper(double upper)
virtual void mousePressEvent(QMouseEvent *event, const QVariant &details) override
void setSelectedBasePen(const QPen &pen)
virtual void selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged) override
virtual void applyDefaultAntialiasingHint(QCPPainter *painter) const override
void rescale(bool onlyVisiblePlottables=false)
void setTickLengthOut(int outside)
void setSubTickLength(int inside, int outside=0)
QPen getBasePen() const
virtual void wheelEvent(QWheelEvent *event) override
QPointF coordToPixel(double angleCoord, double radiusCoord) const
Q_SLOT void setScaleType(QCPPolarAxisRadial::ScaleType type)
Q_SLOT void setSelectedParts(const QCPPolarAxisRadial::SelectableParts &selectedParts)
A radial graph used to display data in polar plots.
void setValueAxis(QCPPolarAxisRadial *axis)
void applyDefaultAntialiasingHint(QCPPainter *painter) const override
virtual void deselectEvent(bool *selectionStateChanged) override
@ lsNone
data points are not connected with any lines (e.g.
@ lsLine
data points are connected by a straight line
void setKeyAxis(QCPPolarAxisAngular *axis)
QCPPolarGraph(QCPPolarAxisAngular *keyAxis, QCPPolarAxisRadial *valueAxis)
virtual void draw(QCPPainter *painter) override
QVector< QPointF > dataToLines(const QVector< QCPGraphData > &data) const
virtual QRect clipRect() const override
virtual void drawFill(QCPPainter *painter, QVector< QPointF > *lines) const
virtual void drawScatterPlot(QCPPainter *painter, const QVector< QPointF > &scatters, const QCPScatterStyle &style) const
void setData(QSharedPointer< QCPGraphDataContainer > data)
void setPen(const QPen &pen)
virtual void selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged) override
virtual QCP::Interaction selectionCategory() const override
void setName(const QString &name)
virtual double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details=0) const override
void setLineStyle(LineStyle ls)
Q_SLOT void setSelection(QCPDataSelection selection)
Q_SLOT void setSelectable(QCP::SelectionType selectable)
void setAntialiasedFill(bool enabled)
void setBrush(const QBrush &brush)
virtual void drawLinePlot(QCPPainter *painter, const QVector< QPointF > &lines) const
void setScatterStyle(const QCPScatterStyle &style)
void getLines(QVector< QPointF > *lines, const QCPDataRange &dataRange) const
void setAntialiasedScatters(bool enabled)
void coordsToPixels(double key, double value, double &x, double &y) const
The grid in both angular and radial dimensions for polar plots.
QCPPolarGrid(QCPPolarAxisAngular *parentAxis)
virtual void applyDefaultAntialiasingHint(QCPPainter *painter) const override
void setAngularSubGridPen(const QPen &pen)
void setAntialiasedZeroLine(bool enabled)
virtual void draw(QCPPainter *painter) override
void setAntialiasedSubGrid(bool enabled)
void setAngularPen(const QPen &pen)
A legend item for polar plots.
virtual void draw(QCPPainter *painter) override
virtual QSize minimumOuterSizeHint() const override
Represents the range an axis is encompassing.
void expand(const QCPRange &otherRange)
QCPRange bounded(double lowerBound, double upperBound) const
QCPRange sanitizedForLogScale() const
static const double maxRange
double size() const
QCPRange sanitizedForLinScale() const
QCPRange expanded(const QCPRange &otherRange) const
static bool validRange(double lower, double upper)
static const double minRange
bool contains(double value) const
void normalize()
Represents the visual appearance of scatter points.
bool isPenDefined() const
void setPixmap(const QPixmap &pixmap)
bool isNone() const
void setBrush(const QBrush &brush)
void setPen(const QPen &pen)
void setShape(ScatterShape shape)
void setFromOther(const QCPScatterStyle &other, ScatterProperties properties)
@ spShape
0x08 The shape property, see setShape
@ spSize
0x04 The size property, see setSize
@ spPen
0x01 The pen property, see setPen
@ spBrush
0x02 The brush property, see setBrush
void drawShape(QCPPainter *painter, const QPointF &pos) const
void setCustomPath(const QPainterPath &customPath)
void setSize(double size)
@ ssDot
\enumimage{ssDot.png} a single pixel (use ssDisc or ssCircle if you want a round shape with a certain...
@ ssCustom
custom painter operations are performed per scatter (As QPainterPath, see setCustomPath)
@ ssSquare
\enumimage{ssSquare.png} a square
@ ssDisc
\enumimage{ssDisc.png} a circle which is filled with the pen's color (not the brush as with ssCircle)
@ ssPlus
\enumimage{ssPlus.png} a plus
@ ssDiamond
\enumimage{ssDiamond.png} a diamond
@ ssCrossCircle
\enumimage{ssCrossCircle.png} a circle with a cross inside
@ ssPlusSquare
\enumimage{ssPlusSquare.png} a square with a plus inside
@ ssStar
\enumimage{ssStar.png} a star with eight arms, i.e. a combination of cross and plus
@ ssTriangleInverted
\enumimage{ssTriangleInverted.png} an equilateral triangle, standing on corner
@ ssPlusCircle
\enumimage{ssPlusCircle.png} a circle with a plus inside
@ ssCrossSquare
\enumimage{ssCrossSquare.png} a square with a cross inside
@ ssTriangle
\enumimage{ssTriangle.png} an equilateral triangle, standing on baseline
@ ssCircle
\enumimage{ssCircle.png} a circle
@ ssPixmap
a custom pixmap specified by setPixmap, centered on the data point coordinates
@ ssCross
\enumimage{ssCross.png} a cross
@ ssNone
no scatter symbols are drawn (e.g. in QCPGraph, data only represented with lines)
@ ssPeace
\enumimage{ssPeace.png} a circle, with one vertical and two downward diagonal lines
void applyTo(QCPPainter *painter, const QPen &defaultPen) const
void setBracketStyle(BracketStyle style)
void setBracketBrush(const QBrush &brush)
virtual void drawBracket(QCPPainter *painter, int direction) const
virtual void drawDecoration(QCPPainter *painter, QCPDataSelection selection) override
void setTangentToData(bool enabled)
QPointF getPixelCoordinates(const QCPPlottableInterface1D *interface1d, int dataIndex) const
@ bsEllipse
An ellipse is drawn. The size of the ellipse is given by the bracket width/height properties.
@ bsSquareBracket
A square bracket is drawn.
@ bsHalfEllipse
A half ellipse is drawn. The size of the ellipse is given by the bracket width/height properties.
double getTangentAngle(const QCPPlottableInterface1D *interface1d, int dataIndex, int direction) const
void setBracketPen(const QPen &pen)
void setTangentAverage(int pointCount)
Controls how a plottable's data selection is drawn.
QCPScatterStyle getFinalScatterStyle(const QCPScatterStyle &unselectedStyle) const
void applyBrush(QCPPainter *painter) const
virtual void copyFrom(const QCPSelectionDecorator *other)
virtual void drawDecoration(QCPPainter *painter, QCPDataSelection selection)
void applyPen(QCPPainter *painter) const
void setUsedScatterProperties(const QCPScatterStyle::ScatterProperties &properties)
void setBrush(const QBrush &brush)
void setScatterStyle(const QCPScatterStyle &scatterStyle, QCPScatterStyle::ScatterProperties usedProperties=QCPScatterStyle::spPen)
void setPen(const QPen &pen)
virtual bool registerWithPlottable(QCPAbstractPlottable *plottable)
Provides rect/rubber-band data selection and range zoom interaction.
void accepted(const QRect &rect, QMouseEvent *event)
virtual void keyPressEvent(QKeyEvent *event)
void changed(const QRect &rect, QMouseEvent *event)
QCPRange range(const QCPAxis *axis) const
virtual void startSelection(QMouseEvent *event)
virtual void applyDefaultAntialiasingHint(QCPPainter *painter) const override
virtual void endSelection(QMouseEvent *event)
void started(QMouseEvent *event)
virtual void moveSelection(QMouseEvent *event)
virtual void draw(QCPPainter *painter) override
void setBrush(const QBrush &brush)
bool isActive() const
void setPen(const QPen &pen)
QCPSelectionRect(QCustomPlot *parentPlot)
void canceled(const QRect &rect, QInputEvent *event)
Q_SLOT void cancel()
Holds the data of one single data point for QCPStatisticalBox.
virtual void drawStatisticalBox(QCPPainter *painter, QCPStatisticalBoxDataContainer::const_iterator it, const QCPScatterStyle &outlierStyle) const
void setData(QSharedPointer< QCPStatisticalBoxDataContainer > data)
void setWidth(double width)
void getVisibleDataBounds(QCPStatisticalBoxDataContainer::const_iterator &begin, QCPStatisticalBoxDataContainer::const_iterator &end) const
QVector< QLineF > getWhiskerBackboneLines(QCPStatisticalBoxDataContainer::const_iterator it) const
virtual void drawLegendIcon(QCPPainter *painter, const QRectF &rect) const override
void setWhiskerPen(const QPen &pen)
virtual QCPDataSelection selectTestRect(const QRectF &rect, bool onlySelectable) const override
virtual double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details=nullptr) const override
void setWhiskerAntialiased(bool enabled)
void setMedianPen(const QPen &pen)
QSharedPointer< QCPStatisticalBoxDataContainer > data() const
QCPStatisticalBox(QCPAxis *keyAxis, QCPAxis *valueAxis)
virtual QCPRange getValueRange(bool &foundRange, QCP::SignDomain inSignDomain=QCP::sdBoth, const QCPRange &inKeyRange=QCPRange()) const override
void addData(const QVector< double > &keys, const QVector< double > &minimum, const QVector< double > &lowerQuartile, const QVector< double > &median, const QVector< double > &upperQuartile, const QVector< double > &maximum, bool alreadySorted=false)
QRectF getQuartileBox(QCPStatisticalBoxDataContainer::const_iterator it) const
void setWhiskerBarPen(const QPen &pen)
virtual QCPRange getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain=QCP::sdBoth) const override
void setOutlierStyle(const QCPScatterStyle &style)
virtual void draw(QCPPainter *painter) override
void setWhiskerWidth(double width)
QVector< QLineF > getWhiskerBarLines(QCPStatisticalBoxDataContainer::const_iterator it) const
void setFont(const QFont &font)
void setSelectedFont(const QFont &font)
virtual void mouseDoubleClickEvent(QMouseEvent *event, const QVariant &details) override
virtual QSize maximumOuterSizeHint() const override
Q_SLOT void setSelectable(bool selectable)
virtual void selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged) override
void selectionChanged(bool selected)
void setTextColor(const QColor &color)
virtual void mousePressEvent(QMouseEvent *event, const QVariant &details) override
virtual void mouseReleaseEvent(QMouseEvent *event, const QPointF &startPos) override
QColor mainTextColor() const
virtual void draw(QCPPainter *painter) override
void doubleClicked(QMouseEvent *event)
virtual void applyDefaultAntialiasingHint(QCPPainter *painter) const override
void setTextFlags(int flags)
Q_SLOT void setSelected(bool selected)
void setSelectedTextColor(const QColor &color)
void setText(const QString &text)
void clicked(QMouseEvent *event)
virtual double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details=nullptr) const override
QCPTextElement(QCustomPlot *parentPlot)
QFont mainFont() const
virtual QSize minimumOuterSizeHint() const override
virtual void deselectEvent(bool *selectionStateChanged) override
Represents two doubles as a mathematical 2D vector.
QCPVector2D perpendicular() const
double length() const
double distanceSquaredToLine(const QCPVector2D &start, const QCPVector2D &end) const
double angle() const
double dot(const QCPVector2D &vec) const
QCPVector2D & operator-=(const QCPVector2D &vector)
QCPVector2D normalized() const
double lengthSquared() const
QCPVector2D & operator+=(const QCPVector2D &vector)
QCPVector2D & operator*=(double factor)
QPointF toPointF() const
bool isNull() const
QPoint toPoint() const
double distanceToStraightLine(const QCPVector2D &base, const QCPVector2D &direction) const
QCPVector2D & operator/=(double divisor)
The central class of the library. This is the QWidget which displays the plot and interacts with the ...
void legendDoubleClick(QCPLegend *legend, QCPAbstractLegendItem *item, QMouseEvent *event)
QCPLayer * currentLayer() const
void drawBackground(QCPPainter *painter)
QCPLayer * layer(const QString &name) const
void setSelectionRect(QCPSelectionRect *selectionRect)
void beforeReplot()
QList< QCPAxisRect * > axisRects() const
QCPAbstractItem * item() const
void setBackground(const QPixmap &pm)
void setBufferDevicePixelRatio(double ratio)
int itemCount() const
void toPainter(QCPPainter *painter, int width=0, int height=0)
void setupPaintBuffers()
QCPGraph * addGraph(QCPAxis *keyAxis=nullptr, QCPAxis *valueAxis=nullptr)
QCPAbstractPlottable * plottable(int index)
void setBackgroundScaled(bool scaled)
void setPlottingHint(QCP::PlottingHint hint, bool enabled=true)
QCustomPlot(QWidget *parent=nullptr)
void setViewport(const QRect &rect)
QList< QCPLayerable * > layerableListAt(const QPointF &pos, bool onlySelectable, QList< QVariant > *selectionDetails=nullptr) const
bool removeLayer(QCPLayer *layer)
void setInteraction(const QCP::Interaction &interaction, bool enabled=true)
@ rpQueuedReplot
Queues the entire replot for the next event loop iteration. This way multiple redundant replots can b...
@ rpRefreshHint
Whether to use immediate or queued refresh depends on whether the plotting hint QCP::phImmediateRefre...
@ rpImmediateRefresh
Replots immediately and repaints the widget immediately by calling QWidget::repaint() after the replo...
@ rpQueuedRefresh
Replots immediately, but queues the widget repaint, by calling QWidget::update() after the replot....
QCPAxisRect * axisRectAt(const QPointF &pos) const
void setBackgroundScaledMode(Qt::AspectRatioMode mode)
void setSelectionTolerance(int pixels)
void selectionChangedByUser()
PlottableType * plottableAt(const QPointF &pos, bool onlySelectable=false, int *dataIndex=nullptr) const
virtual Q_SLOT void processRectZoom(QRect rect, QMouseEvent *event)
int graphCount() const
void setInteractions(const QCP::Interactions &interactions)
int plottableCount() const
void axisDoubleClick(QCPAxis *axis, QCPAxis::SelectablePart part, QMouseEvent *event)
virtual void updateLayout()
void afterReplot()
bool hasPlottable(QCPAbstractPlottable *plottable) const
bool setCurrentLayer(const QString &name)
QCPLegend * legend
void mouseMove(QMouseEvent *event)
QList< QCPAbstractPlottable * > selectedPlottables() const
@ limAbove
Layer is inserted above other layer.
bool saveJpg(const QString &fileName, int width=0, int height=0, double scale=1.0, int quality=-1, int resolution=96, QCP::ResolutionUnit resolutionUnit=QCP::ruDotsPerInch)
void setNoAntialiasingOnDrag(bool enabled)
void legendClick(QCPLegend *legend, QCPAbstractLegendItem *item, QMouseEvent *event)
void setOpenGl(bool enabled, int multisampling=16)
QList< QCPAxis * > selectedAxes() const
void updateLayerIndices() const
void setSelectionRectMode(QCP::SelectionRectMode mode)
virtual void mouseReleaseEvent(QMouseEvent *event) override
virtual void mouseDoubleClickEvent(QMouseEvent *event) override
void plottableDoubleClick(QCPAbstractPlottable *plottable, int dataIndex, QMouseEvent *event)
ItemType * itemAt(const QPointF &pos, bool onlySelectable=false) const
virtual void axisRemoved(QCPAxis *axis)
virtual void resizeEvent(QResizeEvent *event) override
bool addLayer(const QString &name, QCPLayer *otherLayer=nullptr, LayerInsertMode insertMode=limAbove)
int axisRectCount() const
void setMultiSelectModifier(Qt::KeyboardModifier modifier)
bool removeGraph(QCPGraph *graph)
QCPAbstractPaintBuffer * createPaintBuffer()
void setPlottingHints(const QCP::PlottingHints &hints)
virtual QSize sizeHint() const override
QCPAxis * xAxis
QCPLayerable * layerableAt(const QPointF &pos, bool onlySelectable, QVariant *selectionDetails=nullptr) const
void mouseDoubleClick(QMouseEvent *event)
virtual void legendRemoved(QCPLegend *legend)
Q_SLOT void deselectAll()
void afterLayout()
Q_SLOT void replot(QCustomPlot::RefreshPriority refreshPriority=QCustomPlot::rpRefreshHint)
QPixmap toPixmap(int width=0, int height=0, double scale=1.0)
QCPGraph * graph() const
virtual void mousePressEvent(QMouseEvent *event) override
void axisClick(QCPAxis *axis, QCPAxis::SelectablePart part, QMouseEvent *event)
virtual Q_SLOT void processRectSelection(QRect rect, QMouseEvent *event)
virtual Q_SLOT void processPointSelection(QMouseEvent *event)
void mouseWheel(QWheelEvent *event)
void itemDoubleClick(QCPAbstractItem *item, QMouseEvent *event)
QList< QCPLegend * > selectedLegends() const
void mouseRelease(QMouseEvent *event)
bool savePng(const QString &fileName, int width=0, int height=0, double scale=1.0, int quality=-1, int resolution=96, QCP::ResolutionUnit resolutionUnit=QCP::ruDotsPerInch)
void mousePress(QMouseEvent *event)
bool registerGraph(QCPGraph *graph)
virtual void wheelEvent(QWheelEvent *event) override
QList< QCPGraph * > selectedGraphs() const
bool hasInvalidatedPaintBuffers()
double replotTime(bool average=false) const
bool savePdf(const QString &fileName, int width=0, int height=0, QCP::ExportPen exportPen=QCP::epAllowCosmetic, const QString &pdfCreator=QString(), const QString &pdfTitle=QString())
bool saveRastered(const QString &fileName, int width, int height, double scale, const char *format, int quality=-1, int resolution=96, QCP::ResolutionUnit resolutionUnit=QCP::ruDotsPerInch)
virtual void draw(QCPPainter *painter)
QCPSelectionRect * selectionRect() const
Q_SLOT void rescaleAxes(bool onlyVisiblePlottables=false)
void setAutoAddPlottableToLegend(bool on)
QCPAxis * xAxis2
QCPAbstractPlottable * plottable()
bool removeItem(QCPAbstractItem *item)
void setNotAntialiasedElements(const QCP::AntialiasedElements &notAntialiasedElements)
void itemClick(QCPAbstractItem *item, QMouseEvent *event)
bool saveBmp(const QString &fileName, int width=0, int height=0, double scale=1.0, int resolution=96, QCP::ResolutionUnit resolutionUnit=QCP::ruDotsPerInch)
QCPAxisRect * axisRect(int index=0) const
bool moveLayer(QCPLayer *layer, QCPLayer *otherLayer, LayerInsertMode insertMode=limAbove)
virtual void paintEvent(QPaintEvent *event) override
bool registerPlottable(QCPAbstractPlottable *plottable)
void setAntialiasedElement(QCP::AntialiasedElement antialiasedElement, bool enabled=true)
bool hasItem(QCPAbstractItem *item) const
QCPAxis * yAxis2
virtual QSize minimumSizeHint() const override
bool removePlottable(QCPAbstractPlottable *plottable)
void plottableClick(QCPAbstractPlottable *plottable, int dataIndex, QMouseEvent *event)
virtual void mouseMoveEvent(QMouseEvent *event) override
void setAntialiasedElements(const QCP::AntialiasedElements &antialiasedElements)
QCPAxis * yAxis
int layerCount() const
QCPLayoutElement * layoutElementAt(const QPointF &pos) const
bool registerItem(QCPAbstractItem *item)
void setNotAntialiasedElement(QCP::AntialiasedElement notAntialiasedElement, bool enabled=true)
QList< QCPAbstractItem * > selectedItems() const
VehicleSection::Type type(QStringView coachNumber, QStringView coachClassification)
const QList< QKeySequence > & begin()
const QList< QKeySequence > & end()
bool isInvalidData(double value)
@ epNoCosmetic
Cosmetic pens are converted to pens with pixel width 1 when exporting.
int getMarginValue(const QMargins &margins, QCP::MarginSide side)
Interaction
@ iSelectLegend
0x020 Legends are selectable (or their child items, see QCPLegend::setSelectableParts)
@ iRangeDrag
0x001 Axis ranges are draggable (see QCPAxisRect::setRangeDrag, QCPAxisRect::setRangeDragAxes)
@ iSelectPlottables
0x008 Plottables are selectable (e.g. graphs, curves, bars,... see QCPAbstractPlottable)
@ iRangeZoom
0x002 Axis ranges are zoomable with the mouse wheel (see QCPAxisRect::setRangeZoom,...
@ iSelectPlottablesBeyondAxisRect
0x100 When performing plottable selection/hit tests, this flag extends the sensitive area beyond the ...
@ iSelectAxes
0x010 Axes are selectable (or parts of them, see QCPAxis::setSelectableParts)
@ iSelectItems
0x040 Items are selectable (Rectangles, Arrows, Textitems, etc. see QCPAbstractItem)
@ iMultiSelect
0x004 The user can select multiple objects by holding the modifier set by QCustomPlot::setMultiSelect...
@ iSelectOther
0x080 All other objects are selectable (e.g. your own derived layerables, other layout elements,...
PlottingHint
@ phImmediateRefresh
0x002 causes an immediate repaint() instead of a soft update() when QCustomPlot::replot() is called w...
@ phCacheLabels
0x004 axis (tick) labels will be cached as pixmaps, increasing replot performance.
@ phFastPolylines
0x001 Graph/Curve lines are drawn with a faster method.
ResolutionUnit
@ ruDotsPerCentimeter
Resolution is given in dots per centimeter (dpcm)
@ ruDotsPerMeter
Resolution is given in dots per meter (dpm)
@ ruDotsPerInch
Resolution is given in dots per inch (DPI/PPI)
@ msAll
0xFF all margins
@ msBottom
0x08 bottom margin
@ msTop
0x04 top margin
@ msNone
0x00 no margin
@ msRight
0x02 right margin
@ msLeft
0x01 left margin
SelectionType
@ stMultipleDataRanges
Any combination of data points/ranges can be selected.
@ stDataRange
Multiple contiguous data points (a data range) can be selected.
@ stNone
The plottable is not selectable.
@ stSingleData
One individual data point can be selected at a time.
@ stWhole
Selection behaves like stMultipleDataRanges, but if there are any data points selected,...
SelectionRectMode
@ srmSelect
When dragging the mouse, a selection rect becomes active. Upon releasing, plottable data points that ...
@ srmZoom
When dragging the mouse, a selection rect becomes active. Upon releasing, the axes that are currently...
@ srmNone
The selection rect is disabled, and all mouse events are forwarded to the underlying objects,...
AntialiasedElement
@ aeLegendItems
0x0010 Legend items
@ aeZeroLine
0x0200 Zero-lines, see QCPGrid::setZeroLinePen
@ aePlottables
0x0020 Main lines of plottables
@ aeGrid
0x0002 Grid lines
@ aeOther
0x8000 Other elements that don't fit into any of the existing categories
@ aeFills
0x0100 Borders of fills (e.g. under or between graphs)
@ aeLegend
0x0008 Legend box
@ aeAll
0xFFFF All elements
@ aeSubGrid
0x0004 Sub grid lines
@ aeScatters
0x0080 Scatter symbols of plottables (excluding scatter symbols of type ssPixmap)
@ aeAxes
0x0001 Axis base line and tick marks
@ aeItems
0x0040 Main lines of items
void setMarginValue(QMargins &margins, QCP::MarginSide side, int value)
@ sdNegative
The negative sign domain, i.e. numbers smaller than zero.
@ sdPositive
The positive sign domain, i.e. numbers greater than zero.
@ sdBoth
Both sign domains, including zero, i.e. all numbers.
const QColor & color() const const
void setColor(Qt::GlobalColor color)
void setStyle(Qt::BrushStyle style)
Qt::BrushStyle style() const const
QByteArray & append(QByteArrayView data)
QByteArray number(double n, char format, int precision)
void clear()
bool contains(const Key &key) const const
bool insert(const Key &key, T *object, qsizetype cost)
qsizetype maxCost() const const
T * object(const Key &key) const const
void setMaxCost(qsizetype cost)
T * take(const Key &key)
bool isDigit(char32_t ucs4)
char toLatin1() const const
int alpha() const const
float alphaF() const const
int blue() const const
float blueF() const const
QColor fromHsvF(float h, float s, float v, float a)
int green() const const
float greenF() const const
float hueF() const const
QString name(NameFormat format) const const
int red() const const
float redF() const const
QRgb rgb() const const
QRgb rgba() const const
float saturationF() const const
int value() const const
float valueF() const const
int day() const const
int daysInMonth() const const
int month() const const
QDateTime startOfDay() const const
int year() const const
QDateTime addMSecs(qint64 msecs) const const
QDateTime addMonths(int nmonths) const const
QDate date() const const
QDateTime fromMSecsSinceEpoch(qint64 msecs)
void setDate(QDate date)
void setTime(QTime time)
QTime time() const const
qint64 toMSecsSinceEpoch() const const
bool testFlag(Enum flag) const const
int pixelSize() const const
int pointSize() const const
qreal pointSizeF() const const
void setPixelSize(int pixelSize)
void setPointSize(int pointSize)
void setPointSizeF(qreal pointSize)
QString toString() const const
QRect boundingRect(QChar ch) const const
bool contains(const Key &key) const const
iterator insert(const Key &key, const T &value)
bool remove(const Key &key)
T value(const Key &key) const const
bool hasNext() const const
const Key & key() const const
const T & value() const const
void fill(Qt::GlobalColor color)
int height() const const
bool isNull() const const
QImage mirrored(bool horizontal, bool vertical) &&
bool save(QIODevice *device, const char *format, int quality) const const
QImage scaled(const QSize &size, Qt::AspectRatioMode aspectRatioMode, Qt::TransformationMode transformMode) const const
uchar * scanLine(int i)
void setDotsPerMeterX(int x)
void setDotsPerMeterY(int y)
int width() const const
qreal dx() const const
qreal dy() const const
bool isNull() const const
QPointF p1() const const
QPointF p2() const const
void setLine(qreal x1, qreal y1, qreal x2, qreal y2)
void setPoints(const QPointF &p1, const QPointF &p2)
QLine toLine() const const
QLineF translated(const QPointF &offset) const const
qreal x1() const const
qreal x2() const const
qreal y1() const const
qreal y2() const const
QList< T > toVector() const const
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
iterator begin()
void clear()
const_iterator constBegin() const const
const_pointer constData() const const
const_iterator constEnd() const const
bool contains(const AT &value) const const
iterator end()
T & first()
qsizetype indexOf(const AT &value, qsizetype from) const const
iterator insert(const_iterator before, parameter_type value)
bool isEmpty() const const
T & last()
QList< T > mid(qsizetype pos, qsizetype length) const const
void move(qsizetype from, qsizetype to)
void prepend(parameter_type value)
void remove(qsizetype i, qsizetype n)
qsizetype removeAll(const AT &t)
void removeAt(qsizetype i)
void removeLast()
bool removeOne(const AT &t)
void reserve(qsizetype size)
void resize(qsizetype size)
qsizetype size() const const
OmitGroupSeparator
void setNumberOptions(NumberOptions options)
QString toString(QDate date, FormatType format) const const
void clear()
const_iterator constBegin() const const
const_iterator constEnd() const const
iterator insert(const Key &key, const T &value)
bool isEmpty() const const
iterator lowerBound(const Key &key)
size_type size() const const
iterator upperBound(const Key &key)
T value(const Key &key, const T &defaultValue) const const
int bottom() const const
int left() const const
int right() const const
int top() const const
const QObjectList & children() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
virtual bool event(QEvent *e)
QObject * parent() const const
T qobject_cast(QObject *object)
QObject * sender() const const
void setParent(QObject *parent)
void setFormat(const QSurfaceFormat &format)
QOpenGLContext * currentContext()
void setAttachment(QOpenGLFramebufferObject::Attachment attachment)
void setSamples(int samples)
virtual bool setPageLayout(const QPageLayout &newPageLayout)
bool setMargins(const QMarginsF &margins)
void setMode(Mode mode)
void setOrientation(Orientation orientation)
void setPageSize(const QPageSize &pageSize, const QMarginsF &minMargins)
qreal devicePixelRatio() const const
qreal devicePixelRatioF() const const
bool begin(QPaintDevice *device)
const QBrush & brush() const const
QRectF clipBoundingRect() const const
QRegion clipRegion() const const
QPaintDevice * device() const const
void drawArc(const QRect &rectangle, int startAngle, int spanAngle)
void drawConvexPolygon(const QPoint *points, int pointCount)
void drawEllipse(const QPoint &center, int rx, int ry)
void drawImage(const QPoint &point, const QImage &image)
void drawLine(const QLine &line)
void drawLines(const QLine *lines, int lineCount)
void drawPath(const QPainterPath &path)
void drawPixmap(const QPoint &point, const QPixmap &pixmap)
void drawPolygon(const QPoint *points, int pointCount, Qt::FillRule fillRule)
void drawPolyline(const QPoint *points, int pointCount)
void drawRect(const QRect &rectangle)
void drawText(const QPoint &position, const QString &text)
bool end()
void fillPath(const QPainterPath &path, const QBrush &brush)
void fillRect(const QRect &rectangle, QGradient::Preset preset)
const QFont & font() const const
QFontMetrics fontMetrics() const const
bool isActive() const const
const QPen & pen() const const
RenderHints renderHints() const const
void restore()
void rotate(qreal angle)
void save()
void scale(qreal sx, qreal sy)
void setBrush(Qt::BrushStyle style)
void setClipRect(const QRect &rectangle, Qt::ClipOperation operation)
void setClipRegion(const QRegion &region, Qt::ClipOperation operation)
void setFont(const QFont &font)
void setPen(Qt::PenStyle style)
void setRenderHint(RenderHint hint, bool on)
void setTransform(const QTransform &transform, bool combine)
void setWindow(const QRect &rectangle)
const QTransform & transform() const const
void translate(const QPoint &offset)
void addEllipse(const QPointF &center, qreal rx, qreal ry)
qreal angleAtPercent(qreal t) const const
QRectF controlPointRect() const const
void cubicTo(const QPointF &c1, const QPointF &c2, const QPointF &endPoint)
QList< QPolygonF > toSubpathPolygons(const QTransform &matrix) const const
Qt::PenCapStyle capStyle() const const
QColor color() const const
bool isCosmetic() const const
void setCapStyle(Qt::PenCapStyle style)
void setColor(const QColor &color)
void setJoinStyle(Qt::PenJoinStyle style)
void setStyle(Qt::PenStyle style)
void setWidth(int width)
Qt::PenStyle style() const const
qreal widthF() const const
qreal devicePixelRatio() const const
void fill(const QColor &color)
QPixmap fromImage(QImage &&image, Qt::ImageConversionFlags flags)
int height() const const
bool isNull() const const
QRect rect() const const
QPixmap scaled(const QSize &size, Qt::AspectRatioMode aspectRatioMode, Qt::TransformationMode transformMode) const const
void setDevicePixelRatio(qreal scaleFactor)
QSize size() const const
QImage toImage() const const
int width() const const
void setX(int x)
void setY(int y)
int x() const const
int y() const const
T * data() const const
bool isNull() const const
qreal & rx()
qreal & ry()
void setX(qreal x)
void setY(qreal y)
QPoint toPoint() const const
qreal x() const const
qreal y() const const
QRect boundingRect() const const
virtual void setProperty(PrintEnginePropertyKey key, const QVariant &value)=0
QPrintEngine * printEngine() const const
void setColorMode(ColorMode newColorMode)
void setFullPage(bool fp)
void setOutputFileName(const QString &fileName)
void setOutputFormat(OutputFormat format)
void adjust(int dx1, int dy1, int dx2, int dy2)
QRect adjusted(int dx1, int dy1, int dx2, int dy2) const const
int bottom() const const
QPoint bottomLeft() const const
QPoint bottomRight() const const
QPoint center() const const
bool contains(const QPoint &point, bool proper) const const
int height() const const
bool intersects(const QRect &rectangle) const const
bool isEmpty() const const
bool isNull() const const
int left() const const
void moveTopLeft(const QPoint &position)
QRect normalized() const const
int right() const const
void setBottomRight(const QPoint &position)
void setCoords(int x1, int y1, int x2, int y2)
QSize size() const const
int top() const const
QPoint topLeft() const const
QPoint topRight() const const
QRect translated(const QPoint &offset) const const
int width() const const
int x() const const
int y() const const
void adjust(qreal dx1, qreal dy1, qreal dx2, qreal dy2)
QRectF adjusted(qreal dx1, qreal dy1, qreal dx2, qreal dy2) const const
qreal bottom() const const
QPointF bottomLeft() const const
QPointF bottomRight() const const
QPointF center() const const
bool contains(const QPointF &point) const const
qreal height() const const
bool intersects(const QRectF &rectangle) const const
qreal left() const const
void moveBottom(qreal y)
void moveCenter(const QPointF &position)
void moveLeft(qreal x)
void moveRight(qreal x)
void moveTop(qreal y)
void moveTopLeft(const QPointF &position)
QRectF normalized() const const
qreal right() const const
void setBottomRight(const QPointF &position)
void setHeight(qreal height)
void setSize(const QSizeF &size)
void setTopLeft(const QPointF &position)
void setWidth(qreal width)
QSizeF size() const const
QRect toRect() const const
qreal top() const const
QPointF topLeft() const const
QPointF topRight() const const
QRectF translated(const QPointF &offset) const const
qreal width() const const
QRect boundingRect() const const
bool contains(const QSet< T > &other) const const
iterator insert(const T &value)
bool remove(const T &value)
QList< T > values() const const
T * data() const const
QSharedPointer< X > dynamicCast() const const
int height() const const
int & rheight()
int & rwidth()
void scale(const QSize &size, Qt::AspectRatioMode mode)
void setHeight(int height)
void setWidth(int width)
int width() const const
qreal height() const const
QSize toSize() const const
qreal width() const const
void push(const T &t)
QString & append(QChar ch)
QString arg(Args &&... args) const const
const QChar at(qsizetype position) const const
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
QString fromUtf8(QByteArrayView str)
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
QString left(qsizetype n) const const
qsizetype length() const const
QString mid(qsizetype position, qsizetype n) const const
QString normalized(NormalizationForm mode, QChar::UnicodeVersion version) const const
QString number(double n, char format, int precision)
QString & prepend(QChar ch)
QString & remove(QChar ch, Qt::CaseSensitivity cs)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
qsizetype size() const const
QByteArray toLatin1() const const
QByteArray toUtf8() const const
QString trimmed() const const
void setSamples(int numSamples)
typedef Alignment
AspectRatioMode
SolidPattern
IntersectClip
ClickFocus
transparent
Key_Escape
KeyboardModifier
LeftButton
Horizontal
MiterJoin
PenStyle
TextDontClip
TimeSpec
SmoothTransformation
WA_NoMousePropagation
QTextStream & center(QTextStream &stream)
int msec() const const
QLine map(const QLine &l) const const
QRect mapRect(const QRect &rectangle) const const
QTransform & rotate(qreal a, Qt::Axis axis)
QTransform & translate(qreal dx, qreal dy)
QVariant fromValue(T &&value)
void setValue(QVariant &&value)
T value() const const
QSharedPointer< T > toStrongRef() const const
virtual bool event(QEvent *event) override
void setFocusPolicy(Qt::FocusPolicy policy)
void setMouseTracking(bool enable)
void repaint()
void setAttribute(Qt::WidgetAttribute attribute, bool on)
void update()
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Oct 11 2024 12:15:10 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.