mirror of
				https://github.com/saitohirga/WSJT-X.git
				synced 2025-10-31 13:10:19 -04:00 
			
		
		
		
	git-svn-id: svn+ssh://svn.code.sf.net/p/wsjt/wsjt/branches/wsjtx@7285 ab8295b8-cf94-4d9e-aec4-7959e3be5d79
		
			
				
	
	
		
			29757 lines
		
	
	
		
			1.1 MiB
		
	
	
	
	
	
	
	
			
		
		
	
	
			29757 lines
		
	
	
		
			1.1 MiB
		
	
	
	
	
	
	
	
| /***************************************************************************
 | |
| **                                                                        **
 | |
| **  QCustomPlot, an easy to use, modern plotting widget for Qt            **
 | |
| **  Copyright (C) 2011-2016 Emanuel Eichhammer                            **
 | |
| **                                                                        **
 | |
| **  This program is free software: you can redistribute it and/or modify  **
 | |
| **  it under the terms of the GNU General Public License as published by  **
 | |
| **  the Free Software Foundation, either version 3 of the License, or     **
 | |
| **  (at your option) any later version.                                   **
 | |
| **                                                                        **
 | |
| **  This program is distributed in the hope that it will be useful,       **
 | |
| **  but WITHOUT ANY WARRANTY; without even the implied warranty of        **
 | |
| **  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         **
 | |
| **  GNU General Public License for more details.                          **
 | |
| **                                                                        **
 | |
| **  You should have received a copy of the GNU General Public License     **
 | |
| **  along with this program.  If not, see http://www.gnu.org/licenses/.   **
 | |
| **                                                                        **
 | |
| ****************************************************************************
 | |
| **           Author: Emanuel Eichhammer                                   **
 | |
| **  Website/Contact: http://www.qcustomplot.com/                          **
 | |
| **             Date: 13.09.16                                             **
 | |
| **          Version: 2.0.0-beta                                           **
 | |
| ****************************************************************************/
 | |
| 
 | |
| #include "qcustomplot.h"
 | |
| #define QT_NO_PRINTER 1
 | |
| 
 | |
| /* including file 'src/vector2d.cpp', size 7340                              */
 | |
| /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPVector2D
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPVector2D
 | |
|   \brief Represents two doubles as a mathematical 2D vector
 | |
|   
 | |
|   This class acts as a replacement for QVector2D with the advantage of double precision instead of
 | |
|   single, and some convenience methods tailored for the QCustomPlot library.
 | |
| */
 | |
| 
 | |
| /* start documentation of inline functions */
 | |
| 
 | |
| /*! \fn void QCPVector2D::setX(double x)
 | |
|   
 | |
|   Sets the x coordinate of this vector to \a x.
 | |
|   
 | |
|   \see setY
 | |
| */
 | |
| 
 | |
| /*! \fn void QCPVector2D::setY(double y)
 | |
|   
 | |
|   Sets the y coordinate of this vector to \a y.
 | |
|   
 | |
|   \see setX
 | |
| */
 | |
| 
 | |
| /*! \fn double QCPVector2D::length() const
 | |
|   
 | |
|   Returns the length of this vector.
 | |
|   
 | |
|   \see lengthSquared
 | |
| */
 | |
| 
 | |
| /*! \fn double QCPVector2D::lengthSquared() const
 | |
|   
 | |
|   Returns the squared length of this vector. In some situations, e.g. when just trying to find the
 | |
|   shortest vector of a group, this is faster than calculating \ref length, because it avoids
 | |
|   calculation of a square root.
 | |
|   
 | |
|   \see length
 | |
| */
 | |
| 
 | |
| /*! \fn QPoint QCPVector2D::toPoint() const
 | |
|   
 | |
|   Returns a QPoint which has the x and y coordinates of this vector, truncating any floating point
 | |
|   information.
 | |
|   
 | |
|   \see toPointF
 | |
| */
 | |
| 
 | |
| /*! \fn QPointF QCPVector2D::toPointF() const
 | |
|   
 | |
|   Returns a QPointF which has the x and y coordinates of this vector.
 | |
|   
 | |
|   \see toPoint
 | |
| */
 | |
| 
 | |
| /*! \fn bool QCPVector2D::isNull() const
 | |
|   
 | |
|   Returns whether this vector is null. A vector is null if \c qIsNull returns true for both x and y
 | |
|   coordinates, i.e. if both are binary equal to 0.
 | |
| */
 | |
| 
 | |
| /*! \fn QCPVector2D QCPVector2D::perpendicular() const
 | |
|   
 | |
|   Returns a vector perpendicular to this vector, with the same length.
 | |
| */
 | |
| 
 | |
| /*! \fn double QCPVector2D::dot() const
 | |
|   
 | |
|   Returns the dot/scalar product of this vector with the specified vector \a vec.
 | |
| */
 | |
| 
 | |
| /* end documentation of inline functions */
 | |
| 
 | |
| /*!
 | |
|   Creates a QCPVector2D object and initializes the x and y coordinates to 0.
 | |
| */
 | |
| QCPVector2D::QCPVector2D() :
 | |
|   mX(0),
 | |
|   mY(0)
 | |
| {
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Creates a QCPVector2D object and initializes the \a x and \a y coordinates with the specified
 | |
|   values.
 | |
| */
 | |
| QCPVector2D::QCPVector2D(double x, double y) :
 | |
|   mX(x),
 | |
|   mY(y)
 | |
| {
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Creates a QCPVector2D object and initializes the x and y coordinates respective coordinates of
 | |
|   the specified \a point.
 | |
| */
 | |
| QCPVector2D::QCPVector2D(const QPoint &point) :
 | |
|   mX(point.x()),
 | |
|   mY(point.y())
 | |
| {
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Creates a QCPVector2D object and initializes the x and y coordinates respective coordinates of
 | |
|   the specified \a point.
 | |
| */
 | |
| QCPVector2D::QCPVector2D(const QPointF &point) :
 | |
|   mX(point.x()),
 | |
|   mY(point.y())
 | |
| {
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Normalizes this vector. After this operation, the length of the vector is equal to 1.
 | |
|   
 | |
|   \see normalized, length, lengthSquared
 | |
| */
 | |
| void QCPVector2D::normalize()
 | |
| {
 | |
|   double len = length();
 | |
|   mX /= len;
 | |
|   mY /= len;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns a normalized version of this vector. The length of the returned vector is equal to 1.
 | |
|   
 | |
|   \see normalize, length, lengthSquared
 | |
| */
 | |
| QCPVector2D QCPVector2D::normalized() const
 | |
| {
 | |
|   QCPVector2D result(mX, mY);
 | |
|   result.normalize();
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Returns the squared shortest distance of this vector (interpreted as a point) to the finite line
 | |
|   segment given by \a start and \a end.
 | |
|   
 | |
|   \see distanceToStraightLine
 | |
| */
 | |
| double QCPVector2D::distanceSquaredToLine(const QCPVector2D &start, const QCPVector2D &end) const
 | |
| {
 | |
|   QCPVector2D v(end-start);
 | |
|   double vLengthSqr = v.lengthSquared();
 | |
|   if (!qFuzzyIsNull(vLengthSqr))
 | |
|   {
 | |
|     double mu = v.dot(*this-start)/vLengthSqr;
 | |
|     if (mu < 0)
 | |
|       return (*this-start).lengthSquared();
 | |
|     else if (mu > 1)
 | |
|       return (*this-end).lengthSquared();
 | |
|     else
 | |
|       return ((start + mu*v)-*this).lengthSquared();
 | |
|   } else
 | |
|     return (*this-start).lengthSquared();
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Returns the squared shortest distance of this vector (interpreted as a point) to the finite line
 | |
|   segment given by \a line.
 | |
|   
 | |
|   \see distanceToStraightLine
 | |
| */
 | |
| double QCPVector2D::distanceSquaredToLine(const QLineF &line) const
 | |
| {
 | |
|   return distanceSquaredToLine(QCPVector2D(line.p1()), QCPVector2D(line.p2()));
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the shortest distance of this vector (interpreted as a point) to the infinite straight
 | |
|   line given by a \a base point and a \a direction vector.
 | |
|   
 | |
|   \see distanceSquaredToLine
 | |
| */
 | |
| double QCPVector2D::distanceToStraightLine(const QCPVector2D &base, const QCPVector2D &direction) const
 | |
| {
 | |
|   return qAbs((*this-base).dot(direction.perpendicular()))/direction.length();
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Scales this vector by the given \a factor, i.e. the x and y components are multiplied by \a
 | |
|   factor.
 | |
| */
 | |
| QCPVector2D &QCPVector2D::operator*=(double factor)
 | |
| {
 | |
|   mX *= factor;
 | |
|   mY *= factor;
 | |
|   return *this;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Scales this vector by the given \a divisor, i.e. the x and y components are divided by \a
 | |
|   divisor.
 | |
| */
 | |
| QCPVector2D &QCPVector2D::operator/=(double divisor)
 | |
| {
 | |
|   mX /= divisor;
 | |
|   mY /= divisor;
 | |
|   return *this;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Adds the given \a vector to this vector component-wise.
 | |
| */
 | |
| QCPVector2D &QCPVector2D::operator+=(const QCPVector2D &vector)
 | |
| {
 | |
|   mX += vector.mX;
 | |
|   mY += vector.mY;
 | |
|   return *this;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   subtracts the given \a vector from this vector component-wise.
 | |
| */
 | |
| QCPVector2D &QCPVector2D::operator-=(const QCPVector2D &vector)
 | |
| {
 | |
|   mX -= vector.mX;
 | |
|   mY -= vector.mY;
 | |
|   return *this;
 | |
| }
 | |
| /* end of 'src/vector2d.cpp' */
 | |
| 
 | |
| 
 | |
| /* including file 'src/painter.cpp', size 8670                               */
 | |
| /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPPainter
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPPainter
 | |
|   \brief QPainter subclass used internally
 | |
|   
 | |
|   This QPainter subclass is used to provide some extended functionality e.g. for tweaking position
 | |
|   consistency between antialiased and non-antialiased painting. Further it provides workarounds
 | |
|   for QPainter quirks.
 | |
|   
 | |
|   \warning This class intentionally hides non-virtual functions of QPainter, e.g. setPen, save and
 | |
|   restore. So while it is possible to pass a QCPPainter instance to a function that expects a
 | |
|   QPainter pointer, some of the workarounds and tweaks will be unavailable to the function (because
 | |
|   it will call the base class implementations of the functions actually hidden by QCPPainter).
 | |
| */
 | |
| 
 | |
| /*!
 | |
|   Creates a new QCPPainter instance and sets default values
 | |
| */
 | |
| QCPPainter::QCPPainter() :
 | |
|   QPainter(),
 | |
|   mModes(pmDefault),
 | |
|   mIsAntialiasing(false)
 | |
| {
 | |
|   // don't setRenderHint(QPainter::NonCosmeticDefautPen) here, because painter isn't active yet and
 | |
|   // a call to begin() will follow
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Creates a new QCPPainter instance on the specified paint \a device and sets default values. Just
 | |
|   like the analogous QPainter constructor, begins painting on \a device immediately.
 | |
|   
 | |
|   Like \ref begin, this method sets QPainter::NonCosmeticDefaultPen in Qt versions before Qt5.
 | |
| */
 | |
| QCPPainter::QCPPainter(QPaintDevice *device) :
 | |
|   QPainter(device),
 | |
|   mModes(pmDefault),
 | |
|   mIsAntialiasing(false)
 | |
| {
 | |
| #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.
 | |
|   if (isActive())
 | |
|     setRenderHint(QPainter::NonCosmeticDefaultPen);
 | |
| #endif
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the pen of the painter and applies certain fixes to it, depending on the mode of this
 | |
|   QCPPainter.
 | |
|   
 | |
|   \note this function hides the non-virtual base class implementation.
 | |
| */
 | |
| void QCPPainter::setPen(const QPen &pen)
 | |
| {
 | |
|   QPainter::setPen(pen);
 | |
|   if (mModes.testFlag(pmNonCosmetic))
 | |
|     makeNonCosmetic();
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Sets the pen (by color) of the painter and applies certain fixes to it, depending on the mode of
 | |
|   this QCPPainter.
 | |
|   
 | |
|   \note this function hides the non-virtual base class implementation.
 | |
| */
 | |
| void QCPPainter::setPen(const QColor &color)
 | |
| {
 | |
|   QPainter::setPen(color);
 | |
|   if (mModes.testFlag(pmNonCosmetic))
 | |
|     makeNonCosmetic();
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Sets the pen (by style) of the painter and applies certain fixes to it, depending on the mode of
 | |
|   this QCPPainter.
 | |
|   
 | |
|   \note this function hides the non-virtual base class implementation.
 | |
| */
 | |
| void QCPPainter::setPen(Qt::PenStyle penStyle)
 | |
| {
 | |
|   QPainter::setPen(penStyle);
 | |
|   if (mModes.testFlag(pmNonCosmetic))
 | |
|     makeNonCosmetic();
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Works around a Qt bug introduced with Qt 4.8 which makes drawing QLineF unpredictable when
 | |
|   antialiasing is disabled. Thus when antialiasing is disabled, it rounds the \a line to
 | |
|   integer coordinates and then passes it to the original drawLine.
 | |
|   
 | |
|   \note this function hides the non-virtual base class implementation.
 | |
| */
 | |
| void QCPPainter::drawLine(const QLineF &line)
 | |
| {
 | |
|   if (mIsAntialiasing || mModes.testFlag(pmVectorized))
 | |
|     QPainter::drawLine(line);
 | |
|   else
 | |
|     QPainter::drawLine(line.toLine());
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets whether painting uses antialiasing or not. Use this method instead of using setRenderHint
 | |
|   with QPainter::Antialiasing directly, as it allows QCPPainter to regain pixel exactness between
 | |
|   antialiased and non-antialiased painting (Since Qt < 5.0 uses slightly different coordinate systems for
 | |
|   AA/Non-AA painting).
 | |
| */
 | |
| void QCPPainter::setAntialiasing(bool enabled)
 | |
| {
 | |
|   setRenderHint(QPainter::Antialiasing, enabled);
 | |
|   if (mIsAntialiasing != enabled)
 | |
|   {
 | |
|     mIsAntialiasing = enabled;
 | |
|     if (!mModes.testFlag(pmVectorized)) // antialiasing half-pixel shift only needed for rasterized outputs
 | |
|     {
 | |
|       if (mIsAntialiasing)
 | |
|         translate(0.5, 0.5);
 | |
|       else
 | |
|         translate(-0.5, -0.5);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the mode of the painter. This controls whether the painter shall adjust its
 | |
|   fixes/workarounds optimized for certain output devices.
 | |
| */
 | |
| void QCPPainter::setModes(QCPPainter::PainterModes modes)
 | |
| {
 | |
|   mModes = modes;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the QPainter::NonCosmeticDefaultPen in Qt versions before Qt5 after beginning painting on \a
 | |
|   device. This is necessary to get cosmetic pen consistency across Qt versions, because since Qt5,
 | |
|   all pens are non-cosmetic by default, and in Qt4 this render hint must be set to get that
 | |
|   behaviour.
 | |
|   
 | |
|   The Constructor \ref QCPPainter(QPaintDevice *device) which directly starts painting also sets
 | |
|   the render hint as appropriate.
 | |
|   
 | |
|   \note this function hides the non-virtual base class implementation.
 | |
| */
 | |
| bool QCPPainter::begin(QPaintDevice *device)
 | |
| {
 | |
|   bool result = QPainter::begin(device);
 | |
| #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.
 | |
|   if (result)
 | |
|     setRenderHint(QPainter::NonCosmeticDefaultPen);
 | |
| #endif
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Sets the mode of the painter. This controls whether the painter shall adjust its
 | |
|   fixes/workarounds optimized for certain output devices.
 | |
| */
 | |
| void QCPPainter::setMode(QCPPainter::PainterMode mode, bool enabled)
 | |
| {
 | |
|   if (!enabled && mModes.testFlag(mode))
 | |
|     mModes &= ~mode;
 | |
|   else if (enabled && !mModes.testFlag(mode))
 | |
|     mModes |= mode;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Saves the painter (see QPainter::save). Since QCPPainter adds some new internal state to
 | |
|   QPainter, the save/restore functions are reimplemented to also save/restore those members.
 | |
|   
 | |
|   \note this function hides the non-virtual base class implementation.
 | |
|   
 | |
|   \see restore
 | |
| */
 | |
| void QCPPainter::save()
 | |
| {
 | |
|   mAntialiasingStack.push(mIsAntialiasing);
 | |
|   QPainter::save();
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Restores the painter (see QPainter::restore). Since QCPPainter adds some new internal state to
 | |
|   QPainter, the save/restore functions are reimplemented to also save/restore those members.
 | |
|   
 | |
|   \note this function hides the non-virtual base class implementation.
 | |
|   
 | |
|   \see save
 | |
| */
 | |
| void QCPPainter::restore()
 | |
| {
 | |
|   if (!mAntialiasingStack.isEmpty())
 | |
|     mIsAntialiasing = mAntialiasingStack.pop();
 | |
|   else
 | |
|     qDebug() << Q_FUNC_INFO << "Unbalanced save/restore";
 | |
|   QPainter::restore();
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Changes the pen width to 1 if it currently is 0. This function is called in the \ref setPen
 | |
|   overrides when the \ref pmNonCosmetic mode is set.
 | |
| */
 | |
| void QCPPainter::makeNonCosmetic()
 | |
| {
 | |
|   if (qFuzzyIsNull(pen().widthF()))
 | |
|   {
 | |
|     QPen p = pen();
 | |
|     p.setWidth(1);
 | |
|     QPainter::setPen(p);
 | |
|   }
 | |
| }
 | |
| /* end of 'src/painter.cpp' */
 | |
| 
 | |
| 
 | |
| /* including file 'src/paintbuffer.cpp', size 18502                          */
 | |
| /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPAbstractPaintBuffer
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPAbstractPaintBuffer
 | |
|   \brief The abstract base class for paint buffers, which define the rendering backend
 | |
| 
 | |
|   This abstract base class defines the basic interface that a paint buffer needs to provide in
 | |
|   order to be usable by QCustomPlot.
 | |
| 
 | |
|   A paint buffer manages both a surface to draw onto, and the matching paint device. The size of
 | |
|   the surface can be changed via \ref setSize. External classes (\ref QCustomPlot and \ref
 | |
|   QCPLayer) request a painter via \ref startPainting and then perform the draw calls. Once the
 | |
|   painting is complete, \ref donePainting is called, so the paint buffer implementation can do
 | |
|   clean up if necessary. Before rendering a frame, each paint buffer is usually filled with a color
 | |
|   using \ref clear (usually the color is \c Qt::transparent), to remove the contents of the
 | |
|   previous frame.
 | |
| 
 | |
|   The simplest paint buffer implementation is \ref QCPPaintBufferPixmap which allows regular
 | |
|   software rendering via the raster engine. Hardware accelerated rendering via pixel buffers and
 | |
|   frame buffer objects is provided by \ref QCPPaintBufferGlPbuffer and \ref QCPPaintBufferGlFbo.
 | |
|   They are used automatically if \ref QCustomPlot::setOpenGl is enabled.
 | |
| */
 | |
| 
 | |
| /* start documentation of pure virtual functions */
 | |
| 
 | |
| /*! \fn virtual QCPPainter *QCPAbstractPaintBuffer::startPainting() = 0
 | |
| 
 | |
|   Returns a \ref QCPPainter which is ready to draw to this buffer. The ownership and thus the
 | |
|   responsibility to delete the painter after the painting operations are complete is given to the
 | |
|   caller of this method.
 | |
| 
 | |
|   Once you are done using the painter, delete the painter and call \ref donePainting.
 | |
| 
 | |
|   While a painter generated with this method is active, you must not call \ref setSize, \ref
 | |
|   setDevicePixelRatio or \ref clear.
 | |
| 
 | |
|   This method may return 0, if a painter couldn't be activated on the buffer. This usually
 | |
|   indicates a problem with the respective painting backend.
 | |
| */
 | |
| 
 | |
| /*! \fn virtual void QCPAbstractPaintBuffer::draw(QCPPainter *painter) const = 0
 | |
| 
 | |
|   Draws the contents of this buffer with the provided \a painter. This is the method that is used
 | |
|   to finally join all paint buffers and draw them onto the screen.
 | |
| */
 | |
| 
 | |
| /*! \fn virtual void QCPAbstractPaintBuffer::clear(const QColor &color) = 0
 | |
| 
 | |
|   Fills the entire buffer with the provided \a color. To have an empty transparent buffer, use the
 | |
|   named color \c Qt::transparent.
 | |
| 
 | |
|   This method must not be called if there is currently a painter (acquired with \ref startPainting)
 | |
|   active.
 | |
| */
 | |
| 
 | |
| /*! \fn virtual void QCPAbstractPaintBuffer::reallocateBuffer() = 0
 | |
| 
 | |
|   Reallocates the internal buffer with the currently configured size (\ref setSize) and device
 | |
|   pixel ratio, if applicable (\ref setDevicePixelRatio). It is called as soon as any of those
 | |
|   properties are changed on this paint buffer.
 | |
| 
 | |
|   \note Subclasses of \ref QCPAbstractPaintBuffer must call their reimplementation of this method
 | |
|   in their constructor, to perform the first allocation (this can not be done by the base class
 | |
|   because calling pure virtual methods in base class constructors is not possible).
 | |
| */
 | |
| 
 | |
| /* end documentation of pure virtual functions */
 | |
| /* start documentation of inline functions */
 | |
| 
 | |
| /*! \fn virtual void QCPAbstractPaintBuffer::donePainting()
 | |
| 
 | |
|   If you have acquired a \ref QCPPainter to paint onto this paint buffer via \ref startPainting,
 | |
|   call this method as soon as you are done with the painting operations and have deleted the
 | |
|   painter.
 | |
| 
 | |
|   paint buffer subclasses may use this method to perform any type of cleanup that is necessary. The
 | |
|   default implementation does nothing.
 | |
| */
 | |
| 
 | |
| /* end documentation of inline functions */
 | |
| 
 | |
| /*!
 | |
|   Creates a paint buffer and initializes it with the provided \a size and \a devicePixelRatio.
 | |
| 
 | |
|   Subclasses must call their \ref reallocateBuffer implementation in their respective constructors.
 | |
| */
 | |
| QCPAbstractPaintBuffer::QCPAbstractPaintBuffer(const QSize &size, double devicePixelRatio) :
 | |
|   mSize(size),
 | |
|   mDevicePixelRatio(devicePixelRatio),
 | |
|   mInvalidated(true)
 | |
| {
 | |
| }
 | |
| 
 | |
| QCPAbstractPaintBuffer::~QCPAbstractPaintBuffer()
 | |
| {
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the paint buffer size.
 | |
| 
 | |
|   The buffer is reallocated (by calling \ref reallocateBuffer), so any painters that were obtained
 | |
|   by \ref startPainting are invalidated and must not be used after calling this method.
 | |
| 
 | |
|   If \a size is already the current buffer size, this method does nothing.
 | |
| */
 | |
| void QCPAbstractPaintBuffer::setSize(const QSize &size)
 | |
| {
 | |
|   if (mSize != size)
 | |
|   {
 | |
|     mSize = size;
 | |
|     reallocateBuffer();
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the invalidated flag to \a invalidated.
 | |
| 
 | |
|   This mechanism is used internally in conjunction with isolated replotting of \ref QCPLayer
 | |
|   instances (in \ref QCPLayer::lmBuffered mode). If \ref QCPLayer::replot is called on a buffered
 | |
|   layer, i.e. an isolated repaint of only that layer (and its dedicated paint buffer) is requested,
 | |
|   QCustomPlot will decide depending on the invalidated flags of other paint buffers whether it also
 | |
|   replots them, instead of only the layer on which the replot was called.
 | |
| 
 | |
|   The invalidated flag is set to true when \ref QCPLayer association has changed, i.e. if layers
 | |
|   were added or removed from this buffer, or if they were reordered. It is set to false as soon as
 | |
|   all associated \ref QCPLayer instances are drawn onto the buffer.
 | |
| 
 | |
|   Under normal circumstances, it is not necessary to manually call this method.
 | |
| */
 | |
| void QCPAbstractPaintBuffer::setInvalidated(bool invalidated)
 | |
| {
 | |
|   mInvalidated = invalidated;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the the device pixel ratio to \a ratio. This is useful to render on high-DPI output devices.
 | |
|   The ratio is automatically set to the device pixel ratio used by the parent QCustomPlot instance.
 | |
| 
 | |
|   The buffer is reallocated (by calling \ref reallocateBuffer), so any painters that were obtained
 | |
|   by \ref startPainting are invalidated and must not be used after calling this method.
 | |
| 
 | |
|   \note This method is only available for Qt versions 5.4 and higher.
 | |
| */
 | |
| void QCPAbstractPaintBuffer::setDevicePixelRatio(double ratio)
 | |
| {
 | |
|   if (!qFuzzyCompare(ratio, mDevicePixelRatio))
 | |
|   {
 | |
| #ifdef QCP_DEVICEPIXELRATIO_SUPPORTED
 | |
|     mDevicePixelRatio = ratio;
 | |
|     reallocateBuffer();
 | |
| #else
 | |
|     qDebug() << Q_FUNC_INFO << "Device pixel ratios not supported for Qt versions before 5.4";
 | |
|     mDevicePixelRatio = 1.0;
 | |
| #endif
 | |
|   }
 | |
| }
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPPaintBufferPixmap
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPPaintBufferPixmap
 | |
|   \brief A paint buffer based on QPixmap, using software raster rendering
 | |
| 
 | |
|   This paint buffer is the default and fall-back paint buffer which uses software rendering and
 | |
|   QPixmap as internal buffer. It is used if \ref QCustomPlot::setOpenGl is false.
 | |
| */
 | |
| 
 | |
| /*!
 | |
|   Creates a pixmap paint buffer instancen with the specified \a size and \a devicePixelRatio, if
 | |
|   applicable.
 | |
| */
 | |
| QCPPaintBufferPixmap::QCPPaintBufferPixmap(const QSize &size, double devicePixelRatio) :
 | |
|   QCPAbstractPaintBuffer(size, devicePixelRatio)
 | |
| {
 | |
|   QCPPaintBufferPixmap::reallocateBuffer();
 | |
| }
 | |
| 
 | |
| QCPPaintBufferPixmap::~QCPPaintBufferPixmap()
 | |
| {
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| QCPPainter *QCPPaintBufferPixmap::startPainting()
 | |
| {
 | |
|   QCPPainter *result = new QCPPainter(&mBuffer);
 | |
|   result->setRenderHint(QPainter::HighQualityAntialiasing);
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPPaintBufferPixmap::draw(QCPPainter *painter) const
 | |
| {
 | |
|   if (painter && painter->isActive())
 | |
|     painter->drawPixmap(0, 0, mBuffer);
 | |
|   else
 | |
|     qDebug() << Q_FUNC_INFO << "invalid or inactive painter passed";
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPPaintBufferPixmap::clear(const QColor &color)
 | |
| {
 | |
|   mBuffer.fill(color);
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPPaintBufferPixmap::reallocateBuffer()
 | |
| {
 | |
|   setInvalidated();
 | |
|   if (!qFuzzyCompare(1.0, mDevicePixelRatio))
 | |
|   {
 | |
| #ifdef QCP_DEVICEPIXELRATIO_SUPPORTED
 | |
|     mBuffer = QPixmap(mSize*mDevicePixelRatio);
 | |
|     mBuffer.setDevicePixelRatio(mDevicePixelRatio);
 | |
| #else
 | |
|     qDebug() << Q_FUNC_INFO << "Device pixel ratios not supported for Qt versions before 5.4";
 | |
|     mDevicePixelRatio = 1.0;
 | |
|     mBuffer = QPixmap(mSize);
 | |
| #endif
 | |
|   } else
 | |
|   {
 | |
|     mBuffer = QPixmap(mSize);
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| #ifdef QCP_OPENGL_PBUFFER
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPPaintBufferGlPbuffer
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPPaintBufferGlPbuffer
 | |
|   \brief A paint buffer based on OpenGL pixel buffers, using hardware accelerated rendering
 | |
| 
 | |
|   This paint buffer is one of the OpenGL paint buffers which facilitate hardware accelerated plot
 | |
|   rendering. It is based on OpenGL pixel buffers (pbuffer) and is used in Qt versions before 5.0.
 | |
|   (See \ref QCPPaintBufferGlFbo used in newer Qt versions.)
 | |
| 
 | |
|   The OpenGL paint buffers are used if \ref QCustomPlot::setOpenGl is set to true, and if they are
 | |
|   supported by the system.
 | |
| */
 | |
| 
 | |
| /*!
 | |
|   Creates a \ref QCPPaintBufferGlPbuffer instance with the specified \a size and \a
 | |
|   devicePixelRatio, if applicable.
 | |
| 
 | |
|   The parameter \a multisamples defines how many samples are used per pixel. Higher values thus
 | |
|   result in higher quality antialiasing. If the specified \a multisamples value exceeds the
 | |
|   capability of the graphics hardware, the highest supported multisampling is used.
 | |
| */
 | |
| QCPPaintBufferGlPbuffer::QCPPaintBufferGlPbuffer(const QSize &size, double devicePixelRatio, int multisamples) :
 | |
|   QCPAbstractPaintBuffer(size, devicePixelRatio),
 | |
|   mGlPBuffer(0),
 | |
|   mMultisamples(qMax(0, multisamples))
 | |
| {
 | |
|   QCPPaintBufferGlPbuffer::reallocateBuffer();
 | |
| }
 | |
| 
 | |
| QCPPaintBufferGlPbuffer::~QCPPaintBufferGlPbuffer()
 | |
| {
 | |
|   if (mGlPBuffer)
 | |
|     delete mGlPBuffer;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| QCPPainter *QCPPaintBufferGlPbuffer::startPainting()
 | |
| {
 | |
|   if (!mGlPBuffer->isValid())
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "OpenGL frame buffer object doesn't exist, reallocateBuffer was not called?";
 | |
|     return 0;
 | |
|   }
 | |
|   
 | |
|   QCPPainter *result = new QCPPainter(mGlPBuffer);
 | |
|   result->setRenderHint(QPainter::HighQualityAntialiasing);
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPPaintBufferGlPbuffer::draw(QCPPainter *painter) const
 | |
| {
 | |
|   if (!painter || !painter->isActive())
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "invalid or inactive painter passed";
 | |
|     return;
 | |
|   }
 | |
|   if (!mGlPBuffer->isValid())
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "OpenGL pbuffer isn't valid, reallocateBuffer was not called?";
 | |
|     return;
 | |
|   }
 | |
|   painter->drawImage(0, 0, mGlPBuffer->toImage());
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPPaintBufferGlPbuffer::clear(const QColor &color)
 | |
| {
 | |
|   if (mGlPBuffer->isValid())
 | |
|   {
 | |
|     mGlPBuffer->makeCurrent();
 | |
|     glClearColor(color.redF(), color.greenF(), color.blueF(), color.alphaF());
 | |
|     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 | |
|     mGlPBuffer->doneCurrent();
 | |
|   } else
 | |
|     qDebug() << Q_FUNC_INFO << "OpenGL pbuffer invalid or context not current";
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPPaintBufferGlPbuffer::reallocateBuffer()
 | |
| {
 | |
|   if (mGlPBuffer)
 | |
|     delete mGlPBuffer;
 | |
|   
 | |
|   QGLFormat format;
 | |
|   format.setAlpha(true);
 | |
|   format.setSamples(mMultisamples);
 | |
|   mGlPBuffer = new QGLPixelBuffer(mSize, format);
 | |
| }
 | |
| #endif // QCP_OPENGL_PBUFFER
 | |
| 
 | |
| 
 | |
| #ifdef QCP_OPENGL_FBO
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPPaintBufferGlFbo
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPPaintBufferGlFbo
 | |
|   \brief A paint buffer based on OpenGL frame buffers objects, using hardware accelerated rendering
 | |
| 
 | |
|   This paint buffer is one of the OpenGL paint buffers which facilitate hardware accelerated plot
 | |
|   rendering. It is based on OpenGL frame buffer objects (fbo) and is used in Qt versions 5.0 and
 | |
|   higher. (See \ref QCPPaintBufferGlPbuffer used in older Qt versions.)
 | |
| 
 | |
|   The OpenGL paint buffers are used if \ref QCustomPlot::setOpenGl is set to true, and if they are
 | |
|   supported by the system.
 | |
| */
 | |
| 
 | |
| /*!
 | |
|   Creates a \ref QCPPaintBufferGlFbo instance with the specified \a size and \a devicePixelRatio,
 | |
|   if applicable.
 | |
| 
 | |
|   All frame buffer objects shall share one OpenGL context and paint device, which need to be set up
 | |
|   externally and passed via \a glContext and \a glPaintDevice. The set-up is done in \ref
 | |
|   QCustomPlot::setupOpenGl and the context and paint device are managed by the parent QCustomPlot
 | |
|   instance.
 | |
| */
 | |
| QCPPaintBufferGlFbo::QCPPaintBufferGlFbo(const QSize &size, double devicePixelRatio, QWeakPointer<QOpenGLContext> glContext, QWeakPointer<QOpenGLPaintDevice> glPaintDevice) :
 | |
|   QCPAbstractPaintBuffer(size, devicePixelRatio),
 | |
|   mGlContext(glContext),
 | |
|   mGlPaintDevice(glPaintDevice),
 | |
|   mGlFrameBuffer(0)
 | |
| {
 | |
|   QCPPaintBufferGlFbo::reallocateBuffer();
 | |
| }
 | |
| 
 | |
| QCPPaintBufferGlFbo::~QCPPaintBufferGlFbo()
 | |
| {
 | |
|   if (mGlFrameBuffer)
 | |
|     delete mGlFrameBuffer;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| QCPPainter *QCPPaintBufferGlFbo::startPainting()
 | |
| {
 | |
|   if (mGlPaintDevice.isNull())
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "OpenGL paint device doesn't exist";
 | |
|     return 0;
 | |
|   }
 | |
|   if (!mGlFrameBuffer)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "OpenGL frame buffer object doesn't exist, reallocateBuffer was not called?";
 | |
|     return 0;
 | |
|   }
 | |
|   
 | |
|   if (QOpenGLContext::currentContext() != mGlContext.data())
 | |
|     mGlContext.data()->makeCurrent(mGlContext.data()->surface());
 | |
|   mGlFrameBuffer->bind();
 | |
|   QCPPainter *result = new QCPPainter(mGlPaintDevice.data());
 | |
|   result->setRenderHint(QPainter::HighQualityAntialiasing);
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPPaintBufferGlFbo::donePainting()
 | |
| {
 | |
|   if (mGlFrameBuffer && mGlFrameBuffer->isBound())
 | |
|     mGlFrameBuffer->release();
 | |
|   else
 | |
|     qDebug() << Q_FUNC_INFO << "Either OpenGL frame buffer not valid or was not bound";
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPPaintBufferGlFbo::draw(QCPPainter *painter) const
 | |
| {
 | |
|   if (!painter || !painter->isActive())
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "invalid or inactive painter passed";
 | |
|     return;
 | |
|   }
 | |
|   if (!mGlFrameBuffer)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "OpenGL frame buffer object doesn't exist, reallocateBuffer was not called?";
 | |
|     return;
 | |
|   }
 | |
|   painter->drawImage(0, 0, mGlFrameBuffer->toImage());
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPPaintBufferGlFbo::clear(const QColor &color)
 | |
| {
 | |
|   if (mGlContext.isNull())
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "OpenGL context doesn't exist";
 | |
|     return;
 | |
|   }
 | |
|   if (!mGlFrameBuffer)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "OpenGL frame buffer object doesn't exist, reallocateBuffer was not called?";
 | |
|     return;
 | |
|   }
 | |
|   
 | |
|   if (QOpenGLContext::currentContext() != mGlContext.data())
 | |
|     mGlContext.data()->makeCurrent(mGlContext.data()->surface());
 | |
|   mGlFrameBuffer->bind();
 | |
|   glClearColor(color.redF(), color.greenF(), color.blueF(), color.alphaF());
 | |
|   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 | |
|   mGlFrameBuffer->release();
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPPaintBufferGlFbo::reallocateBuffer()
 | |
| {
 | |
|   // release and delete possibly existing framebuffer:
 | |
|   if (mGlFrameBuffer)
 | |
|   {
 | |
|     if (mGlFrameBuffer->isBound())
 | |
|       mGlFrameBuffer->release();
 | |
|     delete mGlFrameBuffer;
 | |
|     mGlFrameBuffer = 0;
 | |
|   }
 | |
|   
 | |
|   if (mGlContext.isNull())
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "OpenGL context doesn't exist";
 | |
|     return;
 | |
|   }
 | |
|   if (mGlPaintDevice.isNull())
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "OpenGL paint device doesn't exist";
 | |
|     return;
 | |
|   }
 | |
|   
 | |
|   // create new fbo with appropriate size:
 | |
|   mGlContext.data()->makeCurrent(mGlContext.data()->surface());
 | |
|   QOpenGLFramebufferObjectFormat frameBufferFormat;
 | |
|   frameBufferFormat.setSamples(mGlContext.data()->format().samples());
 | |
|   frameBufferFormat.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
 | |
|   mGlFrameBuffer = new QOpenGLFramebufferObject(mSize*mDevicePixelRatio, frameBufferFormat);
 | |
|   if (mGlPaintDevice.data()->size() != mSize*mDevicePixelRatio)
 | |
|     mGlPaintDevice.data()->setSize(mSize*mDevicePixelRatio);
 | |
| #ifdef QCP_DEVICEPIXELRATIO_SUPPORTED
 | |
|   mGlPaintDevice.data()->setDevicePixelRatio(mDevicePixelRatio);
 | |
| #endif
 | |
| }
 | |
| #endif // QCP_OPENGL_FBO
 | |
| /* end of 'src/paintbuffer.cpp' */
 | |
| 
 | |
| 
 | |
| /* including file 'src/layer.cpp', size 37064                                */
 | |
| /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPLayer
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPLayer
 | |
|   \brief A layer that may contain objects, to control the rendering order
 | |
| 
 | |
|   The Layering system of QCustomPlot is the mechanism to control the rendering order of the
 | |
|   elements inside the plot.
 | |
| 
 | |
|   It is based on the two classes QCPLayer and QCPLayerable. QCustomPlot holds an ordered list of
 | |
|   one or more instances of QCPLayer (see QCustomPlot::addLayer, QCustomPlot::layer,
 | |
|   QCustomPlot::moveLayer, etc.). When replotting, QCustomPlot goes through the list of layers
 | |
|   bottom to top and successively draws the layerables of the layers into the paint buffer(s).
 | |
| 
 | |
|   A QCPLayer contains an ordered list of QCPLayerable instances. QCPLayerable is an abstract base
 | |
|   class from which almost all visible objects derive, like axes, grids, graphs, items, etc.
 | |
| 
 | |
|   \section qcplayer-defaultlayers Default layers
 | |
| 
 | |
|   Initially, QCustomPlot has six layers: "background", "grid", "main", "axes", "legend" and
 | |
|   "overlay" (in that order). On top is the "overlay" layer, which only contains the QCustomPlot's
 | |
|   selection rect (\ref QCustomPlot::selectionRect). The next two layers "axes" and "legend" contain
 | |
|   the default axes and legend, so they will be drawn above plottables. In the middle, there is the
 | |
|   "main" layer. It is initially empty and set as the current layer (see
 | |
|   QCustomPlot::setCurrentLayer). This means, all new plottables, items etc. are created on this
 | |
|   layer by default. Then comes the "grid" layer which contains the QCPGrid instances (which belong
 | |
|   tightly to QCPAxis, see \ref QCPAxis::grid). The Axis rect background shall be drawn behind
 | |
|   everything else, thus the default QCPAxisRect instance is placed on the "background" layer. Of
 | |
|   course, the layer affiliation of the individual objects can be changed as required (\ref
 | |
|   QCPLayerable::setLayer).
 | |
| 
 | |
|   \section qcplayer-ordering Controlling the rendering order via layers
 | |
| 
 | |
|   Controlling the ordering of layerables in the plot is easy: Create a new layer in the position
 | |
|   you want the layerable to be in, e.g. above "main", with \ref QCustomPlot::addLayer. Then set the
 | |
|   current layer with \ref QCustomPlot::setCurrentLayer to that new layer and finally create the
 | |
|   objects normally. They will be placed on the new layer automatically, due to the current layer
 | |
|   setting. Alternatively you could have also ignored the current layer setting and just moved the
 | |
|   objects with \ref QCPLayerable::setLayer to the desired layer after creating them.
 | |
| 
 | |
|   It is also possible to move whole layers. For example, If you want the grid to be shown in front
 | |
|   of all plottables/items on the "main" layer, just move it above "main" with
 | |
|   QCustomPlot::moveLayer.
 | |
| 
 | |
|   The rendering order within one layer is simply by order of creation or insertion. The item
 | |
|   created last (or added last to the layer), is drawn on top of all other objects on that layer.
 | |
| 
 | |
|   When a layer is deleted, the objects on it are not deleted with it, but fall on the layer below
 | |
|   the deleted layer, see QCustomPlot::removeLayer.
 | |
| 
 | |
|   \section qcplayer-buffering Replotting only a specific layer
 | |
| 
 | |
|   If the layer mode (\ref setMode) is set to \ref lmBuffered, you can replot only this specific
 | |
|   layer by calling \ref replot. In certain situations this can provide better replot performance,
 | |
|   compared with a full replot of all layers. Upon creation of a new layer, the layer mode is
 | |
|   initialized to \ref lmLogical. The only layer that is set to \ref lmBuffered in a new \ref
 | |
|   QCustomPlot instance is the "overlay" layer, containing the selection rect.
 | |
| */
 | |
| 
 | |
| /* start documentation of inline functions */
 | |
| 
 | |
| /*! \fn QList<QCPLayerable*> QCPLayer::children() const
 | |
|   
 | |
|   Returns a list of all layerables on this layer. The order corresponds to the rendering order:
 | |
|   layerables with higher indices are drawn above layerables with lower indices.
 | |
| */
 | |
| 
 | |
| /*! \fn int QCPLayer::index() const
 | |
|   
 | |
|   Returns the index this layer has in the QCustomPlot. The index is the integer number by which this layer can be
 | |
|   accessed via \ref QCustomPlot::layer.
 | |
|   
 | |
|   Layers with higher indices will be drawn above layers with lower indices.
 | |
| */
 | |
| 
 | |
| /* end documentation of inline functions */
 | |
| 
 | |
| /*!
 | |
|   Creates a new QCPLayer instance.
 | |
|   
 | |
|   Normally you shouldn't directly instantiate layers, use \ref QCustomPlot::addLayer instead.
 | |
|   
 | |
|   \warning It is not checked that \a layerName is actually a unique layer name in \a parentPlot.
 | |
|   This check is only performed by \ref QCustomPlot::addLayer.
 | |
| */
 | |
| QCPLayer::QCPLayer(QCustomPlot *parentPlot, const QString &layerName) :
 | |
|   QObject(parentPlot),
 | |
|   mParentPlot(parentPlot),
 | |
|   mName(layerName),
 | |
|   mIndex(-1), // will be set to a proper value by the QCustomPlot layer creation function
 | |
|   mVisible(true),
 | |
|   mMode(lmLogical)
 | |
| {
 | |
|   // Note: no need to make sure layerName is unique, because layer
 | |
|   // management is done with QCustomPlot functions.
 | |
| }
 | |
| 
 | |
| QCPLayer::~QCPLayer()
 | |
| {
 | |
|   // If child layerables are still on this layer, detach them, so they don't try to reach back to this
 | |
|   // then invalid layer once they get deleted/moved themselves. This only happens when layers are deleted
 | |
|   // directly, like in the QCustomPlot destructor. (The regular layer removal procedure for the user is to
 | |
|   // call QCustomPlot::removeLayer, which moves all layerables off this layer before deleting it.)
 | |
|   
 | |
|   while (!mChildren.isEmpty())
 | |
|     mChildren.last()->setLayer(0); // removes itself from mChildren via removeChild()
 | |
|   
 | |
|   if (mParentPlot->currentLayer() == this)
 | |
|     qDebug() << Q_FUNC_INFO << "The parent plot's mCurrentLayer will be a dangling pointer. Should have been set to a valid layer or 0 beforehand.";
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets whether this layer is visible or not. If \a visible is set to false, all layerables on this
 | |
|   layer will be invisible.
 | |
| 
 | |
|   This function doesn't change the visibility property of the layerables (\ref
 | |
|   QCPLayerable::setVisible), but the \ref QCPLayerable::realVisibility of each layerable takes the
 | |
|   visibility of the parent layer into account.
 | |
| */
 | |
| void QCPLayer::setVisible(bool visible)
 | |
| {
 | |
|   mVisible = visible;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the rendering mode of this layer.
 | |
| 
 | |
|   If \a mode is set to \ref lmBuffered for a layer, it will be given a dedicated paint buffer by
 | |
|   the parent QCustomPlot instance. This means it may be replotted individually by calling \ref
 | |
|   QCPLayer::replot, without needing to replot all other layers.
 | |
| 
 | |
|   Layers which are set to \ref lmLogical (the default) are used only to define the rendering order
 | |
|   and can't be replotted individually.
 | |
| 
 | |
|   Note that each layer which is set to \ref lmBuffered requires additional paint buffers for the
 | |
|   layers below, above and for the layer itself. This increases the memory consumption and
 | |
|   (slightly) decreases the repainting speed because multiple paint buffers need to be joined. So
 | |
|   you should carefully choose which layers benefit from having their own paint buffer. A typical
 | |
|   example would be a layer which contains certain layerables (e.g. items) that need to be changed
 | |
|   and thus replotted regularly, while all other layerables on other layers stay static. By default,
 | |
|   only the topmost layer called "overlay" is in mode \ref lmBuffered, and contains the selection
 | |
|   rect.
 | |
| 
 | |
|   \see replot
 | |
| */
 | |
| void QCPLayer::setMode(QCPLayer::LayerMode mode)
 | |
| {
 | |
|   if (mMode != mode)
 | |
|   {
 | |
|     mMode = mode;
 | |
|     if (!mPaintBuffer.isNull())
 | |
|       mPaintBuffer.data()->setInvalidated();
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Draws the contents of this layer with the provided \a painter.
 | |
| 
 | |
|   \see replot, drawToPaintBuffer
 | |
| */
 | |
| void QCPLayer::draw(QCPPainter *painter)
 | |
| {
 | |
|   foreach (QCPLayerable *child, mChildren)
 | |
|   {
 | |
|     if (child->realVisibility())
 | |
|     {
 | |
|       painter->save();
 | |
|       painter->setClipRect(child->clipRect().translated(0, -1));
 | |
|       child->applyDefaultAntialiasingHint(painter);
 | |
|       child->draw(painter);
 | |
|       painter->restore();
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Draws the contents of this layer into the paint buffer which is associated with this layer. The
 | |
|   association is established by the parent QCustomPlot, which manages all paint buffers (see \ref
 | |
|   QCustomPlot::setupPaintBuffers).
 | |
| 
 | |
|   \see draw
 | |
| */
 | |
| void QCPLayer::drawToPaintBuffer()
 | |
| {
 | |
|   if (!mPaintBuffer.isNull())
 | |
|   {
 | |
|     if (QCPPainter *painter = mPaintBuffer.data()->startPainting())
 | |
|     {
 | |
|       if (painter->isActive())
 | |
|         draw(painter);
 | |
|       else
 | |
|         qDebug() << Q_FUNC_INFO << "paint buffer returned inactive painter";
 | |
|       delete painter;
 | |
|       mPaintBuffer.data()->donePainting();
 | |
|     } else
 | |
|       qDebug() << Q_FUNC_INFO << "paint buffer returned zero painter";
 | |
|   } else
 | |
|     qDebug() << Q_FUNC_INFO << "no valid paint buffer associated with this layer";
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   If the layer mode (\ref setMode) is set to \ref lmBuffered, this method allows replotting only
 | |
|   the layerables on this specific layer, without the need to replot all other layers (as a call to
 | |
|   \ref QCustomPlot::replot would do).
 | |
| 
 | |
|   If the layer mode is \ref lmLogical however, this method simply calls \ref QCustomPlot::replot on
 | |
|   the parent QCustomPlot instance.
 | |
| 
 | |
|   QCustomPlot also makes sure to replot all layers instead of only this one, if the layer ordering
 | |
|   has changed since the last full replot and the other paint buffers were thus invalidated.
 | |
| 
 | |
|   \see draw
 | |
| */
 | |
| void QCPLayer::replot()
 | |
| {
 | |
|   if (mMode == lmBuffered && !mParentPlot->hasInvalidatedPaintBuffers())
 | |
|   {
 | |
|     if (!mPaintBuffer.isNull())
 | |
|     {
 | |
|       mPaintBuffer.data()->clear(Qt::transparent);
 | |
|       drawToPaintBuffer();
 | |
|       mPaintBuffer.data()->setInvalidated(false);
 | |
|       mParentPlot->update();
 | |
|     } else
 | |
|       qDebug() << Q_FUNC_INFO << "no valid paint buffer associated with this layer";
 | |
|   } else if (mMode == lmLogical)
 | |
|     mParentPlot->replot();
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Adds the \a layerable to the list of this layer. If \a prepend is set to true, the layerable will
 | |
|   be prepended to the list, i.e. be drawn beneath the other layerables already in the list.
 | |
|   
 | |
|   This function does not change the \a mLayer member of \a layerable to this layer. (Use
 | |
|   QCPLayerable::setLayer to change the layer of an object, not this function.)
 | |
|   
 | |
|   \see removeChild
 | |
| */
 | |
| void QCPLayer::addChild(QCPLayerable *layerable, bool prepend)
 | |
| {
 | |
|   if (!mChildren.contains(layerable))
 | |
|   {
 | |
|     if (prepend)
 | |
|       mChildren.prepend(layerable);
 | |
|     else
 | |
|       mChildren.append(layerable);
 | |
|     if (!mPaintBuffer.isNull())
 | |
|       mPaintBuffer.data()->setInvalidated();
 | |
|   } else
 | |
|     qDebug() << Q_FUNC_INFO << "layerable is already child of this layer" << reinterpret_cast<quintptr>(layerable);
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Removes the \a layerable from the list of this layer.
 | |
|   
 | |
|   This function does not change the \a mLayer member of \a layerable. (Use QCPLayerable::setLayer
 | |
|   to change the layer of an object, not this function.)
 | |
|   
 | |
|   \see addChild
 | |
| */
 | |
| void QCPLayer::removeChild(QCPLayerable *layerable)
 | |
| {
 | |
|   if (mChildren.removeOne(layerable))
 | |
|   {
 | |
|     if (!mPaintBuffer.isNull())
 | |
|       mPaintBuffer.data()->setInvalidated();
 | |
|   } else
 | |
|     qDebug() << Q_FUNC_INFO << "layerable is not child of this layer" << reinterpret_cast<quintptr>(layerable);
 | |
| }
 | |
| 
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPLayerable
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPLayerable
 | |
|   \brief Base class for all drawable objects
 | |
|   
 | |
|   This is the abstract base class most visible objects derive from, e.g. plottables, axes, grid
 | |
|   etc.
 | |
| 
 | |
|   Every layerable is on a layer (QCPLayer) which allows controlling the rendering order by stacking
 | |
|   the layers accordingly.
 | |
|   
 | |
|   For details about the layering mechanism, see the QCPLayer documentation.
 | |
| */
 | |
| 
 | |
| /* start documentation of inline functions */
 | |
| 
 | |
| /*! \fn QCPLayerable *QCPLayerable::parentLayerable() const
 | |
|  
 | |
|   Returns the parent layerable of this layerable. The parent layerable is used to provide
 | |
|   visibility hierarchies in conjunction with the method \ref realVisibility. This way, layerables
 | |
|   only get drawn if their parent layerables are visible, too.
 | |
|   
 | |
|   Note that a parent layerable is not necessarily also the QObject parent for memory management.
 | |
|   Further, a layerable doesn't always have a parent layerable, so this function may return 0.
 | |
|   
 | |
|   A parent layerable is set implicitly when placed inside layout elements and doesn't need to be
 | |
|   set manually by the user.
 | |
| */
 | |
| 
 | |
| /* end documentation of inline functions */
 | |
| /* start documentation of pure virtual functions */
 | |
| 
 | |
| /*! \fn virtual void QCPLayerable::applyDefaultAntialiasingHint(QCPPainter *painter) const = 0
 | |
|   \internal
 | |
|   
 | |
|   This function applies the default antialiasing setting to the specified \a painter, using the
 | |
|   function \ref applyAntialiasingHint. It is the antialiasing state the painter is put in, when
 | |
|   \ref draw is called on the layerable. If the layerable has multiple entities whose antialiasing
 | |
|   setting may be specified individually, this function should set the antialiasing state of the
 | |
|   most prominent entity. In this case however, the \ref draw function usually calls the specialized
 | |
|   versions of this function before drawing each entity, effectively overriding the setting of the
 | |
|   default antialiasing hint.
 | |
|   
 | |
|   <b>First example:</b> QCPGraph has multiple entities that have an antialiasing setting: The graph
 | |
|   line, fills and scatters. Those can be configured via QCPGraph::setAntialiased,
 | |
|   QCPGraph::setAntialiasedFill and QCPGraph::setAntialiasedScatters. Consequently, there isn't only
 | |
|   the QCPGraph::applyDefaultAntialiasingHint function (which corresponds to the graph line's
 | |
|   antialiasing), but specialized ones like QCPGraph::applyFillAntialiasingHint and
 | |
|   QCPGraph::applyScattersAntialiasingHint. So before drawing one of those entities, QCPGraph::draw
 | |
|   calls the respective specialized applyAntialiasingHint function.
 | |
|   
 | |
|   <b>Second example:</b> QCPItemLine consists only of a line so there is only one antialiasing
 | |
|   setting which can be controlled with QCPItemLine::setAntialiased. (This function is inherited by
 | |
|   all layerables. The specialized functions, as seen on QCPGraph, must be added explicitly to the
 | |
|   respective layerable subclass.) Consequently it only has the normal
 | |
|   QCPItemLine::applyDefaultAntialiasingHint. The \ref QCPItemLine::draw function doesn't need to
 | |
|   care about setting any antialiasing states, because the default antialiasing hint is already set
 | |
|   on the painter when the \ref draw function is called, and that's the state it wants to draw the
 | |
|   line with.
 | |
| */
 | |
| 
 | |
| /*! \fn virtual void QCPLayerable::draw(QCPPainter *painter) const = 0
 | |
|   \internal
 | |
|   
 | |
|   This function draws the layerable with the specified \a painter. It is only called by
 | |
|   QCustomPlot, if the layerable is visible (\ref setVisible).
 | |
|   
 | |
|   Before this function is called, the painter's antialiasing state is set via \ref
 | |
|   applyDefaultAntialiasingHint, see the documentation there. Further, the clipping rectangle was
 | |
|   set to \ref clipRect.
 | |
| */
 | |
| 
 | |
| /* end documentation of pure virtual functions */
 | |
| /* start documentation of signals */
 | |
| 
 | |
| /*! \fn void QCPLayerable::layerChanged(QCPLayer *newLayer);
 | |
|   
 | |
|   This signal is emitted when the layer of this layerable changes, i.e. this layerable is moved to
 | |
|   a different layer.
 | |
|   
 | |
|   \see setLayer
 | |
| */
 | |
| 
 | |
| /* end documentation of signals */
 | |
| 
 | |
| /*!
 | |
|   Creates a new QCPLayerable instance.
 | |
|   
 | |
|   Since QCPLayerable is an abstract base class, it can't be instantiated directly. Use one of the
 | |
|   derived classes.
 | |
|   
 | |
|   If \a plot is provided, it automatically places itself on the layer named \a targetLayer. If \a
 | |
|   targetLayer is an empty string, it places itself on the current layer of the plot (see \ref
 | |
|   QCustomPlot::setCurrentLayer).
 | |
|   
 | |
|   It is possible to provide 0 as \a plot. In that case, you should assign a parent plot at a later
 | |
|   time with \ref initializeParentPlot.
 | |
|   
 | |
|   The layerable's parent layerable is set to \a parentLayerable, if provided. Direct layerable
 | |
|   parents are mainly used to control visibility in a hierarchy of layerables. This means a
 | |
|   layerable is only drawn, if all its ancestor layerables are also visible. Note that \a
 | |
|   parentLayerable does not become the QObject-parent (for memory management) of this layerable, \a
 | |
|   plot does. It is not uncommon to set the QObject-parent to something else in the constructors of
 | |
|   QCPLayerable subclasses, to guarantee a working destruction hierarchy.
 | |
| */
 | |
| QCPLayerable::QCPLayerable(QCustomPlot *plot, QString targetLayer, QCPLayerable *parentLayerable) :
 | |
|   QObject(plot),
 | |
|   mVisible(true),
 | |
|   mParentPlot(plot),
 | |
|   mParentLayerable(parentLayerable),
 | |
|   mLayer(0),
 | |
|   mAntialiased(true)
 | |
| {
 | |
|   if (mParentPlot)
 | |
|   {
 | |
|     if (targetLayer.isEmpty())
 | |
|       setLayer(mParentPlot->currentLayer());
 | |
|     else if (!setLayer(targetLayer))
 | |
|       qDebug() << Q_FUNC_INFO << "setting QCPlayerable initial layer to" << targetLayer << "failed.";
 | |
|   }
 | |
| }
 | |
| 
 | |
| QCPLayerable::~QCPLayerable()
 | |
| {
 | |
|   if (mLayer)
 | |
|   {
 | |
|     mLayer->removeChild(this);
 | |
|     mLayer = 0;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the visibility of this layerable object. If an object is not visible, it will not be drawn
 | |
|   on the QCustomPlot surface, and user interaction with it (e.g. click and selection) is not
 | |
|   possible.
 | |
| */
 | |
| void QCPLayerable::setVisible(bool on)
 | |
| {
 | |
|   mVisible = on;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the \a layer of this layerable object. The object will be placed on top of the other objects
 | |
|   already on \a layer.
 | |
|   
 | |
|   If \a layer is 0, this layerable will not be on any layer and thus not appear in the plot (or
 | |
|   interact/receive events).
 | |
|   
 | |
|   Returns true if the layer of this layerable was successfully changed to \a layer.
 | |
| */
 | |
| bool QCPLayerable::setLayer(QCPLayer *layer)
 | |
| {
 | |
|   return moveToLayer(layer, false);
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   Sets the layer of this layerable object by name
 | |
|   
 | |
|   Returns true on success, i.e. if \a layerName is a valid layer name.
 | |
| */
 | |
| bool QCPLayerable::setLayer(const QString &layerName)
 | |
| {
 | |
|   if (!mParentPlot)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "no parent QCustomPlot set";
 | |
|     return false;
 | |
|   }
 | |
|   if (QCPLayer *layer = mParentPlot->layer(layerName))
 | |
|   {
 | |
|     return setLayer(layer);
 | |
|   } else
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "there is no layer with name" << layerName;
 | |
|     return false;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets whether this object will be drawn antialiased or not.
 | |
|   
 | |
|   Note that antialiasing settings may be overridden by QCustomPlot::setAntialiasedElements and
 | |
|   QCustomPlot::setNotAntialiasedElements.
 | |
| */
 | |
| void QCPLayerable::setAntialiased(bool enabled)
 | |
| {
 | |
|   mAntialiased = enabled;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns whether this layerable is visible, taking the visibility of the layerable parent and the
 | |
|   visibility of this layerable's layer into account. This is the method that is consulted to decide
 | |
|   whether a layerable shall be drawn or not.
 | |
|   
 | |
|   If this layerable has a direct layerable parent (usually set via hierarchies implemented in
 | |
|   subclasses, like in the case of \ref QCPLayoutElement), this function returns true only if this
 | |
|   layerable has its visibility set to true and the parent layerable's \ref realVisibility returns
 | |
|   true.
 | |
| */
 | |
| bool QCPLayerable::realVisibility() const
 | |
| {
 | |
|   return mVisible && (!mLayer || mLayer->visible()) && (!mParentLayerable || mParentLayerable.data()->realVisibility());
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   This function is used to decide whether a click hits a layerable object or not.
 | |
| 
 | |
|   \a pos is a point in pixel coordinates on the QCustomPlot surface. This function returns the
 | |
|   shortest pixel distance of this point to the object. If the object is either invisible or the
 | |
|   distance couldn't be determined, -1.0 is returned. Further, if \a onlySelectable is true and the
 | |
|   object is not selectable, -1.0 is returned, too.
 | |
| 
 | |
|   If the object is represented not by single lines but by an area like a \ref QCPItemText or the
 | |
|   bars of a \ref QCPBars plottable, a click inside the area should also be considered a hit. In
 | |
|   these cases this function thus returns a constant value greater zero but still below the parent
 | |
|   plot's selection tolerance. (typically the selectionTolerance multiplied by 0.99).
 | |
|   
 | |
|   Providing a constant value for area objects allows selecting line objects even when they are
 | |
|   obscured by such area objects, by clicking close to the lines (i.e. closer than
 | |
|   0.99*selectionTolerance).
 | |
|   
 | |
|   The actual setting of the selection state is not done by this function. This is handled by the
 | |
|   parent QCustomPlot when the mouseReleaseEvent occurs, and the finally selected object is notified
 | |
|   via the \ref selectEvent/\ref deselectEvent methods.
 | |
|   
 | |
|   \a details is an optional output parameter. Every layerable subclass may place any information
 | |
|   in \a details. This information will be passed to \ref selectEvent when the parent QCustomPlot
 | |
|   decides on the basis of this selectTest call, that the object was successfully selected. The
 | |
|   subsequent call to \ref selectEvent will carry the \a details. This is useful for multi-part
 | |
|   objects (like QCPAxis). This way, a possibly complex calculation to decide which part was clicked
 | |
|   is only done once in \ref selectTest. The result (i.e. the actually clicked part) can then be
 | |
|   placed in \a details. So in the subsequent \ref selectEvent, the decision which part was
 | |
|   selected doesn't have to be done a second time for a single selection operation.
 | |
|   
 | |
|   You may pass 0 as \a details to indicate that you are not interested in those selection details.
 | |
|   
 | |
|   \see selectEvent, deselectEvent, mousePressEvent, wheelEvent, QCustomPlot::setInteractions
 | |
| */
 | |
| double QCPLayerable::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
 | |
| {
 | |
|   Q_UNUSED(pos)
 | |
|   Q_UNUSED(onlySelectable)
 | |
|   Q_UNUSED(details)
 | |
|   return -1.0;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Sets the parent plot of this layerable. Use this function once to set the parent plot if you have
 | |
|   passed 0 in the constructor. It can not be used to move a layerable from one QCustomPlot to
 | |
|   another one.
 | |
|   
 | |
|   Note that, unlike when passing a non-null parent plot in the constructor, this function does not
 | |
|   make \a parentPlot the QObject-parent of this layerable. If you want this, call
 | |
|   QObject::setParent(\a parentPlot) in addition to this function.
 | |
|   
 | |
|   Further, you will probably want to set a layer (\ref setLayer) after calling this function, to
 | |
|   make the layerable appear on the QCustomPlot.
 | |
|   
 | |
|   The parent plot change will be propagated to subclasses via a call to \ref parentPlotInitialized
 | |
|   so they can react accordingly (e.g. also initialize the parent plot of child layerables, like
 | |
|   QCPLayout does).
 | |
| */
 | |
| void QCPLayerable::initializeParentPlot(QCustomPlot *parentPlot)
 | |
| {
 | |
|   if (mParentPlot)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "called with mParentPlot already initialized";
 | |
|     return;
 | |
|   }
 | |
|   
 | |
|   if (!parentPlot)
 | |
|     qDebug() << Q_FUNC_INFO << "called with parentPlot zero";
 | |
|   
 | |
|   mParentPlot = parentPlot;
 | |
|   parentPlotInitialized(mParentPlot);
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Sets the parent layerable of this layerable to \a parentLayerable. Note that \a parentLayerable does not
 | |
|   become the QObject-parent (for memory management) of this layerable.
 | |
|   
 | |
|   The parent layerable has influence on the return value of the \ref realVisibility method. Only
 | |
|   layerables with a fully visible parent tree will return true for \ref realVisibility, and thus be
 | |
|   drawn.
 | |
|   
 | |
|   \see realVisibility
 | |
| */
 | |
| void QCPLayerable::setParentLayerable(QCPLayerable *parentLayerable)
 | |
| {
 | |
|   mParentLayerable = parentLayerable;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Moves this layerable object to \a layer. If \a prepend is true, this object will be prepended to
 | |
|   the new layer's list, i.e. it will be drawn below the objects already on the layer. If it is
 | |
|   false, the object will be appended.
 | |
|   
 | |
|   Returns true on success, i.e. if \a layer is a valid layer.
 | |
| */
 | |
| bool QCPLayerable::moveToLayer(QCPLayer *layer, bool prepend)
 | |
| {
 | |
|   if (layer && !mParentPlot)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "no parent QCustomPlot set";
 | |
|     return false;
 | |
|   }
 | |
|   if (layer && layer->parentPlot() != mParentPlot)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "layer" << layer->name() << "is not in same QCustomPlot as this layerable";
 | |
|     return false;
 | |
|   }
 | |
|   
 | |
|   QCPLayer *oldLayer = mLayer;
 | |
|   if (mLayer)
 | |
|     mLayer->removeChild(this);
 | |
|   mLayer = layer;
 | |
|   if (mLayer)
 | |
|     mLayer->addChild(this, prepend);
 | |
|   if (mLayer != oldLayer)
 | |
|     emit layerChanged(mLayer);
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Sets the QCPainter::setAntialiasing state on the provided \a painter, depending on the \a
 | |
|   localAntialiased value as well as the overrides \ref QCustomPlot::setAntialiasedElements and \ref
 | |
|   QCustomPlot::setNotAntialiasedElements. Which override enum this function takes into account is
 | |
|   controlled via \a overrideElement.
 | |
| */
 | |
| void QCPLayerable::applyAntialiasingHint(QCPPainter *painter, bool localAntialiased, QCP::AntialiasedElement overrideElement) const
 | |
| {
 | |
|   if (mParentPlot && mParentPlot->notAntialiasedElements().testFlag(overrideElement))
 | |
|     painter->setAntialiasing(false);
 | |
|   else if (mParentPlot && mParentPlot->antialiasedElements().testFlag(overrideElement))
 | |
|     painter->setAntialiasing(true);
 | |
|   else
 | |
|     painter->setAntialiasing(localAntialiased);
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   This function is called by \ref initializeParentPlot, to allow subclasses to react on the setting
 | |
|   of a parent plot. This is the case when 0 was passed as parent plot in the constructor, and the
 | |
|   parent plot is set at a later time.
 | |
|   
 | |
|   For example, QCPLayoutElement/QCPLayout hierarchies may be created independently of any
 | |
|   QCustomPlot at first. When they are then added to a layout inside the QCustomPlot, the top level
 | |
|   element of the hierarchy gets its parent plot initialized with \ref initializeParentPlot. To
 | |
|   propagate the parent plot to all the children of the hierarchy, the top level element then uses
 | |
|   this function to pass the parent plot on to its child elements.
 | |
|   
 | |
|   The default implementation does nothing.
 | |
|   
 | |
|   \see initializeParentPlot
 | |
| */
 | |
| void QCPLayerable::parentPlotInitialized(QCustomPlot *parentPlot)
 | |
| {
 | |
|    Q_UNUSED(parentPlot)
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Returns the selection category this layerable shall belong to. The selection category is used in
 | |
|   conjunction with \ref QCustomPlot::setInteractions to control which objects are selectable and
 | |
|   which aren't.
 | |
|   
 | |
|   Subclasses that don't fit any of the normal \ref QCP::Interaction values can use \ref
 | |
|   QCP::iSelectOther. This is what the default implementation returns.
 | |
|   
 | |
|   \see QCustomPlot::setInteractions
 | |
| */
 | |
| QCP::Interaction QCPLayerable::selectionCategory() const
 | |
| {
 | |
|   return QCP::iSelectOther;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns the clipping rectangle of this layerable object. By default, this is the viewport of the
 | |
|   parent QCustomPlot. Specific subclasses may reimplement this function to provide different
 | |
|   clipping rects.
 | |
|   
 | |
|   The returned clipping rect is set on the painter before the draw function of the respective
 | |
|   object is called.
 | |
| */
 | |
| QRect QCPLayerable::clipRect() const
 | |
| {
 | |
|   if (mParentPlot)
 | |
|     return mParentPlot->viewport();
 | |
|   else
 | |
|     return QRect();
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   This event is called when the layerable shall be selected, as a consequence of a click by the
 | |
|   user. Subclasses should react to it by setting their selection state appropriately. The default
 | |
|   implementation does nothing.
 | |
|   
 | |
|   \a event is the mouse event that caused the selection. \a additive indicates, whether the user
 | |
|   was holding the multi-select-modifier while performing the selection (see \ref
 | |
|   QCustomPlot::setMultiSelectModifier). if \a additive is true, the selection state must be toggled
 | |
|   (i.e. become selected when unselected and unselected when selected).
 | |
|   
 | |
|   Every selectEvent is preceded by a call to \ref selectTest, which has returned positively (i.e.
 | |
|   returned a value greater than 0 and less than the selection tolerance of the parent QCustomPlot).
 | |
|   The \a details data you output from \ref selectTest is fed back via \a details here. You may
 | |
|   use it to transport any kind of information from the selectTest to the possibly subsequent
 | |
|   selectEvent. Usually \a details is used to transfer which part was clicked, if it is a layerable
 | |
|   that has multiple individually selectable parts (like QCPAxis). This way selectEvent doesn't need
 | |
|   to do the calculation again to find out which part was actually clicked.
 | |
|   
 | |
|   \a selectionStateChanged is an output parameter. If the pointer is non-null, this function must
 | |
|   set the value either to true or false, depending on whether the selection state of this layerable
 | |
|   was actually changed. For layerables that only are selectable as a whole and not in parts, this
 | |
|   is simple: if \a additive is true, \a selectionStateChanged must also be set to true, because the
 | |
|   selection toggles. If \a additive is false, \a selectionStateChanged is only set to true, if the
 | |
|   layerable was previously unselected and now is switched to the selected state.
 | |
|   
 | |
|   \see selectTest, deselectEvent
 | |
| */
 | |
| void QCPLayerable::selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged)
 | |
| {
 | |
|   Q_UNUSED(event)
 | |
|   Q_UNUSED(additive)
 | |
|   Q_UNUSED(details)
 | |
|   Q_UNUSED(selectionStateChanged)
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   This event is called when the layerable shall be deselected, either as consequence of a user
 | |
|   interaction or a call to \ref QCustomPlot::deselectAll. Subclasses should react to it by
 | |
|   unsetting their selection appropriately.
 | |
|   
 | |
|   just as in \ref selectEvent, the output parameter \a selectionStateChanged (if non-null), must
 | |
|   return true or false when the selection state of this layerable has changed or not changed,
 | |
|   respectively.
 | |
|   
 | |
|   \see selectTest, selectEvent
 | |
| */
 | |
| void QCPLayerable::deselectEvent(bool *selectionStateChanged)
 | |
| {
 | |
|   Q_UNUSED(selectionStateChanged)
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   This event gets called when the user presses a mouse button while the cursor is over the
 | |
|   layerable. Whether a cursor is over the layerable is decided by a preceding call to \ref
 | |
|   selectTest.
 | |
| 
 | |
|   The current pixel position of the cursor on the QCustomPlot widget is accessible via \c
 | |
|   event->pos(). The parameter \a details contains layerable-specific details about the hit, which
 | |
|   were generated in the previous call to \ref selectTest. For example, One-dimensional plottables
 | |
|   like \ref QCPGraph or \ref QCPBars convey the clicked data point in the \a details parameter, as
 | |
|   \ref QCPDataSelection packed as QVariant. Multi-part objects convey the specific \c
 | |
|   SelectablePart that was hit (e.g. \ref QCPAxis::SelectablePart in the case of axes).
 | |
| 
 | |
|   QCustomPlot uses an event propagation system that works the same as Qt's system. If your
 | |
|   layerable doesn't reimplement the \ref mousePressEvent or explicitly calls \c event->ignore() in
 | |
|   its reimplementation, the event will be propagated to the next layerable in the stacking order.
 | |
| 
 | |
|   Once a layerable has accepted the \ref mousePressEvent, it is considered the mouse grabber and
 | |
|   will receive all following calls to \ref mouseMoveEvent or \ref mouseReleaseEvent for this mouse
 | |
|   interaction (a "mouse interaction" in this context ends with the release).
 | |
| 
 | |
|   The default implementation does nothing except explicitly ignoring the event with \c
 | |
|   event->ignore().
 | |
| 
 | |
|   \see mouseMoveEvent, mouseReleaseEvent, mouseDoubleClickEvent, wheelEvent
 | |
| */
 | |
| void QCPLayerable::mousePressEvent(QMouseEvent *event, const QVariant &details)
 | |
| {
 | |
|   Q_UNUSED(details)
 | |
|   event->ignore();
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   This event gets called when the user moves the mouse while holding a mouse button, after this
 | |
|   layerable has become the mouse grabber by accepting the preceding \ref mousePressEvent.
 | |
| 
 | |
|   The current pixel position of the cursor on the QCustomPlot widget is accessible via \c
 | |
|   event->pos(). The parameter \a startPos indicates the position where the initial \ref
 | |
|   mousePressEvent occured, that started the mouse interaction.
 | |
| 
 | |
|   The default implementation does nothing.
 | |
| 
 | |
|   \see mousePressEvent, mouseReleaseEvent, mouseDoubleClickEvent, wheelEvent
 | |
| */
 | |
| void QCPLayerable::mouseMoveEvent(QMouseEvent *event, const QPointF &startPos)
 | |
| {
 | |
|   Q_UNUSED(startPos)
 | |
|   event->ignore();
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   This event gets called when the user releases the mouse button, after this layerable has become
 | |
|   the mouse grabber by accepting the preceding \ref mousePressEvent.
 | |
| 
 | |
|   The current pixel position of the cursor on the QCustomPlot widget is accessible via \c
 | |
|   event->pos(). The parameter \a startPos indicates the position where the initial \ref
 | |
|   mousePressEvent occured, that started the mouse interaction.
 | |
| 
 | |
|   The default implementation does nothing.
 | |
| 
 | |
|   \see mousePressEvent, mouseMoveEvent, mouseDoubleClickEvent, wheelEvent
 | |
| */
 | |
| void QCPLayerable::mouseReleaseEvent(QMouseEvent *event, const QPointF &startPos)
 | |
| {
 | |
|   Q_UNUSED(startPos)
 | |
|   event->ignore();
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   This event gets called when the user presses the mouse button a second time in a double-click,
 | |
|   while the cursor is over the layerable. Whether a cursor is over the layerable is decided by a
 | |
|   preceding call to \ref selectTest.
 | |
| 
 | |
|   The \ref mouseDoubleClickEvent is called instead of the second \ref mousePressEvent. So in the
 | |
|   case of a double-click, the event succession is
 | |
|   <i>pressEvent – releaseEvent – doubleClickEvent – releaseEvent</i>.
 | |
| 
 | |
|   The current pixel position of the cursor on the QCustomPlot widget is accessible via \c
 | |
|   event->pos(). The parameter \a details contains layerable-specific details about the hit, which
 | |
|   were generated in the previous call to \ref selectTest. For example, One-dimensional plottables
 | |
|   like \ref QCPGraph or \ref QCPBars convey the clicked data point in the \a details parameter, as
 | |
|   \ref QCPDataSelection packed as QVariant. Multi-part objects convey the specific \c
 | |
|   SelectablePart that was hit (e.g. \ref QCPAxis::SelectablePart in the case of axes).
 | |
| 
 | |
|   Similarly to \ref mousePressEvent, once a layerable has accepted the \ref mouseDoubleClickEvent,
 | |
|   it is considered the mouse grabber and will receive all following calls to \ref mouseMoveEvent
 | |
|   and \ref mouseReleaseEvent for this mouse interaction (a "mouse interaction" in this context ends
 | |
|   with the release).
 | |
| 
 | |
|   The default implementation does nothing except explicitly ignoring the event with \c
 | |
|   event->ignore().
 | |
| 
 | |
|   \see mousePressEvent, mouseMoveEvent, mouseReleaseEvent, wheelEvent
 | |
| */
 | |
| void QCPLayerable::mouseDoubleClickEvent(QMouseEvent *event, const QVariant &details)
 | |
| {
 | |
|   Q_UNUSED(details)
 | |
|   event->ignore();
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   This event gets called when the user turns the mouse scroll wheel while the cursor is over the
 | |
|   layerable. Whether a cursor is over the layerable is decided by a preceding call to \ref
 | |
|   selectTest.
 | |
| 
 | |
|   The current pixel position of the cursor on the QCustomPlot widget is accessible via \c
 | |
|   event->pos().
 | |
| 
 | |
|   The \c event->delta() indicates how far the mouse wheel was turned, which is usually +/- 120 for
 | |
|   single rotation steps. However, if the mouse wheel is turned rapidly, multiple steps may
 | |
|   accumulate to one event, making \c event->delta() larger. On the other hand, if the wheel has
 | |
|   very smooth steps or none at all, the delta may be smaller.
 | |
| 
 | |
|   The default implementation does nothing.
 | |
| 
 | |
|   \see mousePressEvent, mouseMoveEvent, mouseReleaseEvent, mouseDoubleClickEvent
 | |
| */
 | |
| void QCPLayerable::wheelEvent(QWheelEvent *event)
 | |
| {
 | |
|   event->ignore();
 | |
| }
 | |
| /* end of 'src/layer.cpp' */
 | |
| 
 | |
| 
 | |
| /* including file 'src/axis/range.cpp', size 12221                           */
 | |
| /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPRange
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| /*! \class QCPRange
 | |
|   \brief Represents the range an axis is encompassing.
 | |
|   
 | |
|   contains a \a lower and \a upper double value and provides convenience input, output and
 | |
|   modification functions.
 | |
|   
 | |
|   \see QCPAxis::setRange
 | |
| */
 | |
| 
 | |
| /* start of documentation of inline functions */
 | |
| 
 | |
| /*! \fn double QCPRange::size() const
 | |
| 
 | |
|   Returns the size of the range, i.e. \a upper-\a lower
 | |
| */
 | |
| 
 | |
| /*! \fn double QCPRange::center() const
 | |
| 
 | |
|   Returns the center of the range, i.e. (\a upper+\a lower)*0.5
 | |
| */
 | |
| 
 | |
| /*! \fn void QCPRange::normalize()
 | |
| 
 | |
|   Makes sure \a lower is numerically smaller than \a upper. If this is not the case, the values are
 | |
|   swapped.
 | |
| */
 | |
| 
 | |
| /*! \fn bool QCPRange::contains(double value) const
 | |
| 
 | |
|   Returns true when \a value lies within or exactly on the borders of the range.
 | |
| */
 | |
| 
 | |
| /*! \fn QCPRange &QCPRange::operator+=(const double& value)
 | |
| 
 | |
|   Adds \a value to both boundaries of the range.
 | |
| */
 | |
| 
 | |
| /*! \fn QCPRange &QCPRange::operator-=(const double& value)
 | |
| 
 | |
|   Subtracts \a value from both boundaries of the range.
 | |
| */
 | |
| 
 | |
| /*! \fn QCPRange &QCPRange::operator*=(const double& value)
 | |
| 
 | |
|   Multiplies both boundaries of the range by \a value.
 | |
| */
 | |
| 
 | |
| /*! \fn QCPRange &QCPRange::operator/=(const double& value)
 | |
| 
 | |
|   Divides both boundaries of the range by \a value.
 | |
| */
 | |
| 
 | |
| /* end of documentation of inline functions */
 | |
| 
 | |
| /*!
 | |
|   Minimum range size (\a upper - \a lower) the range changing functions will accept. Smaller
 | |
|   intervals would cause errors due to the 11-bit exponent of double precision numbers,
 | |
|   corresponding to a minimum magnitude of roughly 1e-308.
 | |
| 
 | |
|   \warning Do not use this constant to indicate "arbitrarily small" values in plotting logic (as
 | |
|   values that will appear in the plot)! It is intended only as a bound to compare against, e.g. to
 | |
|   prevent axis ranges from obtaining underflowing ranges.
 | |
| 
 | |
|   \see validRange, maxRange
 | |
| */
 | |
| const double QCPRange::minRange = 1e-280;
 | |
| 
 | |
| /*!
 | |
|   Maximum values (negative and positive) the range will accept in range-changing functions.
 | |
|   Larger absolute values would cause errors due to the 11-bit exponent of double precision numbers,
 | |
|   corresponding to a maximum magnitude of roughly 1e308.
 | |
| 
 | |
|   \warning Do not use this constant to indicate "arbitrarily large" values in plotting logic (as
 | |
|   values that will appear in the plot)! It is intended only as a bound to compare against, e.g. to
 | |
|   prevent axis ranges from obtaining overflowing ranges.
 | |
| 
 | |
|   \see validRange, minRange
 | |
| */
 | |
| const double QCPRange::maxRange = 1e250;
 | |
| 
 | |
| /*!
 | |
|   Constructs a range with \a lower and \a upper set to zero.
 | |
| */
 | |
| QCPRange::QCPRange() :
 | |
|   lower(0),
 | |
|   upper(0)
 | |
| {
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
| 
 | |
|   Constructs a range with the specified \a lower and \a upper values.
 | |
| 
 | |
|   The resulting range will be normalized (see \ref normalize), so if \a lower is not numerically
 | |
|   smaller than \a upper, they will be swapped.
 | |
| */
 | |
| QCPRange::QCPRange(double lower, double upper) :
 | |
|   lower(lower),
 | |
|   upper(upper)
 | |
| {
 | |
|   normalize();
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
| 
 | |
|   Expands this range such that \a otherRange is contained in the new range. It is assumed that both
 | |
|   this range and \a otherRange are normalized (see \ref normalize).
 | |
| 
 | |
|   If this range contains NaN as lower or upper bound, it will be replaced by the respective bound
 | |
|   of \a otherRange.
 | |
| 
 | |
|   If \a otherRange is already inside the current range, this function does nothing.
 | |
| 
 | |
|   \see expanded
 | |
| */
 | |
| void QCPRange::expand(const QCPRange &otherRange)
 | |
| {
 | |
|   if (lower > otherRange.lower || qIsNaN(lower))
 | |
|     lower = otherRange.lower;
 | |
|   if (upper < otherRange.upper || qIsNaN(upper))
 | |
|     upper = otherRange.upper;
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
| 
 | |
|   Expands this range such that \a includeCoord is contained in the new range. It is assumed that
 | |
|   this range is normalized (see \ref normalize).
 | |
| 
 | |
|   If this range contains NaN as lower or upper bound, the respective bound will be set to \a
 | |
|   includeCoord.
 | |
| 
 | |
|   If \a includeCoord is already inside the current range, this function does nothing.
 | |
| 
 | |
|   \see expand
 | |
| */
 | |
| void QCPRange::expand(double includeCoord)
 | |
| {
 | |
|   if (lower > includeCoord || qIsNaN(lower))
 | |
|     lower = includeCoord;
 | |
|   if (upper < includeCoord || qIsNaN(upper))
 | |
|     upper = includeCoord;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*! \overload
 | |
| 
 | |
|   Returns an expanded range that contains this and \a otherRange. It is assumed that both this
 | |
|   range and \a otherRange are normalized (see \ref normalize).
 | |
| 
 | |
|   If this range contains NaN as lower or upper bound, the returned range's bound will be taken from
 | |
|   \a otherRange.
 | |
| 
 | |
|   \see expand
 | |
| */
 | |
| QCPRange QCPRange::expanded(const QCPRange &otherRange) const
 | |
| {
 | |
|   QCPRange result = *this;
 | |
|   result.expand(otherRange);
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
| 
 | |
|   Returns an expanded range that includes the specified \a includeCoord. It is assumed that this
 | |
|   range is normalized (see \ref normalize).
 | |
| 
 | |
|   If this range contains NaN as lower or upper bound, the returned range's bound will be set to \a
 | |
|   includeCoord.
 | |
| 
 | |
|   \see expand
 | |
| */
 | |
| QCPRange QCPRange::expanded(double includeCoord) const
 | |
| {
 | |
|   QCPRange result = *this;
 | |
|   result.expand(includeCoord);
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns this range, possibly modified to not exceed the bounds provided as \a lowerBound and \a
 | |
|   upperBound. If possible, the size of the current range is preserved in the process.
 | |
|   
 | |
|   If the range shall only be bounded at the lower side, you can set \a upperBound to \ref
 | |
|   QCPRange::maxRange. If it shall only be bounded at the upper side, set \a lowerBound to -\ref
 | |
|   QCPRange::maxRange.
 | |
| */
 | |
| QCPRange QCPRange::bounded(double lowerBound, double upperBound) const
 | |
| {
 | |
|   if (lowerBound > upperBound)
 | |
|     qSwap(lowerBound, upperBound);
 | |
|   
 | |
|   QCPRange result(lower, upper);
 | |
|   if (result.lower < lowerBound)
 | |
|   {
 | |
|     result.lower = lowerBound;
 | |
|     result.upper = lowerBound + size();
 | |
|     if (result.upper > upperBound || qFuzzyCompare(size(), upperBound-lowerBound))
 | |
|       result.upper = upperBound;
 | |
|   } else if (result.upper > upperBound)
 | |
|   {
 | |
|     result.upper = upperBound;
 | |
|     result.lower = upperBound - size();
 | |
|     if (result.lower < lowerBound || qFuzzyCompare(size(), upperBound-lowerBound))
 | |
|       result.lower = lowerBound;
 | |
|   }
 | |
|   
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns a sanitized version of the range. Sanitized means for logarithmic scales, that
 | |
|   the range won't span the positive and negative sign domain, i.e. contain zero. Further
 | |
|   \a lower will always be numerically smaller (or equal) to \a upper.
 | |
|   
 | |
|   If the original range does span positive and negative sign domains or contains zero,
 | |
|   the returned range will try to approximate the original range as good as possible.
 | |
|   If the positive interval of the original range is wider than the negative interval, the
 | |
|   returned range will only contain the positive interval, with lower bound set to \a rangeFac or
 | |
|   \a rangeFac *\a upper, whichever is closer to zero. Same procedure is used if the negative interval
 | |
|   is wider than the positive interval, this time by changing the \a upper bound.
 | |
| */
 | |
| QCPRange QCPRange::sanitizedForLogScale() const
 | |
| {
 | |
|   double rangeFac = 1e-3;
 | |
|   QCPRange sanitizedRange(lower, upper);
 | |
|   sanitizedRange.normalize();
 | |
|   // can't have range spanning negative and positive values in log plot, so change range to fix it
 | |
|   //if (qFuzzyCompare(sanitizedRange.lower+1, 1) && !qFuzzyCompare(sanitizedRange.upper+1, 1))
 | |
|   if (sanitizedRange.lower == 0.0 && sanitizedRange.upper != 0.0)
 | |
|   {
 | |
|     // case lower is 0
 | |
|     if (rangeFac < sanitizedRange.upper*rangeFac)
 | |
|       sanitizedRange.lower = rangeFac;
 | |
|     else
 | |
|       sanitizedRange.lower = sanitizedRange.upper*rangeFac;
 | |
|   } //else if (!qFuzzyCompare(lower+1, 1) && qFuzzyCompare(upper+1, 1))
 | |
|   else if (sanitizedRange.lower != 0.0 && sanitizedRange.upper == 0.0)
 | |
|   {
 | |
|     // case upper is 0
 | |
|     if (-rangeFac > sanitizedRange.lower*rangeFac)
 | |
|       sanitizedRange.upper = -rangeFac;
 | |
|     else
 | |
|       sanitizedRange.upper = sanitizedRange.lower*rangeFac;
 | |
|   } else if (sanitizedRange.lower < 0 && sanitizedRange.upper > 0)
 | |
|   {
 | |
|     // find out whether negative or positive interval is wider to decide which sign domain will be chosen
 | |
|     if (-sanitizedRange.lower > sanitizedRange.upper)
 | |
|     {
 | |
|       // negative is wider, do same as in case upper is 0
 | |
|       if (-rangeFac > sanitizedRange.lower*rangeFac)
 | |
|         sanitizedRange.upper = -rangeFac;
 | |
|       else
 | |
|         sanitizedRange.upper = sanitizedRange.lower*rangeFac;
 | |
|     } else
 | |
|     {
 | |
|       // positive is wider, do same as in case lower is 0
 | |
|       if (rangeFac < sanitizedRange.upper*rangeFac)
 | |
|         sanitizedRange.lower = rangeFac;
 | |
|       else
 | |
|         sanitizedRange.lower = sanitizedRange.upper*rangeFac;
 | |
|     }
 | |
|   }
 | |
|   // due to normalization, case lower>0 && upper<0 should never occur, because that implies upper<lower
 | |
|   return sanitizedRange;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns a sanitized version of the range. Sanitized means for linear scales, that
 | |
|   \a lower will always be numerically smaller (or equal) to \a upper.
 | |
| */
 | |
| QCPRange QCPRange::sanitizedForLinScale() const
 | |
| {
 | |
|   QCPRange sanitizedRange(lower, upper);
 | |
|   sanitizedRange.normalize();
 | |
|   return sanitizedRange;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Checks, whether the specified range is within valid bounds, which are defined
 | |
|   as QCPRange::maxRange and QCPRange::minRange.
 | |
|   A valid range means:
 | |
|   \li range bounds within -maxRange and maxRange
 | |
|   \li range size above minRange
 | |
|   \li range size below maxRange
 | |
| */
 | |
| bool QCPRange::validRange(double lower, double upper)
 | |
| {
 | |
|   return (lower > -maxRange &&
 | |
|           upper < maxRange &&
 | |
|           qAbs(lower-upper) > minRange &&
 | |
|           qAbs(lower-upper) < maxRange &&
 | |
|           !(lower > 0 && qIsInf(upper/lower)) &&
 | |
|           !(upper < 0 && qIsInf(lower/upper)));
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   \overload
 | |
|   Checks, whether the specified range is within valid bounds, which are defined
 | |
|   as QCPRange::maxRange and QCPRange::minRange.
 | |
|   A valid range means:
 | |
|   \li range bounds within -maxRange and maxRange
 | |
|   \li range size above minRange
 | |
|   \li range size below maxRange
 | |
| */
 | |
| bool QCPRange::validRange(const QCPRange &range)
 | |
| {
 | |
|   return (range.lower > -maxRange &&
 | |
|           range.upper < maxRange &&
 | |
|           qAbs(range.lower-range.upper) > minRange &&
 | |
|           qAbs(range.lower-range.upper) < maxRange &&
 | |
|           !(range.lower > 0 && qIsInf(range.upper/range.lower)) &&
 | |
|           !(range.upper < 0 && qIsInf(range.lower/range.upper)));
 | |
| }
 | |
| /* end of 'src/axis/range.cpp' */
 | |
| 
 | |
| 
 | |
| /* including file 'src/selection.cpp', size 21898                            */
 | |
| /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPDataRange
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPDataRange
 | |
|   \brief Describes a data range given by begin and end index
 | |
|   
 | |
|   QCPDataRange holds two integers describing the begin (\ref setBegin) and end (\ref setEnd) index
 | |
|   of a contiguous set of data points. The end index points to the data point above the last data point that's part of
 | |
|   the data range, similarly to the nomenclature used in standard iterators.
 | |
|   
 | |
|   Data Ranges are not bound to a certain plottable, thus they can be freely exchanged, created and
 | |
|   modified. If a non-contiguous data set shall be described, the class \ref QCPDataSelection is
 | |
|   used, which holds and manages multiple instances of \ref QCPDataRange. In most situations, \ref
 | |
|   QCPDataSelection is thus used.
 | |
|   
 | |
|   Both \ref QCPDataRange and \ref QCPDataSelection offer convenience methods to work with them,
 | |
|   e.g. \ref bounded, \ref expanded, \ref intersects, \ref intersection, \ref adjusted, \ref
 | |
|   contains. Further, addition and subtraction operators (defined in \ref QCPDataSelection) can be
 | |
|   used to join/subtract data ranges and data selections (or mixtures), to retrieve a corresponding
 | |
|   \ref QCPDataSelection.
 | |
|   
 | |
|   %QCustomPlot's \ref dataselection "data selection mechanism" is based on \ref QCPDataSelection and
 | |
|   QCPDataRange.
 | |
|   
 | |
|   \note Do not confuse \ref QCPDataRange with \ref QCPRange. A \ref QCPRange describes an interval
 | |
|   in floating point plot coordinates, e.g. the current axis range.
 | |
| */
 | |
| 
 | |
| /* start documentation of inline functions */
 | |
| 
 | |
| /*! \fn int QCPDataRange::size() const
 | |
|   
 | |
|   Returns the number of data points described by this data range. This is equal to the end index
 | |
|   minus the begin index.
 | |
|   
 | |
|   \see length
 | |
| */
 | |
| 
 | |
| /*! \fn int QCPDataRange::length() const
 | |
|   
 | |
|   Returns the number of data points described by this data range. Equivalent to \ref size.
 | |
| */
 | |
| 
 | |
| /*! \fn void QCPDataRange::setBegin(int begin)
 | |
|   
 | |
|   Sets the begin of this data range. The \a begin index points to the first data point that is part
 | |
|   of the data range.
 | |
|   
 | |
|   No checks or corrections are made to ensure the resulting range is valid (\ref isValid).
 | |
|   
 | |
|   \see setEnd
 | |
| */
 | |
| 
 | |
| /*! \fn void QCPDataRange::setEnd(int end)
 | |
|   
 | |
|   Sets the end of this data range. The \a end index points to the data point just above the last
 | |
|   data point that is part of the data range.
 | |
|   
 | |
|   No checks or corrections are made to ensure the resulting range is valid (\ref isValid).
 | |
|   
 | |
|   \see setBegin
 | |
| */
 | |
| 
 | |
| /*! \fn bool QCPDataRange::isValid() const
 | |
|   
 | |
|   Returns whether this range is valid. A valid range has a begin index greater or equal to 0, and
 | |
|   an end index greater or equal to the begin index.
 | |
|   
 | |
|   \note Invalid ranges should be avoided and are never the result of any of QCustomPlot's methods
 | |
|   (unless they are themselves fed with invalid ranges). Do not pass invalid ranges to QCustomPlot's
 | |
|   methods. The invalid range is not inherently prevented in QCPDataRange, to allow temporary
 | |
|   invalid begin/end values while manipulating the range. An invalid range is not necessarily empty
 | |
|   (\ref isEmpty), since its \ref length can be negative and thus non-zero.
 | |
| */
 | |
| 
 | |
| /*! \fn bool QCPDataRange::isEmpty() const
 | |
|   
 | |
|   Returns whether this range is empty, i.e. whether its begin index equals its end index.
 | |
|   
 | |
|   \see size, length
 | |
| */
 | |
| 
 | |
| /*! \fn QCPDataRange QCPDataRange::adjusted(int changeBegin, int changeEnd) const
 | |
|   
 | |
|   Returns a data range where \a changeBegin and \a changeEnd were added to the begin and end
 | |
|   indices, respectively.
 | |
| */
 | |
| 
 | |
| /* end documentation of inline functions */
 | |
| 
 | |
| /*!
 | |
|   Creates an empty QCPDataRange, with begin and end set to 0.
 | |
| */
 | |
| QCPDataRange::QCPDataRange() :
 | |
|   mBegin(0),
 | |
|   mEnd(0)
 | |
| {
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Creates a QCPDataRange, initialized with the specified \a begin and \a end.
 | |
|   
 | |
|   No checks or corrections are made to ensure the resulting range is valid (\ref isValid).
 | |
| */
 | |
| QCPDataRange::QCPDataRange(int begin, int end) :
 | |
|   mBegin(begin),
 | |
|   mEnd(end)
 | |
| {
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns a data range that matches this data range, except that parts exceeding \a other are
 | |
|   excluded.
 | |
|   
 | |
|   This method is very similar to \ref intersection, with one distinction: If this range and the \a
 | |
|   other range share no intersection, the returned data range will be empty with begin and end set
 | |
|   to the respective boundary side of \a other, at which this range is residing. (\ref intersection
 | |
|   would just return a range with begin and end set to 0.)
 | |
| */
 | |
| QCPDataRange QCPDataRange::bounded(const QCPDataRange &other) const
 | |
| {
 | |
|   QCPDataRange result(intersection(other));
 | |
|   if (result.isEmpty()) // no intersection, preserve respective bounding side of otherRange as both begin and end of return value
 | |
|   {
 | |
|     if (mEnd <= other.mBegin)
 | |
|       result = QCPDataRange(other.mBegin, other.mBegin);
 | |
|     else
 | |
|       result = QCPDataRange(other.mEnd, other.mEnd);
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns a data range that contains both this data range as well as \a other.
 | |
| */
 | |
| QCPDataRange QCPDataRange::expanded(const QCPDataRange &other) const
 | |
| {
 | |
|   return QCPDataRange(qMin(mBegin, other.mBegin), qMax(mEnd, other.mEnd));
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the data range which is contained in both this data range and \a other.
 | |
|   
 | |
|   This method is very similar to \ref bounded, with one distinction: If this range and the \a other
 | |
|   range share no intersection, the returned data range will be empty with begin and end set to 0.
 | |
|   (\ref bounded would return a range with begin and end set to one of the boundaries of \a other,
 | |
|   depending on which side this range is on.)
 | |
|   
 | |
|   \see QCPDataSelection::intersection
 | |
| */
 | |
| QCPDataRange QCPDataRange::intersection(const QCPDataRange &other) const
 | |
| {
 | |
|   QCPDataRange result(qMax(mBegin, other.mBegin), qMin(mEnd, other.mEnd));
 | |
|   if (result.isValid())
 | |
|     return result;
 | |
|   else
 | |
|     return QCPDataRange();
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns whether this data range and \a other share common data points.
 | |
|   
 | |
|   \see intersection, contains
 | |
| */
 | |
| bool QCPDataRange::intersects(const QCPDataRange &other) const
 | |
| {
 | |
|    return !( (mBegin > other.mBegin && mBegin >= other.mEnd) ||
 | |
|              (mEnd <= other.mBegin && mEnd < other.mEnd) );
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns whether all data points described by this data range are also in \a other.
 | |
|   
 | |
|   \see intersects
 | |
| */
 | |
| bool QCPDataRange::contains(const QCPDataRange &other) const
 | |
| {
 | |
|   return mBegin <= other.mBegin && mEnd >= other.mEnd;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPDataSelection
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPDataSelection
 | |
|   \brief Describes a data set by holding multiple QCPDataRange instances
 | |
|   
 | |
|   QCPDataSelection manages multiple instances of QCPDataRange in order to represent any (possibly
 | |
|   disjoint) set of data selection.
 | |
|   
 | |
|   The data selection can be modified with addition and subtraction operators which take
 | |
|   QCPDataSelection and QCPDataRange instances, as well as methods such as \ref addDataRange and
 | |
|   \ref clear. Read access is provided by \ref dataRange, \ref dataRanges, \ref dataRangeCount, etc.
 | |
|   
 | |
|   The method \ref simplify is used to join directly adjacent or even overlapping QCPDataRange
 | |
|   instances. QCPDataSelection automatically simplifies when using the addition/subtraction
 | |
|   operators. The only case when \ref simplify is left to the user, is when calling \ref
 | |
|   addDataRange, with the parameter \a simplify explicitly set to false. This is useful if many data
 | |
|   ranges will be added to the selection successively and the overhead for simplifying after each
 | |
|   iteration shall be avoided. In this case, you should make sure to call \ref simplify after
 | |
|   completing the operation.
 | |
|   
 | |
|   Use \ref enforceType to bring the data selection into a state complying with the constraints for
 | |
|   selections defined in \ref QCP::SelectionType.
 | |
|   
 | |
|   %QCustomPlot's \ref dataselection "data selection mechanism" is based on QCPDataSelection and
 | |
|   QCPDataRange.
 | |
|   
 | |
|   \section qcpdataselection-iterating Iterating over a data selection
 | |
|   
 | |
|   As an example, the following code snippet calculates the average value of a graph's data
 | |
|   \ref QCPAbstractPlottable::selection "selection":
 | |
|   
 | |
|   \snippet documentation/doc-code-snippets/mainwindow.cpp qcpdataselection-iterating-1
 | |
|   
 | |
| */
 | |
| 
 | |
| /* start documentation of inline functions */
 | |
| 
 | |
| /*! \fn int QCPDataSelection::dataRangeCount() const
 | |
|   
 | |
|   Returns the number of ranges that make up the data selection. The ranges can be accessed by \ref
 | |
|   dataRange via their index.
 | |
|   
 | |
|   \see dataRange, dataPointCount
 | |
| */
 | |
| 
 | |
| /*! \fn QList<QCPDataRange> QCPDataSelection::dataRanges() const
 | |
|   
 | |
|   Returns all data ranges that make up the data selection. If the data selection is simplified (the
 | |
|   usual state of the selection, see \ref simplify), the ranges are sorted by ascending data point
 | |
|   index.
 | |
|   
 | |
|   \see dataRange
 | |
| */
 | |
| 
 | |
| /*! \fn bool QCPDataSelection::isEmpty() const
 | |
|   
 | |
|   Returns true if there are no data ranges, and thus no data points, in this QCPDataSelection
 | |
|   instance.
 | |
|   
 | |
|   \see dataRangeCount
 | |
| */
 | |
| 
 | |
| /* end documentation of inline functions */
 | |
| 
 | |
| /*!
 | |
|   Creates an empty QCPDataSelection.
 | |
| */
 | |
| QCPDataSelection::QCPDataSelection()
 | |
| {
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Creates a QCPDataSelection containing the provided \a range.
 | |
| */
 | |
| QCPDataSelection::QCPDataSelection(const QCPDataRange &range)
 | |
| {
 | |
|   mDataRanges.append(range);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns true if this selection is identical (contains the same data ranges with the same begin
 | |
|   and end indices) to \a other.
 | |
| 
 | |
|   Note that both data selections must be in simplified state (the usual state of the selection, see
 | |
|   \ref simplify) for this operator to return correct results.
 | |
| */
 | |
| bool QCPDataSelection::operator==(const QCPDataSelection &other) const
 | |
| {
 | |
|   if (mDataRanges.size() != other.mDataRanges.size())
 | |
|     return false;
 | |
|   for (int i=0; i<mDataRanges.size(); ++i)
 | |
|   {
 | |
|     if (mDataRanges.at(i) != other.mDataRanges.at(i))
 | |
|       return false;
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Adds the data selection of \a other to this data selection, and then simplifies this data
 | |
|   selection (see \ref simplify).
 | |
| */
 | |
| QCPDataSelection &QCPDataSelection::operator+=(const QCPDataSelection &other)
 | |
| {
 | |
|   mDataRanges << other.mDataRanges;
 | |
|   simplify();
 | |
|   return *this;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Adds the data range \a other to this data selection, and then simplifies this data selection (see
 | |
|   \ref simplify).
 | |
| */
 | |
| QCPDataSelection &QCPDataSelection::operator+=(const QCPDataRange &other)
 | |
| {
 | |
|   addDataRange(other);
 | |
|   return *this;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Removes all data point indices that are described by \a other from this data range.
 | |
| */
 | |
| QCPDataSelection &QCPDataSelection::operator-=(const QCPDataSelection &other)
 | |
| {
 | |
|   for (int i=0; i<other.dataRangeCount(); ++i)
 | |
|     *this -= other.dataRange(i);
 | |
|   
 | |
|   return *this;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Removes all data point indices that are described by \a other from this data range.
 | |
| */
 | |
| QCPDataSelection &QCPDataSelection::operator-=(const QCPDataRange &other)
 | |
| {
 | |
|   if (other.isEmpty() || isEmpty())
 | |
|     return *this;
 | |
|   
 | |
|   simplify();
 | |
|   int i=0;
 | |
|   while (i < mDataRanges.size())
 | |
|   {
 | |
|     const int thisBegin = mDataRanges.at(i).begin();
 | |
|     const int thisEnd = mDataRanges.at(i).end();
 | |
|     if (thisBegin >= other.end())
 | |
|       break; // since data ranges are sorted after the simplify() call, no ranges which contain other will come after this
 | |
|     
 | |
|     if (thisEnd > other.begin()) // ranges which don't fulfill this are entirely before other and can be ignored
 | |
|     {
 | |
|       if (thisBegin >= other.begin()) // range leading segment is encompassed
 | |
|       {
 | |
|         if (thisEnd <= other.end()) // range fully encompassed, remove completely
 | |
|         {
 | |
|           mDataRanges.removeAt(i);
 | |
|           continue;
 | |
|         } else // only leading segment is encompassed, trim accordingly
 | |
|           mDataRanges[i].setBegin(other.end());
 | |
|       } else // leading segment is not encompassed
 | |
|       {
 | |
|         if (thisEnd <= other.end()) // only trailing segment is encompassed, trim accordingly
 | |
|         {
 | |
|           mDataRanges[i].setEnd(other.begin());
 | |
|         } else // other lies inside this range, so split range
 | |
|         {
 | |
|           mDataRanges[i].setEnd(other.begin());
 | |
|           mDataRanges.insert(i+1, QCPDataRange(other.end(), thisEnd));
 | |
|           break; // since data ranges are sorted (and don't overlap) after simplify() call, we're done here
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     ++i;
 | |
|   }
 | |
|   
 | |
|   return *this;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the total number of data points contained in all data ranges that make up this data
 | |
|   selection.
 | |
| */
 | |
| int QCPDataSelection::dataPointCount() const
 | |
| {
 | |
|   int result = 0;
 | |
|   for (int i=0; i<mDataRanges.size(); ++i)
 | |
|     result += mDataRanges.at(i).length();
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the data range with the specified \a index.
 | |
|   
 | |
|   If the data selection is simplified (the usual state of the selection, see \ref simplify), the
 | |
|   ranges are sorted by ascending data point index.
 | |
|   
 | |
|   \see dataRangeCount
 | |
| */
 | |
| QCPDataRange QCPDataSelection::dataRange(int index) const
 | |
| {
 | |
|   if (index >= 0 && index < mDataRanges.size())
 | |
|   {
 | |
|     return mDataRanges.at(index);
 | |
|   } else
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "index out of range:" << index;
 | |
|     return QCPDataRange();
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns a \ref QCPDataRange which spans the entire data selection, including possible
 | |
|   intermediate segments which are not part of the original data selection.
 | |
| */
 | |
| QCPDataRange QCPDataSelection::span() const
 | |
| {
 | |
|   if (isEmpty())
 | |
|     return QCPDataRange();
 | |
|   else
 | |
|     return QCPDataRange(mDataRanges.first().begin(), mDataRanges.last().end());
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Adds the given \a dataRange to this data selection. This is equivalent to the += operator but
 | |
|   allows disabling immediate simplification by setting \a simplify to false. This can improve
 | |
|   performance if adding a very large amount of data ranges successively. In this case, make sure to
 | |
|   call \ref simplify manually, after the operation.
 | |
| */
 | |
| void QCPDataSelection::addDataRange(const QCPDataRange &dataRange, bool simplify)
 | |
| {
 | |
|   mDataRanges.append(dataRange);
 | |
|   if (simplify)
 | |
|     this->simplify();
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Removes all data ranges. The data selection then contains no data points.
 | |
|   
 | |
|   \ref isEmpty
 | |
| */
 | |
| void QCPDataSelection::clear()
 | |
| {
 | |
|   mDataRanges.clear();
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sorts all data ranges by range begin index in ascending order, and then joins directly adjacent
 | |
|   or overlapping ranges. This can reduce the number of individual data ranges in the selection, and
 | |
|   prevents possible double-counting when iterating over the data points held by the data ranges.
 | |
| 
 | |
|   This method is automatically called when using the addition/subtraction operators. The only case
 | |
|   when \ref simplify is left to the user, is when calling \ref addDataRange, with the parameter \a
 | |
|   simplify explicitly set to false.
 | |
| */
 | |
| void QCPDataSelection::simplify()
 | |
| {
 | |
|   // remove any empty ranges:
 | |
|   for (int i=mDataRanges.size()-1; i>=0; --i)
 | |
|   {
 | |
|     if (mDataRanges.at(i).isEmpty())
 | |
|       mDataRanges.removeAt(i);
 | |
|   }
 | |
|   if (mDataRanges.isEmpty())
 | |
|     return;
 | |
|   
 | |
|   // sort ranges by starting value, ascending:
 | |
|   std::sort(mDataRanges.begin(), mDataRanges.end(), lessThanDataRangeBegin);
 | |
|   
 | |
|   // join overlapping/contiguous ranges:
 | |
|   int i = 1;
 | |
|   while (i < mDataRanges.size())
 | |
|   {
 | |
|     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
 | |
|     {
 | |
|       mDataRanges[i-1].setEnd(qMax(mDataRanges.at(i-1).end(), mDataRanges.at(i).end()));
 | |
|       mDataRanges.removeAt(i);
 | |
|     } else
 | |
|       ++i;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Makes sure this data selection conforms to the specified \a type selection type. Before the type
 | |
|   is enforced, \ref simplify is called.
 | |
|   
 | |
|   Depending on \a type, enforcing means adding new data points that were previously not part of the
 | |
|   selection, or removing data points from the selection. If the current selection already conforms
 | |
|   to \a type, the data selection is not changed.
 | |
|   
 | |
|   \see QCP::SelectionType
 | |
| */
 | |
| void QCPDataSelection::enforceType(QCP::SelectionType type)
 | |
| {
 | |
|   simplify();
 | |
|   switch (type)
 | |
|   {
 | |
|     case QCP::stNone:
 | |
|     {
 | |
|       mDataRanges.clear();
 | |
|       break;
 | |
|     }
 | |
|     case QCP::stWhole:
 | |
|     {
 | |
|       // whole selection isn't defined by data range, so don't change anything (is handled in plottable methods)
 | |
|       break;
 | |
|     }
 | |
|     case QCP::stSingleData:
 | |
|     {
 | |
|       // reduce all data ranges to the single first data point:
 | |
|       if (!mDataRanges.isEmpty())
 | |
|       {
 | |
|         if (mDataRanges.size() > 1)
 | |
|           mDataRanges = QList<QCPDataRange>() << mDataRanges.first();
 | |
|         if (mDataRanges.first().length() > 1)
 | |
|           mDataRanges.first().setEnd(mDataRanges.first().begin()+1);
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     case QCP::stDataRange:
 | |
|     {
 | |
|       mDataRanges = QList<QCPDataRange>() << span();
 | |
|       break;
 | |
|     }
 | |
|     case QCP::stMultipleDataRanges:
 | |
|     {
 | |
|       // this is the selection type that allows all concievable combinations of ranges, so do nothing
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns true if the data selection \a other is contained entirely in this data selection, i.e.
 | |
|   all data point indices that are in \a other are also in this data selection.
 | |
|   
 | |
|   \see QCPDataRange::contains
 | |
| */
 | |
| bool QCPDataSelection::contains(const QCPDataSelection &other) const
 | |
| {
 | |
|   if (other.isEmpty()) return false;
 | |
|   
 | |
|   int otherIndex = 0;
 | |
|   int thisIndex = 0;
 | |
|   while (thisIndex < mDataRanges.size() && otherIndex < other.mDataRanges.size())
 | |
|   {
 | |
|     if (mDataRanges.at(thisIndex).contains(other.mDataRanges.at(otherIndex)))
 | |
|       ++otherIndex;
 | |
|     else
 | |
|       ++thisIndex;
 | |
|   }
 | |
|   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
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns a data selection containing the points which are both in this data selection and in the
 | |
|   data range \a other.
 | |
| 
 | |
|   A common use case is to limit an unknown data selection to the valid range of a data container,
 | |
|   using \ref QCPDataContainer::dataRange as \a other. One can then safely iterate over the returned
 | |
|   data selection without exceeding the data container's bounds.
 | |
| */
 | |
| QCPDataSelection QCPDataSelection::intersection(const QCPDataRange &other) const
 | |
| {
 | |
|   QCPDataSelection result;
 | |
|   for (int i=0; i<mDataRanges.size(); ++i)
 | |
|     result.addDataRange(mDataRanges.at(i).intersection(other), false);
 | |
|   result.simplify();
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns a data selection containing the points which are both in this data selection and in the
 | |
|   data selection \a other.
 | |
| */
 | |
| QCPDataSelection QCPDataSelection::intersection(const QCPDataSelection &other) const
 | |
| {
 | |
|   QCPDataSelection result;
 | |
|   for (int i=0; i<other.dataRangeCount(); ++i)
 | |
|     result += intersection(other.dataRange(i));
 | |
|   result.simplify();
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns a data selection which is the exact inverse of this data selection, with \a outerRange
 | |
|   defining the base range on which to invert. If \a outerRange is smaller than the \ref span of
 | |
|   this data selection, it is expanded accordingly.
 | |
| 
 | |
|   For example, this method can be used to retrieve all unselected segments by setting \a outerRange
 | |
|   to the full data range of the plottable, and calling this method on a data selection holding the
 | |
|   selected segments.
 | |
| */
 | |
| QCPDataSelection QCPDataSelection::inverse(const QCPDataRange &outerRange) const
 | |
| {
 | |
|   if (isEmpty())
 | |
|     return QCPDataSelection(outerRange);
 | |
|   QCPDataRange fullRange = outerRange.expanded(span());
 | |
|   
 | |
|   QCPDataSelection result;
 | |
|   // first unselected segment:
 | |
|   if (mDataRanges.first().begin() != fullRange.begin())
 | |
|     result.addDataRange(QCPDataRange(fullRange.begin(), mDataRanges.first().begin()), false);
 | |
|   // intermediate unselected segments:
 | |
|   for (int i=1; i<mDataRanges.size(); ++i)
 | |
|     result.addDataRange(QCPDataRange(mDataRanges.at(i-1).end(), mDataRanges.at(i).begin()), false);
 | |
|   // last unselected segment:
 | |
|   if (mDataRanges.last().end() != fullRange.end())
 | |
|     result.addDataRange(QCPDataRange(mDataRanges.last().end(), fullRange.end()), false);
 | |
|   result.simplify();
 | |
|   return result;
 | |
| }
 | |
| /* end of 'src/selection.cpp' */
 | |
| 
 | |
| 
 | |
| /* including file 'src/selectionrect.cpp', size 9224                         */
 | |
| /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPSelectionRect
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPSelectionRect
 | |
|   \brief Provides rect/rubber-band data selection and range zoom interaction
 | |
|   
 | |
|   QCPSelectionRect is used by QCustomPlot when the \ref QCustomPlot::setSelectionRectMode is not
 | |
|   \ref QCP::srmNone. When the user drags the mouse across the plot, the current selection rect
 | |
|   instance (\ref QCustomPlot::setSelectionRect) is forwarded these events and makes sure an
 | |
|   according rect shape is drawn. At the begin, during, and after completion of the interaction, it
 | |
|   emits the corresponding signals \ref started, \ref changed, \ref canceled, and \ref accepted.
 | |
|   
 | |
|   The QCustomPlot instance connects own slots to the current selection rect instance, in order to
 | |
|   react to an accepted selection rect interaction accordingly.
 | |
|   
 | |
|   \ref isActive can be used to check whether the selection rect is currently active. An ongoing
 | |
|   selection interaction can be cancelled programmatically via calling \ref cancel at any time.
 | |
|   
 | |
|   The appearance of the selection rect can be controlled via \ref setPen and \ref setBrush.
 | |
| 
 | |
|   If you wish to provide custom behaviour, e.g. a different visual representation of the selection
 | |
|   rect (\ref QCPSelectionRect::draw), you can subclass QCPSelectionRect and pass an instance of
 | |
|   your subclass to \ref QCustomPlot::setSelectionRect.
 | |
| */
 | |
| 
 | |
| /* start of documentation of inline functions */
 | |
| 
 | |
| /*! \fn bool QCPSelectionRect::isActive() const
 | |
|    
 | |
|   Returns true if there is currently a selection going on, i.e. the user has started dragging a
 | |
|   selection rect, but hasn't released the mouse button yet.
 | |
|     
 | |
|   \see cancel
 | |
| */
 | |
| 
 | |
| /* end of documentation of inline functions */
 | |
| /* start documentation of signals */
 | |
| 
 | |
| /*! \fn void QCPSelectionRect::started(QMouseEvent *event);
 | |
|    
 | |
|   This signal is emitted when a selection rect interaction was initiated, i.e. the user just
 | |
|   started dragging the selection rect with the mouse.
 | |
| */
 | |
| 
 | |
| /*! \fn void QCPSelectionRect::changed(const QRect &rect, QMouseEvent *event);
 | |
|   
 | |
|   This signal is emitted while the selection rect interaction is ongoing and the \a rect has
 | |
|   changed its size due to the user moving the mouse.
 | |
|   
 | |
|   Note that \a rect may have a negative width or height, if the selection is being dragged to the
 | |
|   upper or left side of the selection rect origin.
 | |
| */
 | |
| 
 | |
| /*! \fn void QCPSelectionRect::canceled(const QRect &rect, QInputEvent *event);
 | |
|   
 | |
|   This signal is emitted when the selection interaction was cancelled. Note that \a event is 0 if
 | |
|   the selection interaction was cancelled programmatically, by a call to \ref cancel.
 | |
|   
 | |
|   The user may cancel the selection interaction by pressing the escape key. In this case, \a event
 | |
|   holds the respective input event.
 | |
|   
 | |
|   Note that \a rect may have a negative width or height, if the selection is being dragged to the
 | |
|   upper or left side of the selection rect origin.
 | |
| */
 | |
| 
 | |
| /*! \fn void QCPSelectionRect::accepted(const QRect &rect, QMouseEvent *event);
 | |
|   
 | |
|   This signal is emitted when the selection interaction was completed by the user releasing the
 | |
|   mouse button.
 | |
|     
 | |
|   Note that \a rect may have a negative width or height, if the selection is being dragged to the
 | |
|   upper or left side of the selection rect origin.
 | |
| */
 | |
| 
 | |
| /* end documentation of signals */
 | |
| 
 | |
| /*!
 | |
|   Creates a new QCPSelectionRect instance. To make QCustomPlot use the selection rect instance,
 | |
|   pass it to \ref QCustomPlot::setSelectionRect. \a parentPlot should be set to the same
 | |
|   QCustomPlot widget.
 | |
| */
 | |
| QCPSelectionRect::QCPSelectionRect(QCustomPlot *parentPlot) :
 | |
|   QCPLayerable(parentPlot),
 | |
|   mPen(QBrush(Qt::gray), 0, Qt::DashLine),
 | |
|   mBrush(Qt::NoBrush),
 | |
|   mActive(false)
 | |
| {
 | |
| }
 | |
| 
 | |
| QCPSelectionRect::~QCPSelectionRect()
 | |
| {
 | |
|   cancel();
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   A convenience function which returns the coordinate range of the provided \a axis, that this
 | |
|   selection rect currently encompasses.
 | |
| */
 | |
| QCPRange QCPSelectionRect::range(const QCPAxis *axis) const
 | |
| {
 | |
|   if (axis)
 | |
|   {
 | |
|     if (axis->orientation() == Qt::Horizontal)
 | |
|       return QCPRange(axis->pixelToCoord(mRect.left()), axis->pixelToCoord(mRect.left()+mRect.width()));
 | |
|     else
 | |
|       return QCPRange(axis->pixelToCoord(mRect.top()+mRect.height()), axis->pixelToCoord(mRect.top()));
 | |
|   } else
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "called with axis zero";
 | |
|     return QCPRange();
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the pen that will be used to draw the selection rect outline.
 | |
|   
 | |
|   \see setBrush
 | |
| */
 | |
| void QCPSelectionRect::setPen(const QPen &pen)
 | |
| {
 | |
|   mPen = pen;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the brush that will be used to fill the selection rect. By default the selection rect is not
 | |
|   filled, i.e. \a brush is <tt>Qt::NoBrush</tt>.
 | |
|   
 | |
|   \see setPen
 | |
| */
 | |
| void QCPSelectionRect::setBrush(const QBrush &brush)
 | |
| {
 | |
|   mBrush = brush;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   If there is currently a selection interaction going on (\ref isActive), the interaction is
 | |
|   canceled. The selection rect will emit the \ref canceled signal.
 | |
| */
 | |
| void QCPSelectionRect::cancel()
 | |
| {
 | |
|   if (mActive)
 | |
|   {
 | |
|     mActive = false;
 | |
|     emit canceled(mRect, 0);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   This method is called by QCustomPlot to indicate that a selection rect interaction was initiated.
 | |
|   The default implementation sets the selection rect to active, initializes the selection rect
 | |
|   geometry and emits the \ref started signal.
 | |
| */
 | |
| void QCPSelectionRect::startSelection(QMouseEvent *event)
 | |
| {
 | |
|   mActive = true;
 | |
|   mRect = QRect(event->pos(), event->pos());
 | |
|   emit started(event);
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   This method is called by QCustomPlot to indicate that an ongoing selection rect interaction needs
 | |
|   to update its geometry. The default implementation updates the rect and emits the \ref changed
 | |
|   signal.
 | |
| */
 | |
| void QCPSelectionRect::moveSelection(QMouseEvent *event)
 | |
| {
 | |
|   mRect.setBottomRight(event->pos());
 | |
|   emit changed(mRect, event);
 | |
|   layer()->replot();
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   This method is called by QCustomPlot to indicate that an ongoing selection rect interaction has
 | |
|   finished by the user releasing the mouse button. The default implementation deactivates the
 | |
|   selection rect and emits the \ref accepted signal.
 | |
| */
 | |
| void QCPSelectionRect::endSelection(QMouseEvent *event)
 | |
| {
 | |
|   mRect.setBottomRight(event->pos());
 | |
|   mActive = false;
 | |
|   emit accepted(mRect, event);
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   This method is called by QCustomPlot when a key has been pressed by the user while the selection
 | |
|   rect interaction is active. The default implementation allows to \ref cancel the interaction by
 | |
|   hitting the escape key.
 | |
| */
 | |
| void QCPSelectionRect::keyPressEvent(QKeyEvent *event)
 | |
| {
 | |
|   if (event->key() == Qt::Key_Escape && mActive)
 | |
|   {
 | |
|     mActive = false;
 | |
|     emit canceled(mRect, event);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPSelectionRect::applyDefaultAntialiasingHint(QCPPainter *painter) const
 | |
| {
 | |
|   applyAntialiasingHint(painter, mAntialiased, QCP::aeOther);
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   If the selection rect is active (\ref isActive), draws the selection rect defined by \a mRect.
 | |
|   
 | |
|   \seebaseclassmethod
 | |
| */
 | |
| void QCPSelectionRect::draw(QCPPainter *painter)
 | |
| {
 | |
|   if (mActive)
 | |
|   {
 | |
|     painter->setPen(mPen);
 | |
|     painter->setBrush(mBrush);
 | |
|     painter->drawRect(mRect);
 | |
|   }
 | |
| }
 | |
| /* end of 'src/selectionrect.cpp' */
 | |
| 
 | |
| 
 | |
| /* including file 'src/layout.cpp', size 74302                               */
 | |
| /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPMarginGroup
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPMarginGroup
 | |
|   \brief A margin group allows synchronization of margin sides if working with multiple layout elements.
 | |
|   
 | |
|   QCPMarginGroup allows you to tie a margin side of two or more layout elements together, such that
 | |
|   they will all have the same size, based on the largest required margin in the group.
 | |
|   
 | |
|   \n
 | |
|   \image html QCPMarginGroup.png "Demonstration of QCPMarginGroup"
 | |
|   \n
 | |
|   
 | |
|   In certain situations it is desirable that margins at specific sides are synchronized across
 | |
|   layout elements. For example, if one QCPAxisRect is below another one in a grid layout, it will
 | |
|   provide a cleaner look to the user if the left and right margins of the two axis rects are of the
 | |
|   same size. The left axis of the top axis rect will then be at the same horizontal position as the
 | |
|   left axis of the lower axis rect, making them appear aligned. The same applies for the right
 | |
|   axes. This is what QCPMarginGroup makes possible.
 | |
|   
 | |
|   To add/remove a specific side of a layout element to/from a margin group, use the \ref
 | |
|   QCPLayoutElement::setMarginGroup method. To completely break apart the margin group, either call
 | |
|   \ref clear, or just delete the margin group.
 | |
|   
 | |
|   \section QCPMarginGroup-example Example
 | |
|   
 | |
|   First create a margin group:
 | |
|   \snippet documentation/doc-code-snippets/mainwindow.cpp qcpmargingroup-creation-1
 | |
|   Then set this group on the layout element sides:
 | |
|   \snippet documentation/doc-code-snippets/mainwindow.cpp qcpmargingroup-creation-2
 | |
|   Here, we've used the first two axis rects of the plot and synchronized their left margins with
 | |
|   each other and their right margins with each other.
 | |
| */
 | |
| 
 | |
| /* start documentation of inline functions */
 | |
| 
 | |
| /*! \fn QList<QCPLayoutElement*> QCPMarginGroup::elements(QCP::MarginSide side) const
 | |
|   
 | |
|   Returns a list of all layout elements that have their margin \a side associated with this margin
 | |
|   group.
 | |
| */
 | |
| 
 | |
| /* end documentation of inline functions */
 | |
| 
 | |
| /*!
 | |
|   Creates a new QCPMarginGroup instance in \a parentPlot.
 | |
| */
 | |
| QCPMarginGroup::QCPMarginGroup(QCustomPlot *parentPlot) :
 | |
|   QObject(parentPlot),
 | |
|   mParentPlot(parentPlot)
 | |
| {
 | |
|   mChildren.insert(QCP::msLeft, QList<QCPLayoutElement*>());
 | |
|   mChildren.insert(QCP::msRight, QList<QCPLayoutElement*>());
 | |
|   mChildren.insert(QCP::msTop, QList<QCPLayoutElement*>());
 | |
|   mChildren.insert(QCP::msBottom, QList<QCPLayoutElement*>());
 | |
| }
 | |
| 
 | |
| QCPMarginGroup::~QCPMarginGroup()
 | |
| {
 | |
|   clear();
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns whether this margin group is empty. If this function returns true, no layout elements use
 | |
|   this margin group to synchronize margin sides.
 | |
| */
 | |
| bool QCPMarginGroup::isEmpty() const
 | |
| {
 | |
|   QHashIterator<QCP::MarginSide, QList<QCPLayoutElement*> > it(mChildren);
 | |
|   while (it.hasNext())
 | |
|   {
 | |
|     it.next();
 | |
|     if (!it.value().isEmpty())
 | |
|       return false;
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Clears this margin group. The synchronization of the margin sides that use this margin group is
 | |
|   lifted and they will use their individual margin sizes again.
 | |
| */
 | |
| void QCPMarginGroup::clear()
 | |
| {
 | |
|   // make all children remove themselves from this margin group:
 | |
|   QHashIterator<QCP::MarginSide, QList<QCPLayoutElement*> > it(mChildren);
 | |
|   while (it.hasNext())
 | |
|   {
 | |
|     it.next();
 | |
|     const QList<QCPLayoutElement*> elements = it.value();
 | |
|     for (int i=elements.size()-1; i>=0; --i)
 | |
|       elements.at(i)->setMarginGroup(it.key(), 0); // removes itself from mChildren via removeChild
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns the synchronized common margin for \a side. This is the margin value that will be used by
 | |
|   the layout element on the respective side, if it is part of this margin group.
 | |
|   
 | |
|   The common margin is calculated by requesting the automatic margin (\ref
 | |
|   QCPLayoutElement::calculateAutoMargin) of each element associated with \a side in this margin
 | |
|   group, and choosing the largest returned value. (QCPLayoutElement::minimumMargins is taken into
 | |
|   account, too.)
 | |
| */
 | |
| int QCPMarginGroup::commonMargin(QCP::MarginSide side) const
 | |
| {
 | |
|   // query all automatic margins of the layout elements in this margin group side and find maximum:
 | |
|   int result = 0;
 | |
|   const QList<QCPLayoutElement*> elements = mChildren.value(side);
 | |
|   for (int i=0; i<elements.size(); ++i)
 | |
|   {
 | |
|     if (!elements.at(i)->autoMargins().testFlag(side))
 | |
|       continue;
 | |
|     int m = qMax(elements.at(i)->calculateAutoMargin(side), QCP::getMarginValue(elements.at(i)->minimumMargins(), side));
 | |
|     if (m > result)
 | |
|       result = m;
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Adds \a element to the internal list of child elements, for the margin \a side.
 | |
|   
 | |
|   This function does not modify the margin group property of \a element.
 | |
| */
 | |
| void QCPMarginGroup::addChild(QCP::MarginSide side, QCPLayoutElement *element)
 | |
| {
 | |
|   if (!mChildren[side].contains(element))
 | |
|     mChildren[side].append(element);
 | |
|   else
 | |
|     qDebug() << Q_FUNC_INFO << "element is already child of this margin group side" << reinterpret_cast<quintptr>(element);
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Removes \a element from the internal list of child elements, for the margin \a side.
 | |
|   
 | |
|   This function does not modify the margin group property of \a element.
 | |
| */
 | |
| void QCPMarginGroup::removeChild(QCP::MarginSide side, QCPLayoutElement *element)
 | |
| {
 | |
|   if (!mChildren[side].removeOne(element))
 | |
|     qDebug() << Q_FUNC_INFO << "element is not child of this margin group side" << reinterpret_cast<quintptr>(element);
 | |
| }
 | |
| 
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPLayoutElement
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPLayoutElement
 | |
|   \brief The abstract base class for all objects that form \ref thelayoutsystem "the layout system".
 | |
|   
 | |
|   This is an abstract base class. As such, it can't be instantiated directly, rather use one of its subclasses.
 | |
|   
 | |
|   A Layout element is a rectangular object which can be placed in layouts. It has an outer rect
 | |
|   (QCPLayoutElement::outerRect) and an inner rect (\ref QCPLayoutElement::rect). The difference
 | |
|   between outer and inner rect is called its margin. The margin can either be set to automatic or
 | |
|   manual (\ref setAutoMargins) on a per-side basis. If a side is set to manual, that margin can be
 | |
|   set explicitly with \ref setMargins and will stay fixed at that value. If it's set to automatic,
 | |
|   the layout element subclass will control the value itself (via \ref calculateAutoMargin).
 | |
|   
 | |
|   Layout elements can be placed in layouts (base class QCPLayout) like QCPLayoutGrid. The top level
 | |
|   layout is reachable via \ref QCustomPlot::plotLayout, and is a \ref QCPLayoutGrid. Since \ref
 | |
|   QCPLayout itself derives from \ref QCPLayoutElement, layouts can be nested.
 | |
|   
 | |
|   Thus in QCustomPlot one can divide layout elements into two categories: The ones that are
 | |
|   invisible by themselves, because they don't draw anything. Their only purpose is to manage the
 | |
|   position and size of other layout elements. This category of layout elements usually use
 | |
|   QCPLayout as base class. Then there is the category of layout elements which actually draw
 | |
|   something. For example, QCPAxisRect, QCPLegend and QCPTextElement are of this category. This does
 | |
|   not necessarily mean that the latter category can't have child layout elements. QCPLegend for
 | |
|   instance, actually derives from QCPLayoutGrid and the individual legend items are child layout
 | |
|   elements in the grid layout.
 | |
| */
 | |
| 
 | |
| /* start documentation of inline functions */
 | |
| 
 | |
| /*! \fn QCPLayout *QCPLayoutElement::layout() const
 | |
|   
 | |
|   Returns the parent layout of this layout element.
 | |
| */
 | |
| 
 | |
| /*! \fn QRect QCPLayoutElement::rect() const
 | |
|   
 | |
|   Returns the inner rect of this layout element. The inner rect is the outer rect (\ref
 | |
|   setOuterRect) shrinked by the margins (\ref setMargins, \ref setAutoMargins).
 | |
|   
 | |
|   In some cases, the area between outer and inner rect is left blank. In other cases the margin
 | |
|   area is used to display peripheral graphics while the main content is in the inner rect. This is
 | |
|   where automatic margin calculation becomes interesting because it allows the layout element to
 | |
|   adapt the margins to the peripheral graphics it wants to draw. For example, \ref QCPAxisRect
 | |
|   draws the axis labels and tick labels in the margin area, thus needs to adjust the margins (if
 | |
|   \ref setAutoMargins is enabled) according to the space required by the labels of the axes.
 | |
| */
 | |
| 
 | |
| /* end documentation of inline functions */
 | |
| 
 | |
| /*!
 | |
|   Creates an instance of QCPLayoutElement and sets default values.
 | |
| */
 | |
| QCPLayoutElement::QCPLayoutElement(QCustomPlot *parentPlot) :
 | |
|   QCPLayerable(parentPlot), // parenthood is changed as soon as layout element gets inserted into a layout (except for top level layout)
 | |
|   mParentLayout(0),
 | |
|   mMinimumSize(),
 | |
|   mMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX),
 | |
|   mRect(0, 0, 0, 0),
 | |
|   mOuterRect(0, 0, 0, 0),
 | |
|   mMargins(0, 0, 0, 0),
 | |
|   mMinimumMargins(0, 0, 0, 0),
 | |
|   mAutoMargins(QCP::msAll)
 | |
| {
 | |
| }
 | |
| 
 | |
| QCPLayoutElement::~QCPLayoutElement()
 | |
| {
 | |
|   setMarginGroup(QCP::msAll, 0); // unregister at margin groups, if there are any
 | |
|   // unregister at layout:
 | |
|   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
 | |
|     mParentLayout->take(this);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the outer rect of this layout element. If the layout element is inside a layout, the layout
 | |
|   sets the position and size of this layout element using this function.
 | |
|   
 | |
|   Calling this function externally has no effect, since the layout will overwrite any changes to
 | |
|   the outer rect upon the next replot.
 | |
|   
 | |
|   The layout element will adapt its inner \ref rect by applying the margins inward to the outer rect.
 | |
|   
 | |
|   \see rect
 | |
| */
 | |
| void QCPLayoutElement::setOuterRect(const QRect &rect)
 | |
| {
 | |
|   if (mOuterRect != rect)
 | |
|   {
 | |
|     mOuterRect = rect;
 | |
|     mRect = mOuterRect.adjusted(mMargins.left(), mMargins.top(), -mMargins.right(), -mMargins.bottom());
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the margins of this layout element. If \ref setAutoMargins is disabled for some or all
 | |
|   sides, this function is used to manually set the margin on those sides. Sides that are still set
 | |
|   to be handled automatically are ignored and may have any value in \a margins.
 | |
|   
 | |
|   The margin is the distance between the outer rect (controlled by the parent layout via \ref
 | |
|   setOuterRect) and the inner \ref rect (which usually contains the main content of this layout
 | |
|   element).
 | |
|   
 | |
|   \see setAutoMargins
 | |
| */
 | |
| void QCPLayoutElement::setMargins(const QMargins &margins)
 | |
| {
 | |
|   if (mMargins != margins)
 | |
|   {
 | |
|     mMargins = margins;
 | |
|     mRect = mOuterRect.adjusted(mMargins.left(), mMargins.top(), -mMargins.right(), -mMargins.bottom());
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   If \ref setAutoMargins is enabled on some or all margins, this function is used to provide
 | |
|   minimum values for those margins.
 | |
|   
 | |
|   The minimum values are not enforced on margin sides that were set to be under manual control via
 | |
|   \ref setAutoMargins.
 | |
|   
 | |
|   \see setAutoMargins
 | |
| */
 | |
| void QCPLayoutElement::setMinimumMargins(const QMargins &margins)
 | |
| {
 | |
|   if (mMinimumMargins != margins)
 | |
|   {
 | |
|     mMinimumMargins = margins;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets on which sides the margin shall be calculated automatically. If a side is calculated
 | |
|   automatically, a minimum margin value may be provided with \ref setMinimumMargins. If a side is
 | |
|   set to be controlled manually, the value may be specified with \ref setMargins.
 | |
|   
 | |
|   Margin sides that are under automatic control may participate in a \ref QCPMarginGroup (see \ref
 | |
|   setMarginGroup), to synchronize (align) it with other layout elements in the plot.
 | |
|   
 | |
|   \see setMinimumMargins, setMargins, QCP::MarginSide
 | |
| */
 | |
| void QCPLayoutElement::setAutoMargins(QCP::MarginSides sides)
 | |
| {
 | |
|   mAutoMargins = sides;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the minimum size for the inner \ref rect of this layout element. A parent layout tries to
 | |
|   respect the \a size here by changing row/column sizes in the layout accordingly.
 | |
|   
 | |
|   If the parent layout size is not sufficient to satisfy all minimum size constraints of its child
 | |
|   layout elements, the layout may set a size that is actually smaller than \a size. QCustomPlot
 | |
|   propagates the layout's size constraints to the outside by setting its own minimum QWidget size
 | |
|   accordingly, so violations of \a size should be exceptions.
 | |
| */
 | |
| void QCPLayoutElement::setMinimumSize(const QSize &size)
 | |
| {
 | |
|   if (mMinimumSize != size)
 | |
|   {
 | |
|     mMinimumSize = size;
 | |
|     if (mParentLayout)
 | |
|       mParentLayout->sizeConstraintsChanged();
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Sets the minimum size for the inner \ref rect of this layout element.
 | |
| */
 | |
| void QCPLayoutElement::setMinimumSize(int width, int height)
 | |
| {
 | |
|   setMinimumSize(QSize(width, height));
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the maximum size for the inner \ref rect of this layout element. A parent layout tries to
 | |
|   respect the \a size here by changing row/column sizes in the layout accordingly.
 | |
| */
 | |
| void QCPLayoutElement::setMaximumSize(const QSize &size)
 | |
| {
 | |
|   if (mMaximumSize != size)
 | |
|   {
 | |
|     mMaximumSize = size;
 | |
|     if (mParentLayout)
 | |
|       mParentLayout->sizeConstraintsChanged();
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Sets the maximum size for the inner \ref rect of this layout element.
 | |
| */
 | |
| void QCPLayoutElement::setMaximumSize(int width, int height)
 | |
| {
 | |
|   setMaximumSize(QSize(width, height));
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the margin \a group of the specified margin \a sides.
 | |
|   
 | |
|   Margin groups allow synchronizing specified margins across layout elements, see the documentation
 | |
|   of \ref QCPMarginGroup.
 | |
|   
 | |
|   To unset the margin group of \a sides, set \a group to 0.
 | |
|   
 | |
|   Note that margin groups only work for margin sides that are set to automatic (\ref
 | |
|   setAutoMargins).
 | |
|   
 | |
|   \see QCP::MarginSide
 | |
| */
 | |
| void QCPLayoutElement::setMarginGroup(QCP::MarginSides sides, QCPMarginGroup *group)
 | |
| {
 | |
|   QVector<QCP::MarginSide> sideVector;
 | |
|   if (sides.testFlag(QCP::msLeft)) sideVector.append(QCP::msLeft);
 | |
|   if (sides.testFlag(QCP::msRight)) sideVector.append(QCP::msRight);
 | |
|   if (sides.testFlag(QCP::msTop)) sideVector.append(QCP::msTop);
 | |
|   if (sides.testFlag(QCP::msBottom)) sideVector.append(QCP::msBottom);
 | |
|   
 | |
|   for (int i=0; i<sideVector.size(); ++i)
 | |
|   {
 | |
|     QCP::MarginSide side = sideVector.at(i);
 | |
|     if (marginGroup(side) != group)
 | |
|     {
 | |
|       QCPMarginGroup *oldGroup = marginGroup(side);
 | |
|       if (oldGroup) // unregister at old group
 | |
|         oldGroup->removeChild(side, this);
 | |
|       
 | |
|       if (!group) // if setting to 0, remove hash entry. Else set hash entry to new group and register there
 | |
|       {
 | |
|         mMarginGroups.remove(side);
 | |
|       } else // setting to a new group
 | |
|       {
 | |
|         mMarginGroups[side] = group;
 | |
|         group->addChild(side, this);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Updates the layout element and sub-elements. This function is automatically called before every
 | |
|   replot by the parent layout element. It is called multiple times, once for every \ref
 | |
|   UpdatePhase. The phases are run through in the order of the enum values. For details about what
 | |
|   happens at the different phases, see the documentation of \ref UpdatePhase.
 | |
|   
 | |
|   Layout elements that have child elements should call the \ref update method of their child
 | |
|   elements, and pass the current \a phase unchanged.
 | |
|   
 | |
|   The default implementation executes the automatic margin mechanism in the \ref upMargins phase.
 | |
|   Subclasses should make sure to call the base class implementation.
 | |
| */
 | |
| void QCPLayoutElement::update(UpdatePhase phase)
 | |
| {
 | |
|   if (phase == upMargins)
 | |
|   {
 | |
|     if (mAutoMargins != QCP::msNone)
 | |
|     {
 | |
|       // set the margins of this layout element according to automatic margin calculation, either directly or via a margin group:
 | |
|       QMargins newMargins = mMargins;
 | |
|       QList<QCP::MarginSide> allMarginSides = QList<QCP::MarginSide>() << QCP::msLeft << QCP::msRight << QCP::msTop << QCP::msBottom;
 | |
|       foreach (QCP::MarginSide side, allMarginSides)
 | |
|       {
 | |
|         if (mAutoMargins.testFlag(side)) // this side's margin shall be calculated automatically
 | |
|         {
 | |
|           if (mMarginGroups.contains(side))
 | |
|             QCP::setMarginValue(newMargins, side, mMarginGroups[side]->commonMargin(side)); // this side is part of a margin group, so get the margin value from that group
 | |
|           else
 | |
|             QCP::setMarginValue(newMargins, side, calculateAutoMargin(side)); // this side is not part of a group, so calculate the value directly
 | |
|           // apply minimum margin restrictions:
 | |
|           if (QCP::getMarginValue(newMargins, side) < QCP::getMarginValue(mMinimumMargins, side))
 | |
|             QCP::setMarginValue(newMargins, side, QCP::getMarginValue(mMinimumMargins, side));
 | |
|         }
 | |
|       }
 | |
|       setMargins(newMargins);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the minimum size this layout element (the inner \ref rect) may be compressed to.
 | |
|   
 | |
|   if a minimum size (\ref setMinimumSize) was not set manually, parent layouts consult this
 | |
|   function to determine the minimum allowed size of this layout element. (A manual minimum size is
 | |
|   considered set if it is non-zero.)
 | |
| */
 | |
| QSize QCPLayoutElement::minimumSizeHint() const
 | |
| {
 | |
|   return mMinimumSize;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the maximum size this layout element (the inner \ref rect) may be expanded to.
 | |
|   
 | |
|   if a maximum size (\ref setMaximumSize) was not set manually, parent layouts consult this
 | |
|   function to determine the maximum allowed size of this layout element. (A manual maximum size is
 | |
|   considered set if it is smaller than Qt's QWIDGETSIZE_MAX.)
 | |
| */
 | |
| QSize QCPLayoutElement::maximumSizeHint() const
 | |
| {
 | |
|   return mMaximumSize;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns a list of all child elements in this layout element. If \a recursive is true, all
 | |
|   sub-child elements are included in the list, too.
 | |
|   
 | |
|   \warning There may be entries with value 0 in the returned list. (For example, QCPLayoutGrid may have
 | |
|   empty cells which yield 0 at the respective index.)
 | |
| */
 | |
| QList<QCPLayoutElement*> QCPLayoutElement::elements(bool recursive) const
 | |
| {
 | |
|   Q_UNUSED(recursive)
 | |
|   return QList<QCPLayoutElement*>();
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Layout elements are sensitive to events inside their outer rect. If \a pos is within the outer
 | |
|   rect, this method returns a value corresponding to 0.99 times the parent plot's selection
 | |
|   tolerance. However, layout elements are not selectable by default. So if \a onlySelectable is
 | |
|   true, -1.0 is returned.
 | |
|   
 | |
|   See \ref QCPLayerable::selectTest for a general explanation of this virtual method.
 | |
|   
 | |
|   QCPLayoutElement subclasses may reimplement this method to provide more specific selection test
 | |
|   behaviour.
 | |
| */
 | |
| double QCPLayoutElement::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
 | |
| {
 | |
|   Q_UNUSED(details)
 | |
|   
 | |
|   if (onlySelectable)
 | |
|     return -1;
 | |
|   
 | |
|   if (QRectF(mOuterRect).contains(pos))
 | |
|   {
 | |
|     if (mParentPlot)
 | |
|       return mParentPlot->selectionTolerance()*0.99;
 | |
|     else
 | |
|     {
 | |
|       qDebug() << Q_FUNC_INFO << "parent plot not defined";
 | |
|       return -1;
 | |
|     }
 | |
|   } else
 | |
|     return -1;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   propagates the parent plot initialization to all child elements, by calling \ref
 | |
|   QCPLayerable::initializeParentPlot on them.
 | |
| */
 | |
| void QCPLayoutElement::parentPlotInitialized(QCustomPlot *parentPlot)
 | |
| {
 | |
|   foreach (QCPLayoutElement* el, elements(false))
 | |
|   {
 | |
|     if (!el->parentPlot())
 | |
|       el->initializeParentPlot(parentPlot);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns the margin size for this \a side. It is used if automatic margins is enabled for this \a
 | |
|   side (see \ref setAutoMargins). If a minimum margin was set with \ref setMinimumMargins, the
 | |
|   returned value will not be smaller than the specified minimum margin.
 | |
|   
 | |
|   The default implementation just returns the respective manual margin (\ref setMargins) or the
 | |
|   minimum margin, whichever is larger.
 | |
| */
 | |
| int QCPLayoutElement::calculateAutoMargin(QCP::MarginSide side)
 | |
| {
 | |
|   return qMax(QCP::getMarginValue(mMargins, side), QCP::getMarginValue(mMinimumMargins, side));
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   This virtual method is called when this layout element was moved to a different QCPLayout, or
 | |
|   when this layout element has changed its logical position (e.g. row and/or column) within the
 | |
|   same QCPLayout. Subclasses may use this to react accordingly.
 | |
|   
 | |
|   Since this method is called after the completion of the move, you can access the new parent
 | |
|   layout via \ref layout().
 | |
|   
 | |
|   The default implementation does nothing.
 | |
| */
 | |
| void QCPLayoutElement::layoutChanged()
 | |
| {
 | |
| }
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPLayout
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPLayout
 | |
|   \brief The abstract base class for layouts
 | |
|   
 | |
|   This is an abstract base class for layout elements whose main purpose is to define the position
 | |
|   and size of other child layout elements. In most cases, layouts don't draw anything themselves
 | |
|   (but there are exceptions to this, e.g. QCPLegend).
 | |
|   
 | |
|   QCPLayout derives from QCPLayoutElement, and thus can itself be nested in other layouts.
 | |
|   
 | |
|   QCPLayout introduces a common interface for accessing and manipulating the child elements. Those
 | |
|   functions are most notably \ref elementCount, \ref elementAt, \ref takeAt, \ref take, \ref
 | |
|   simplify, \ref removeAt, \ref remove and \ref clear. Individual subclasses may add more functions
 | |
|   to this interface which are more specialized to the form of the layout. For example, \ref
 | |
|   QCPLayoutGrid adds functions that take row and column indices to access cells of the layout grid
 | |
|   more conveniently.
 | |
|   
 | |
|   Since this is an abstract base class, you can't instantiate it directly. Rather use one of its
 | |
|   subclasses like QCPLayoutGrid or QCPLayoutInset.
 | |
|   
 | |
|   For a general introduction to the layout system, see the dedicated documentation page \ref
 | |
|   thelayoutsystem "The Layout System".
 | |
| */
 | |
| 
 | |
| /* start documentation of pure virtual functions */
 | |
| 
 | |
| /*! \fn virtual int QCPLayout::elementCount() const = 0
 | |
|   
 | |
|   Returns the number of elements/cells in the layout.
 | |
|   
 | |
|   \see elements, elementAt
 | |
| */
 | |
| 
 | |
| /*! \fn virtual QCPLayoutElement* QCPLayout::elementAt(int index) const = 0
 | |
|   
 | |
|   Returns the element in the cell with the given \a index. If \a index is invalid, returns 0.
 | |
|   
 | |
|   Note that even if \a index is valid, the respective cell may be empty in some layouts (e.g.
 | |
|   QCPLayoutGrid), so this function may return 0 in those cases. You may use this function to check
 | |
|   whether a cell is empty or not.
 | |
|   
 | |
|   \see elements, elementCount, takeAt
 | |
| */
 | |
| 
 | |
| /*! \fn virtual QCPLayoutElement* QCPLayout::takeAt(int index) = 0
 | |
|   
 | |
|   Removes the element with the given \a index from the layout and returns it.
 | |
|   
 | |
|   If the \a index is invalid or the cell with that index is empty, returns 0.
 | |
|   
 | |
|   Note that some layouts don't remove the respective cell right away but leave an empty cell after
 | |
|   successful removal of the layout element. To collapse empty cells, use \ref simplify.
 | |
|   
 | |
|   \see elementAt, take
 | |
| */
 | |
| 
 | |
| /*! \fn virtual bool QCPLayout::take(QCPLayoutElement* element) = 0
 | |
|   
 | |
|   Removes the specified \a element from the layout and returns true on success.
 | |
|   
 | |
|   If the \a element isn't in this layout, returns false.
 | |
|   
 | |
|   Note that some layouts don't remove the respective cell right away but leave an empty cell after
 | |
|   successful removal of the layout element. To collapse empty cells, use \ref simplify.
 | |
|   
 | |
|   \see takeAt
 | |
| */
 | |
| 
 | |
| /* end documentation of pure virtual functions */
 | |
| 
 | |
| /*!
 | |
|   Creates an instance of QCPLayout and sets default values. Note that since QCPLayout
 | |
|   is an abstract base class, it can't be instantiated directly.
 | |
| */
 | |
| QCPLayout::QCPLayout()
 | |
| {
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   First calls the QCPLayoutElement::update base class implementation to update the margins on this
 | |
|   layout.
 | |
|   
 | |
|   Then calls \ref updateLayout which subclasses reimplement to reposition and resize their cells.
 | |
|   
 | |
|   Finally, \ref update is called on all child elements.
 | |
| */
 | |
| void QCPLayout::update(UpdatePhase phase)
 | |
| {
 | |
|   QCPLayoutElement::update(phase);
 | |
|   
 | |
|   // set child element rects according to layout:
 | |
|   if (phase == upLayout)
 | |
|     updateLayout();
 | |
|   
 | |
|   // propagate update call to child elements:
 | |
|   const int elCount = elementCount();
 | |
|   for (int i=0; i<elCount; ++i)
 | |
|   {
 | |
|     if (QCPLayoutElement *el = elementAt(i))
 | |
|       el->update(phase);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| QList<QCPLayoutElement*> QCPLayout::elements(bool recursive) const
 | |
| {
 | |
|   const int c = elementCount();
 | |
|   QList<QCPLayoutElement*> result;
 | |
| #if QT_VERSION >= QT_VERSION_CHECK(4, 7, 0)
 | |
|   result.reserve(c);
 | |
| #endif
 | |
|   for (int i=0; i<c; ++i)
 | |
|     result.append(elementAt(i));
 | |
|   if (recursive)
 | |
|   {
 | |
|     for (int i=0; i<c; ++i)
 | |
|     {
 | |
|       if (result.at(i))
 | |
|         result << result.at(i)->elements(recursive);
 | |
|     }
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Simplifies the layout by collapsing empty cells. The exact behavior depends on subclasses, the
 | |
|   default implementation does nothing.
 | |
|   
 | |
|   Not all layouts need simplification. For example, QCPLayoutInset doesn't use explicit
 | |
|   simplification while QCPLayoutGrid does.
 | |
| */
 | |
| void QCPLayout::simplify()
 | |
| {
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Removes and deletes the element at the provided \a index. Returns true on success. If \a index is
 | |
|   invalid or points to an empty cell, returns false.
 | |
|   
 | |
|   This function internally uses \ref takeAt to remove the element from the layout and then deletes
 | |
|   the returned element. Note that some layouts don't remove the respective cell right away but leave an
 | |
|   empty cell after successful removal of the layout element. To collapse empty cells, use \ref
 | |
|   simplify.
 | |
|   
 | |
|   \see remove, takeAt
 | |
| */
 | |
| bool QCPLayout::removeAt(int index)
 | |
| {
 | |
|   if (QCPLayoutElement *el = takeAt(index))
 | |
|   {
 | |
|     delete el;
 | |
|     return true;
 | |
|   } else
 | |
|     return false;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Removes and deletes the provided \a element. Returns true on success. If \a element is not in the
 | |
|   layout, returns false.
 | |
|   
 | |
|   This function internally uses \ref takeAt to remove the element from the layout and then deletes
 | |
|   the element. Note that some layouts don't remove the respective cell right away but leave an
 | |
|   empty cell after successful removal of the layout element. To collapse empty cells, use \ref
 | |
|   simplify.
 | |
|   
 | |
|   \see removeAt, take
 | |
| */
 | |
| bool QCPLayout::remove(QCPLayoutElement *element)
 | |
| {
 | |
|   if (take(element))
 | |
|   {
 | |
|     delete element;
 | |
|     return true;
 | |
|   } else
 | |
|     return false;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Removes and deletes all layout elements in this layout. Finally calls \ref simplify to make sure
 | |
|   all empty cells are collapsed.
 | |
|   
 | |
|   \see remove, removeAt
 | |
| */
 | |
| void QCPLayout::clear()
 | |
| {
 | |
|   for (int i=elementCount()-1; i>=0; --i)
 | |
|   {
 | |
|     if (elementAt(i))
 | |
|       removeAt(i);
 | |
|   }
 | |
|   simplify();
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Subclasses call this method to report changed (minimum/maximum) size constraints.
 | |
|   
 | |
|   If the parent of this layout is again a QCPLayout, forwards the call to the parent's \ref
 | |
|   sizeConstraintsChanged. If the parent is a QWidget (i.e. is the \ref QCustomPlot::plotLayout of
 | |
|   QCustomPlot), calls QWidget::updateGeometry, so if the QCustomPlot widget is inside a Qt QLayout,
 | |
|   it may update itself and resize cells accordingly.
 | |
| */
 | |
| void QCPLayout::sizeConstraintsChanged() const
 | |
| {
 | |
|   if (QWidget *w = qobject_cast<QWidget*>(parent()))
 | |
|     w->updateGeometry();
 | |
|   else if (QCPLayout *l = qobject_cast<QCPLayout*>(parent()))
 | |
|     l->sizeConstraintsChanged();
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Subclasses reimplement this method to update the position and sizes of the child elements/cells
 | |
|   via calling their \ref QCPLayoutElement::setOuterRect. The default implementation does nothing.
 | |
|   
 | |
|   The geometry used as a reference is the inner \ref rect of this layout. Child elements should stay
 | |
|   within that rect.
 | |
|   
 | |
|   \ref getSectionSizes may help with the reimplementation of this function.
 | |
|   
 | |
|   \see update
 | |
| */
 | |
| void QCPLayout::updateLayout()
 | |
| {
 | |
| }
 | |
| 
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Associates \a el with this layout. This is done by setting the \ref QCPLayoutElement::layout, the
 | |
|   \ref QCPLayerable::parentLayerable and the QObject parent to this layout.
 | |
|   
 | |
|   Further, if \a el didn't previously have a parent plot, calls \ref
 | |
|   QCPLayerable::initializeParentPlot on \a el to set the paret plot.
 | |
|   
 | |
|   This method is used by subclass specific methods that add elements to the layout. Note that this
 | |
|   method only changes properties in \a el. The removal from the old layout and the insertion into
 | |
|   the new layout must be done additionally.
 | |
| */
 | |
| void QCPLayout::adoptElement(QCPLayoutElement *el)
 | |
| {
 | |
|   if (el)
 | |
|   {
 | |
|     el->mParentLayout = this;
 | |
|     el->setParentLayerable(this);
 | |
|     el->setParent(this);
 | |
|     if (!el->parentPlot())
 | |
|       el->initializeParentPlot(mParentPlot);
 | |
|     el->layoutChanged();
 | |
|   } else
 | |
|     qDebug() << Q_FUNC_INFO << "Null element passed";
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Disassociates \a el from this layout. This is done by setting the \ref QCPLayoutElement::layout
 | |
|   and the \ref QCPLayerable::parentLayerable to zero. The QObject parent is set to the parent
 | |
|   QCustomPlot.
 | |
|   
 | |
|   This method is used by subclass specific methods that remove elements from the layout (e.g. \ref
 | |
|   take or \ref takeAt). Note that this method only changes properties in \a el. The removal from
 | |
|   the old layout must be done additionally.
 | |
| */
 | |
| void QCPLayout::releaseElement(QCPLayoutElement *el)
 | |
| {
 | |
|   if (el)
 | |
|   {
 | |
|     el->mParentLayout = 0;
 | |
|     el->setParentLayerable(0);
 | |
|     el->setParent(mParentPlot);
 | |
|     // Note: Don't initializeParentPlot(0) here, because layout element will stay in same parent plot
 | |
|   } else
 | |
|     qDebug() << Q_FUNC_INFO << "Null element passed";
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   This is a helper function for the implementation of \ref updateLayout in subclasses.
 | |
|   
 | |
|   It calculates the sizes of one-dimensional sections with provided constraints on maximum section
 | |
|   sizes, minimum section sizes, relative stretch factors and the final total size of all sections.
 | |
|   
 | |
|   The QVector entries refer to the sections. Thus all QVectors must have the same size.
 | |
|   
 | |
|   \a maxSizes gives the maximum allowed size of each section. If there shall be no maximum size
 | |
|   imposed, set all vector values to Qt's QWIDGETSIZE_MAX.
 | |
|   
 | |
|   \a minSizes gives the minimum allowed size of each section. If there shall be no minimum size
 | |
|   imposed, set all vector values to zero. If the \a minSizes entries add up to a value greater than
 | |
|   \a totalSize, sections will be scaled smaller than the proposed minimum sizes. (In other words,
 | |
|   not exceeding the allowed total size is taken to be more important than not going below minimum
 | |
|   section sizes.)
 | |
|   
 | |
|   \a stretchFactors give the relative proportions of the sections to each other. If all sections
 | |
|   shall be scaled equally, set all values equal. If the first section shall be double the size of
 | |
|   each individual other section, set the first number of \a stretchFactors to double the value of
 | |
|   the other individual values (e.g. {2, 1, 1, 1}).
 | |
|   
 | |
|   \a totalSize is the value that the final section sizes will add up to. Due to rounding, the
 | |
|   actual sum may differ slightly. If you want the section sizes to sum up to exactly that value,
 | |
|   you could distribute the remaining difference on the sections.
 | |
|   
 | |
|   The return value is a QVector containing the section sizes.
 | |
| */
 | |
| QVector<int> QCPLayout::getSectionSizes(QVector<int> maxSizes, QVector<int> minSizes, QVector<double> stretchFactors, int totalSize) const
 | |
| {
 | |
|   if (maxSizes.size() != minSizes.size() || minSizes.size() != stretchFactors.size())
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "Passed vector sizes aren't equal:" << maxSizes << minSizes << stretchFactors;
 | |
|     return QVector<int>();
 | |
|   }
 | |
|   if (stretchFactors.isEmpty())
 | |
|     return QVector<int>();
 | |
|   int sectionCount = stretchFactors.size();
 | |
|   QVector<double> sectionSizes(sectionCount);
 | |
|   // if provided total size is forced smaller than total minimum size, ignore minimum sizes (squeeze sections):
 | |
|   int minSizeSum = 0;
 | |
|   for (int i=0; i<sectionCount; ++i)
 | |
|     minSizeSum += minSizes.at(i);
 | |
|   if (totalSize < minSizeSum)
 | |
|   {
 | |
|     // new stretch factors are minimum sizes and minimum sizes are set to zero:
 | |
|     for (int i=0; i<sectionCount; ++i)
 | |
|     {
 | |
|       stretchFactors[i] = minSizes.at(i);
 | |
|       minSizes[i] = 0;
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   QList<int> minimumLockedSections;
 | |
|   QList<int> unfinishedSections;
 | |
|   for (int i=0; i<sectionCount; ++i)
 | |
|     unfinishedSections.append(i);
 | |
|   double freeSize = totalSize;
 | |
|   
 | |
|   int outerIterations = 0;
 | |
|   while (!unfinishedSections.isEmpty() && outerIterations < sectionCount*2) // the iteration check ist just a failsafe in case something really strange happens
 | |
|   {
 | |
|     ++outerIterations;
 | |
|     int innerIterations = 0;
 | |
|     while (!unfinishedSections.isEmpty() && innerIterations < sectionCount*2) // the iteration check ist just a failsafe in case something really strange happens
 | |
|     {
 | |
|       ++innerIterations;
 | |
|       // find section that hits its maximum next:
 | |
|       int nextId = -1;
 | |
|       double nextMax = 1e12;
 | |
|       for (int i=0; i<unfinishedSections.size(); ++i)
 | |
|       {
 | |
|         int secId = unfinishedSections.at(i);
 | |
|         double hitsMaxAt = (maxSizes.at(secId)-sectionSizes.at(secId))/stretchFactors.at(secId);
 | |
|         if (hitsMaxAt < nextMax)
 | |
|         {
 | |
|           nextMax = hitsMaxAt;
 | |
|           nextId = secId;
 | |
|         }
 | |
|       }
 | |
|       // 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
 | |
|       // actually hits its maximum, without exceeding the total size when we add up all sections)
 | |
|       double stretchFactorSum = 0;
 | |
|       for (int i=0; i<unfinishedSections.size(); ++i)
 | |
|         stretchFactorSum += stretchFactors.at(unfinishedSections.at(i));
 | |
|       double nextMaxLimit = freeSize/stretchFactorSum;
 | |
|       if (nextMax < nextMaxLimit) // next maximum is actually hit, move forward to that point and fix the size of that section
 | |
|       {
 | |
|         for (int i=0; i<unfinishedSections.size(); ++i)
 | |
|         {
 | |
|           sectionSizes[unfinishedSections.at(i)] += nextMax*stretchFactors.at(unfinishedSections.at(i)); // increment all sections
 | |
|           freeSize -= nextMax*stretchFactors.at(unfinishedSections.at(i));
 | |
|         }
 | |
|         unfinishedSections.removeOne(nextId); // exclude the section that is now at maximum from further changes
 | |
|       } else // next maximum isn't hit, just distribute rest of free space on remaining sections
 | |
|       {
 | |
|         for (int i=0; i<unfinishedSections.size(); ++i)
 | |
|           sectionSizes[unfinishedSections.at(i)] += nextMaxLimit*stretchFactors.at(unfinishedSections.at(i)); // increment all sections
 | |
|         unfinishedSections.clear();
 | |
|       }
 | |
|     }
 | |
|     if (innerIterations == sectionCount*2)
 | |
|       qDebug() << Q_FUNC_INFO << "Exceeded maximum expected inner iteration count, layouting aborted. Input was:" << maxSizes << minSizes << stretchFactors << totalSize;
 | |
|     
 | |
|     // now check whether the resulting section sizes violate minimum restrictions:
 | |
|     bool foundMinimumViolation = false;
 | |
|     for (int i=0; i<sectionSizes.size(); ++i)
 | |
|     {
 | |
|       if (minimumLockedSections.contains(i))
 | |
|         continue;
 | |
|       if (sectionSizes.at(i) < minSizes.at(i)) // section violates minimum
 | |
|       {
 | |
|         sectionSizes[i] = minSizes.at(i); // set it to minimum
 | |
|         foundMinimumViolation = true; // make sure we repeat the whole optimization process
 | |
|         minimumLockedSections.append(i);
 | |
|       }
 | |
|     }
 | |
|     if (foundMinimumViolation)
 | |
|     {
 | |
|       freeSize = totalSize;
 | |
|       for (int i=0; i<sectionCount; ++i)
 | |
|       {
 | |
|         if (!minimumLockedSections.contains(i)) // only put sections that haven't hit their minimum back into the pool
 | |
|           unfinishedSections.append(i);
 | |
|         else
 | |
|           freeSize -= sectionSizes.at(i); // remove size of minimum locked sections from available space in next round
 | |
|       }
 | |
|       // reset all section sizes to zero that are in unfinished sections (all others have been set to their minimum):
 | |
|       for (int i=0; i<unfinishedSections.size(); ++i)
 | |
|         sectionSizes[unfinishedSections.at(i)] = 0;
 | |
|     }
 | |
|   }
 | |
|   if (outerIterations == sectionCount*2)
 | |
|     qDebug() << Q_FUNC_INFO << "Exceeded maximum expected outer iteration count, layouting aborted. Input was:" << maxSizes << minSizes << stretchFactors << totalSize;
 | |
|   
 | |
|   QVector<int> result(sectionCount);
 | |
|   for (int i=0; i<sectionCount; ++i)
 | |
|     result[i] = qRound(sectionSizes.at(i));
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPLayoutGrid
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPLayoutGrid
 | |
|   \brief A layout that arranges child elements in a grid
 | |
| 
 | |
|   Elements are laid out in a grid with configurable stretch factors (\ref setColumnStretchFactor,
 | |
|   \ref setRowStretchFactor) and spacing (\ref setColumnSpacing, \ref setRowSpacing).
 | |
| 
 | |
|   Elements can be added to cells via \ref addElement. The grid is expanded if the specified row or
 | |
|   column doesn't exist yet. Whether a cell contains a valid layout element can be checked with \ref
 | |
|   hasElement, that element can be retrieved with \ref element. If rows and columns that only have
 | |
|   empty cells shall be removed, call \ref simplify. Removal of elements is either done by just
 | |
|   adding the element to a different layout or by using the QCPLayout interface \ref take or \ref
 | |
|   remove.
 | |
| 
 | |
|   If you use \ref addElement(QCPLayoutElement*) without explicit parameters for \a row and \a
 | |
|   column, the grid layout will choose the position according to the current \ref setFillOrder and
 | |
|   the wrapping (\ref setWrap).
 | |
| 
 | |
|   Row and column insertion can be performed with \ref insertRow and \ref insertColumn.
 | |
| */
 | |
| 
 | |
| /* start documentation of inline functions */
 | |
| 
 | |
| /*! \fn int QCPLayoutGrid::rowCount() const
 | |
| 
 | |
|   Returns the number of rows in the layout.
 | |
| 
 | |
|   \see columnCount
 | |
| */
 | |
| 
 | |
| /*! \fn int QCPLayoutGrid::columnCount() const
 | |
| 
 | |
|   Returns the number of columns in the layout.
 | |
| 
 | |
|   \see rowCount
 | |
| */
 | |
| 
 | |
| /* end documentation of inline functions */
 | |
| 
 | |
| /*!
 | |
|   Creates an instance of QCPLayoutGrid and sets default values.
 | |
| */
 | |
| QCPLayoutGrid::QCPLayoutGrid() :
 | |
|   mColumnSpacing(5),
 | |
|   mRowSpacing(5),
 | |
|   mWrap(0),
 | |
|   mFillOrder(foRowsFirst)
 | |
| {
 | |
| }
 | |
| 
 | |
| QCPLayoutGrid::~QCPLayoutGrid()
 | |
| {
 | |
|   // clear all child layout elements. This is important because only the specific layouts know how
 | |
|   // to handle removing elements (clear calls virtual removeAt method to do that).
 | |
|   clear();
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the element in the cell in \a row and \a column.
 | |
|   
 | |
|   Returns 0 if either the row/column is invalid or if the cell is empty. In those cases, a qDebug
 | |
|   message is printed. To check whether a cell exists and isn't empty, use \ref hasElement.
 | |
|   
 | |
|   \see addElement, hasElement
 | |
| */
 | |
| QCPLayoutElement *QCPLayoutGrid::element(int row, int column) const
 | |
| {
 | |
|   if (row >= 0 && row < mElements.size())
 | |
|   {
 | |
|     if (column >= 0 && column < mElements.first().size())
 | |
|     {
 | |
|       if (QCPLayoutElement *result = mElements.at(row).at(column))
 | |
|         return result;
 | |
|       else
 | |
|         qDebug() << Q_FUNC_INFO << "Requested cell is empty. Row:" << row << "Column:" << column;
 | |
|     } else
 | |
|       qDebug() << Q_FUNC_INFO << "Invalid column. Row:" << row << "Column:" << column;
 | |
|   } else
 | |
|     qDebug() << Q_FUNC_INFO << "Invalid row. Row:" << row << "Column:" << column;
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*! \overload
 | |
| 
 | |
|   Adds the \a element to cell with \a row and \a column. If \a element is already in a layout, it
 | |
|   is first removed from there. If \a row or \a column don't exist yet, the layout is expanded
 | |
|   accordingly.
 | |
| 
 | |
|   Returns true if the element was added successfully, i.e. if the cell at \a row and \a column
 | |
|   didn't already have an element.
 | |
| 
 | |
|   Use the overload of this method without explicit row/column index to place the element according
 | |
|   to the configured fill order and wrapping settings.
 | |
| 
 | |
|   \see element, hasElement, take, remove
 | |
| */
 | |
| bool QCPLayoutGrid::addElement(int row, int column, QCPLayoutElement *element)
 | |
| {
 | |
|   if (!hasElement(row, column))
 | |
|   {
 | |
|     if (element && element->layout()) // remove from old layout first
 | |
|       element->layout()->take(element);
 | |
|     expandTo(row+1, column+1);
 | |
|     mElements[row][column] = element;
 | |
|     if (element)
 | |
|       adoptElement(element);
 | |
|     return true;
 | |
|   } else
 | |
|     qDebug() << Q_FUNC_INFO << "There is already an element in the specified row/column:" << row << column;
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
| 
 | |
|   Adds the \a element to the next empty cell according to the current fill order (\ref
 | |
|   setFillOrder) and wrapping (\ref setWrap). If \a element is already in a layout, it is first
 | |
|   removed from there. If necessary, the layout is expanded to hold the new element.
 | |
| 
 | |
|   Returns true if the element was added successfully.
 | |
| 
 | |
|   \see setFillOrder, setWrap, element, hasElement, take, remove
 | |
| */
 | |
| bool QCPLayoutGrid::addElement(QCPLayoutElement *element)
 | |
| {
 | |
|   int rowIndex = 0;
 | |
|   int colIndex = 0;
 | |
|   if (mFillOrder == foColumnsFirst)
 | |
|   {
 | |
|     while (hasElement(rowIndex, colIndex))
 | |
|     {
 | |
|       ++colIndex;
 | |
|       if (colIndex >= mWrap && mWrap > 0)
 | |
|       {
 | |
|         colIndex = 0;
 | |
|         ++rowIndex;
 | |
|       }
 | |
|     }
 | |
|   } else
 | |
|   {
 | |
|     while (hasElement(rowIndex, colIndex))
 | |
|     {
 | |
|       ++rowIndex;
 | |
|       if (rowIndex >= mWrap && mWrap > 0)
 | |
|       {
 | |
|         rowIndex = 0;
 | |
|         ++colIndex;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   return addElement(rowIndex, colIndex, element);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns whether the cell at \a row and \a column exists and contains a valid element, i.e. isn't
 | |
|   empty.
 | |
|   
 | |
|   \see element
 | |
| */
 | |
| bool QCPLayoutGrid::hasElement(int row, int column)
 | |
| {
 | |
|   if (row >= 0 && row < rowCount() && column >= 0 && column < columnCount())
 | |
|     return mElements.at(row).at(column);
 | |
|   else
 | |
|     return false;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the stretch \a factor of \a column.
 | |
|   
 | |
|   Stretch factors control the relative sizes of rows and columns. Cells will not be resized beyond
 | |
|   their minimum and maximum widths/heights (\ref QCPLayoutElement::setMinimumSize, \ref
 | |
|   QCPLayoutElement::setMaximumSize), regardless of the stretch factor.
 | |
|   
 | |
|   The default stretch factor of newly created rows/columns is 1.
 | |
|   
 | |
|   \see setColumnStretchFactors, setRowStretchFactor
 | |
| */
 | |
| void QCPLayoutGrid::setColumnStretchFactor(int column, double factor)
 | |
| {
 | |
|   if (column >= 0 && column < columnCount())
 | |
|   {
 | |
|     if (factor > 0)
 | |
|       mColumnStretchFactors[column] = factor;
 | |
|     else
 | |
|       qDebug() << Q_FUNC_INFO << "Invalid stretch factor, must be positive:" << factor;
 | |
|   } else
 | |
|     qDebug() << Q_FUNC_INFO << "Invalid column:" << column;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the stretch \a factors of all columns. \a factors must have the size \ref columnCount.
 | |
|   
 | |
|   Stretch factors control the relative sizes of rows and columns. Cells will not be resized beyond
 | |
|   their minimum and maximum widths/heights (\ref QCPLayoutElement::setMinimumSize, \ref
 | |
|   QCPLayoutElement::setMaximumSize), regardless of the stretch factor.
 | |
|   
 | |
|   The default stretch factor of newly created rows/columns is 1.
 | |
|   
 | |
|   \see setColumnStretchFactor, setRowStretchFactors
 | |
| */
 | |
| void QCPLayoutGrid::setColumnStretchFactors(const QList<double> &factors)
 | |
| {
 | |
|   if (factors.size() == mColumnStretchFactors.size())
 | |
|   {
 | |
|     mColumnStretchFactors = factors;
 | |
|     for (int i=0; i<mColumnStretchFactors.size(); ++i)
 | |
|     {
 | |
|       if (mColumnStretchFactors.at(i) <= 0)
 | |
|       {
 | |
|         qDebug() << Q_FUNC_INFO << "Invalid stretch factor, must be positive:" << mColumnStretchFactors.at(i);
 | |
|         mColumnStretchFactors[i] = 1;
 | |
|       }
 | |
|     }
 | |
|   } else
 | |
|     qDebug() << Q_FUNC_INFO << "Column count not equal to passed stretch factor count:" << factors;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the stretch \a factor of \a row.
 | |
|   
 | |
|   Stretch factors control the relative sizes of rows and columns. Cells will not be resized beyond
 | |
|   their minimum and maximum widths/heights (\ref QCPLayoutElement::setMinimumSize, \ref
 | |
|   QCPLayoutElement::setMaximumSize), regardless of the stretch factor.
 | |
|   
 | |
|   The default stretch factor of newly created rows/columns is 1.
 | |
|   
 | |
|   \see setColumnStretchFactors, setRowStretchFactor
 | |
| */
 | |
| void QCPLayoutGrid::setRowStretchFactor(int row, double factor)
 | |
| {
 | |
|   if (row >= 0 && row < rowCount())
 | |
|   {
 | |
|     if (factor > 0)
 | |
|       mRowStretchFactors[row] = factor;
 | |
|     else
 | |
|       qDebug() << Q_FUNC_INFO << "Invalid stretch factor, must be positive:" << factor;
 | |
|   } else
 | |
|     qDebug() << Q_FUNC_INFO << "Invalid row:" << row;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the stretch \a factors of all rows. \a factors must have the size \ref rowCount.
 | |
|   
 | |
|   Stretch factors control the relative sizes of rows and columns. Cells will not be resized beyond
 | |
|   their minimum and maximum widths/heights (\ref QCPLayoutElement::setMinimumSize, \ref
 | |
|   QCPLayoutElement::setMaximumSize), regardless of the stretch factor.
 | |
|   
 | |
|   The default stretch factor of newly created rows/columns is 1.
 | |
|   
 | |
|   \see setRowStretchFactor, setColumnStretchFactors
 | |
| */
 | |
| void QCPLayoutGrid::setRowStretchFactors(const QList<double> &factors)
 | |
| {
 | |
|   if (factors.size() == mRowStretchFactors.size())
 | |
|   {
 | |
|     mRowStretchFactors = factors;
 | |
|     for (int i=0; i<mRowStretchFactors.size(); ++i)
 | |
|     {
 | |
|       if (mRowStretchFactors.at(i) <= 0)
 | |
|       {
 | |
|         qDebug() << Q_FUNC_INFO << "Invalid stretch factor, must be positive:" << mRowStretchFactors.at(i);
 | |
|         mRowStretchFactors[i] = 1;
 | |
|       }
 | |
|     }
 | |
|   } else
 | |
|     qDebug() << Q_FUNC_INFO << "Row count not equal to passed stretch factor count:" << factors;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the gap that is left blank between columns to \a pixels.
 | |
|   
 | |
|   \see setRowSpacing
 | |
| */
 | |
| void QCPLayoutGrid::setColumnSpacing(int pixels)
 | |
| {
 | |
|   mColumnSpacing = pixels;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the gap that is left blank between rows to \a pixels.
 | |
|   
 | |
|   \see setColumnSpacing
 | |
| */
 | |
| void QCPLayoutGrid::setRowSpacing(int pixels)
 | |
| {
 | |
|   mRowSpacing = pixels;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the maximum number of columns or rows that are used, before new elements added with \ref
 | |
|   addElement(QCPLayoutElement*) will start to fill the next row or column, respectively. It depends
 | |
|   on \ref setFillOrder, whether rows or columns are wrapped.
 | |
| 
 | |
|   If \a count is set to zero, no wrapping will ever occur.
 | |
|   
 | |
|   If you wish to re-wrap the elements currently in the layout, call \ref setFillOrder with \a
 | |
|   rearrange set to true (the actual fill order doesn't need to be changed for the rearranging to be
 | |
|   done).
 | |
| 
 | |
|   Note that the method \ref addElement(int row, int column, QCPLayoutElement *element) with
 | |
|   explicitly stated row and column is not subject to wrapping and can place elements even beyond
 | |
|   the specified wrapping point.
 | |
| 
 | |
|   \see setFillOrder
 | |
| */
 | |
| void QCPLayoutGrid::setWrap(int count)
 | |
| {
 | |
|   mWrap = qMax(0, count);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the filling order and wrapping behaviour that is used when adding new elements with the
 | |
|   method \ref addElement(QCPLayoutElement*).
 | |
| 
 | |
|   The specified \a order defines whether rows or columns are filled first. Using \ref setWrap, you
 | |
|   can control at which row/column count wrapping into the next column/row will occur. If you set it
 | |
|   to zero, no wrapping will ever occur. Changing the fill order also changes the meaning of the
 | |
|   linear index used e.g. in \ref elementAt and \ref takeAt.
 | |
| 
 | |
|   If you want to have all current elements arranged in the new order, set \a rearrange to true. The
 | |
|   elements will be rearranged in a way that tries to preserve their linear index. However, empty
 | |
|   cells are skipped during build-up of the new cell order, which shifts the succeding element's
 | |
|   index. The rearranging is performed even if the specified \a order is already the current fill
 | |
|   order. Thus this method can be used to re-wrap the current elements.
 | |
| 
 | |
|   If \a rearrange is false, the current element arrangement is not changed, which means the
 | |
|   linear indexes change (because the linear index is dependent on the fill order).
 | |
| 
 | |
|   Note that the method \ref addElement(int row, int column, QCPLayoutElement *element) with
 | |
|   explicitly stated row and column is not subject to wrapping and can place elements even beyond
 | |
|   the specified wrapping point.
 | |
| 
 | |
|   \see setWrap, addElement(QCPLayoutElement*)
 | |
| */
 | |
| void QCPLayoutGrid::setFillOrder(FillOrder order, bool rearrange)
 | |
| {
 | |
|   // if rearranging, take all elements via linear index of old fill order:
 | |
|   const int elCount = elementCount();
 | |
|   QVector<QCPLayoutElement*> tempElements;
 | |
|   if (rearrange)
 | |
|   {
 | |
|     tempElements.reserve(elCount);
 | |
|     for (int i=0; i<elCount; ++i)
 | |
|     {
 | |
|       if (elementAt(i))
 | |
|         tempElements.append(takeAt(i));
 | |
|     }
 | |
|     simplify();
 | |
|   }
 | |
|   // change fill order as requested:
 | |
|   mFillOrder = order;
 | |
|   // if rearranging, re-insert via linear index according to new fill order:
 | |
|   if (rearrange)
 | |
|   {
 | |
|     for (int i=0; i<tempElements.size(); ++i)
 | |
|       addElement(tempElements.at(i));
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Expands the layout to have \a newRowCount rows and \a newColumnCount columns. So the last valid
 | |
|   row index will be \a newRowCount-1, the last valid column index will be \a newColumnCount-1.
 | |
|   
 | |
|   If the current column/row count is already larger or equal to \a newColumnCount/\a newRowCount,
 | |
|   this function does nothing in that dimension.
 | |
|   
 | |
|   Newly created cells are empty, new rows and columns have the stretch factor 1.
 | |
|   
 | |
|   Note that upon a call to \ref addElement, the layout is expanded automatically to contain the
 | |
|   specified row and column, using this function.
 | |
|   
 | |
|   \see simplify
 | |
| */
 | |
| void QCPLayoutGrid::expandTo(int newRowCount, int newColumnCount)
 | |
| {
 | |
|   // add rows as necessary:
 | |
|   while (rowCount() < newRowCount)
 | |
|   {
 | |
|     mElements.append(QList<QCPLayoutElement*>());
 | |
|     mRowStretchFactors.append(1);
 | |
|   }
 | |
|   // go through rows and expand columns as necessary:
 | |
|   int newColCount = qMax(columnCount(), newColumnCount);
 | |
|   for (int i=0; i<rowCount(); ++i)
 | |
|   {
 | |
|     while (mElements.at(i).size() < newColCount)
 | |
|       mElements[i].append(0);
 | |
|   }
 | |
|   while (mColumnStretchFactors.size() < newColCount)
 | |
|     mColumnStretchFactors.append(1);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Inserts a new row with empty cells at the row index \a newIndex. Valid values for \a newIndex
 | |
|   range from 0 (inserts a row at the top) to \a rowCount (appends a row at the bottom).
 | |
|   
 | |
|   \see insertColumn
 | |
| */
 | |
| void QCPLayoutGrid::insertRow(int newIndex)
 | |
| {
 | |
|   if (mElements.isEmpty() || mElements.first().isEmpty()) // if grid is completely empty, add first cell
 | |
|   {
 | |
|     expandTo(1, 1);
 | |
|     return;
 | |
|   }
 | |
|   
 | |
|   if (newIndex < 0)
 | |
|     newIndex = 0;
 | |
|   if (newIndex > rowCount())
 | |
|     newIndex = rowCount();
 | |
|   
 | |
|   mRowStretchFactors.insert(newIndex, 1);
 | |
|   QList<QCPLayoutElement*> newRow;
 | |
|   for (int col=0; col<columnCount(); ++col)
 | |
|     newRow.append((QCPLayoutElement*)0);
 | |
|   mElements.insert(newIndex, newRow);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Inserts a new column with empty cells at the column index \a newIndex. Valid values for \a
 | |
|   newIndex range from 0 (inserts a row at the left) to \a rowCount (appends a row at the right).
 | |
|   
 | |
|   \see insertRow
 | |
| */
 | |
| void QCPLayoutGrid::insertColumn(int newIndex)
 | |
| {
 | |
|   if (mElements.isEmpty() || mElements.first().isEmpty()) // if grid is completely empty, add first cell
 | |
|   {
 | |
|     expandTo(1, 1);
 | |
|     return;
 | |
|   }
 | |
|   
 | |
|   if (newIndex < 0)
 | |
|     newIndex = 0;
 | |
|   if (newIndex > columnCount())
 | |
|     newIndex = columnCount();
 | |
|   
 | |
|   mColumnStretchFactors.insert(newIndex, 1);
 | |
|   for (int row=0; row<rowCount(); ++row)
 | |
|     mElements[row].insert(newIndex, (QCPLayoutElement*)0);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Converts the given \a row and \a column to the linear index used by some methods of \ref
 | |
|   QCPLayoutGrid and \ref QCPLayout.
 | |
| 
 | |
|   The way the cells are indexed depends on \ref setFillOrder. If it is \ref foRowsFirst, the
 | |
|   indices increase left to right and then top to bottom. If it is \ref foColumnsFirst, the indices
 | |
|   increase top to bottom and then left to right.
 | |
| 
 | |
|   For the returned index to be valid, \a row and \a column must be valid indices themselves, i.e.
 | |
|   greater or equal to zero and smaller than the current \ref rowCount/\ref columnCount.
 | |
| 
 | |
|   \see indexToRowCol
 | |
| */
 | |
| int QCPLayoutGrid::rowColToIndex(int row, int column) const
 | |
| {
 | |
|   if (row >= 0 && row < rowCount())
 | |
|   {
 | |
|     if (column >= 0 && column < columnCount())
 | |
|     {
 | |
|       switch (mFillOrder)
 | |
|       {
 | |
|         case foRowsFirst: return column*rowCount() + row;
 | |
|         case foColumnsFirst: return row*columnCount() + column;
 | |
|       }
 | |
|     } else
 | |
|       qDebug() << Q_FUNC_INFO << "row index out of bounds:" << row;
 | |
|   } else
 | |
|     qDebug() << Q_FUNC_INFO << "column index out of bounds:" << column;
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Converts the linear index to row and column indices and writes the result to \a row and \a
 | |
|   column.
 | |
| 
 | |
|   The way the cells are indexed depends on \ref setFillOrder. If it is \ref foRowsFirst, the
 | |
|   indices increase left to right and then top to bottom. If it is \ref foColumnsFirst, the indices
 | |
|   increase top to bottom and then left to right.
 | |
| 
 | |
|   If there are no cells (i.e. column or row count is zero), sets \a row and \a column to -1.
 | |
| 
 | |
|   For the retrieved \a row and \a column to be valid, the passed \a index must be valid itself,
 | |
|   i.e. greater or equal to zero and smaller than the current \ref elementCount.
 | |
| 
 | |
|   \see rowColToIndex
 | |
| */
 | |
| void QCPLayoutGrid::indexToRowCol(int index, int &row, int &column) const
 | |
| {
 | |
|   row = -1;
 | |
|   column = -1;
 | |
|   if (columnCount() == 0 || rowCount() == 0)
 | |
|     return;
 | |
|   if (index < 0 || index >= elementCount())
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "index out of bounds:" << index;
 | |
|     return;
 | |
|   }
 | |
|   
 | |
|   switch (mFillOrder)
 | |
|   {
 | |
|     case foRowsFirst:
 | |
|     {
 | |
|       column = index / rowCount();
 | |
|       row = index % rowCount();
 | |
|       break;
 | |
|     }
 | |
|     case foColumnsFirst:
 | |
|     {
 | |
|       row = index / columnCount();
 | |
|       column = index % columnCount();
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPLayoutGrid::updateLayout()
 | |
| {
 | |
|   QVector<int> minColWidths, minRowHeights, maxColWidths, maxRowHeights;
 | |
|   getMinimumRowColSizes(&minColWidths, &minRowHeights);
 | |
|   getMaximumRowColSizes(&maxColWidths, &maxRowHeights);
 | |
|   
 | |
|   int totalRowSpacing = (rowCount()-1) * mRowSpacing;
 | |
|   int totalColSpacing = (columnCount()-1) * mColumnSpacing;
 | |
|   QVector<int> colWidths = getSectionSizes(maxColWidths, minColWidths, mColumnStretchFactors.toVector(), mRect.width()-totalColSpacing);
 | |
|   QVector<int> rowHeights = getSectionSizes(maxRowHeights, minRowHeights, mRowStretchFactors.toVector(), mRect.height()-totalRowSpacing);
 | |
|   
 | |
|   // go through cells and set rects accordingly:
 | |
|   int yOffset = mRect.top();
 | |
|   for (int row=0; row<rowCount(); ++row)
 | |
|   {
 | |
|     if (row > 0)
 | |
|       yOffset += rowHeights.at(row-1)+mRowSpacing;
 | |
|     int xOffset = mRect.left();
 | |
|     for (int col=0; col<columnCount(); ++col)
 | |
|     {
 | |
|       if (col > 0)
 | |
|         xOffset += colWidths.at(col-1)+mColumnSpacing;
 | |
|       if (mElements.at(row).at(col))
 | |
|         mElements.at(row).at(col)->setOuterRect(QRect(xOffset, yOffset, colWidths.at(col), rowHeights.at(row)));
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   \seebaseclassmethod
 | |
| 
 | |
|   Note that the association of the linear \a index to the row/column based cells depends on the
 | |
|   current setting of \ref setFillOrder.
 | |
| 
 | |
|   \see rowColToIndex
 | |
| */
 | |
| QCPLayoutElement *QCPLayoutGrid::elementAt(int index) const
 | |
| {
 | |
|   if (index >= 0 && index < elementCount())
 | |
|   {
 | |
|     int row, col;
 | |
|     indexToRowCol(index, row, col);
 | |
|     return mElements.at(row).at(col);
 | |
|   } else
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   \seebaseclassmethod
 | |
| 
 | |
|   Note that the association of the linear \a index to the row/column based cells depends on the
 | |
|   current setting of \ref setFillOrder.
 | |
| 
 | |
|   \see rowColToIndex
 | |
| */
 | |
| QCPLayoutElement *QCPLayoutGrid::takeAt(int index)
 | |
| {
 | |
|   if (QCPLayoutElement *el = elementAt(index))
 | |
|   {
 | |
|     releaseElement(el);
 | |
|     int row, col;
 | |
|     indexToRowCol(index, row, col);
 | |
|     mElements[row][col] = 0;
 | |
|     return el;
 | |
|   } else
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "Attempt to take invalid index:" << index;
 | |
|     return 0;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| bool QCPLayoutGrid::take(QCPLayoutElement *element)
 | |
| {
 | |
|   if (element)
 | |
|   {
 | |
|     for (int i=0; i<elementCount(); ++i)
 | |
|     {
 | |
|       if (elementAt(i) == element)
 | |
|       {
 | |
|         takeAt(i);
 | |
|         return true;
 | |
|       }
 | |
|     }
 | |
|     qDebug() << Q_FUNC_INFO << "Element not in this layout, couldn't take";
 | |
|   } else
 | |
|     qDebug() << Q_FUNC_INFO << "Can't take null element";
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| QList<QCPLayoutElement*> QCPLayoutGrid::elements(bool recursive) const
 | |
| {
 | |
|   QList<QCPLayoutElement*> result;
 | |
|   const int elCount = elementCount();
 | |
| #if QT_VERSION >= QT_VERSION_CHECK(4, 7, 0)
 | |
|   result.reserve(elCount);
 | |
| #endif
 | |
|   for (int i=0; i<elCount; ++i)
 | |
|     result.append(elementAt(i));
 | |
|   if (recursive)
 | |
|   {
 | |
|     for (int i=0; i<elCount; ++i)
 | |
|     {
 | |
|       if (result.at(i))
 | |
|         result << result.at(i)->elements(recursive);
 | |
|     }
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Simplifies the layout by collapsing rows and columns which only contain empty cells.
 | |
| */
 | |
| void QCPLayoutGrid::simplify()
 | |
| {
 | |
|   // remove rows with only empty cells:
 | |
|   for (int row=rowCount()-1; row>=0; --row)
 | |
|   {
 | |
|     bool hasElements = false;
 | |
|     for (int col=0; col<columnCount(); ++col)
 | |
|     {
 | |
|       if (mElements.at(row).at(col))
 | |
|       {
 | |
|         hasElements = true;
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
|     if (!hasElements)
 | |
|     {
 | |
|       mRowStretchFactors.removeAt(row);
 | |
|       mElements.removeAt(row);
 | |
|       if (mElements.isEmpty()) // removed last element, also remove stretch factor (wouldn't happen below because also columnCount changed to 0 now)
 | |
|         mColumnStretchFactors.clear();
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   // remove columns with only empty cells:
 | |
|   for (int col=columnCount()-1; col>=0; --col)
 | |
|   {
 | |
|     bool hasElements = false;
 | |
|     for (int row=0; row<rowCount(); ++row)
 | |
|     {
 | |
|       if (mElements.at(row).at(col))
 | |
|       {
 | |
|         hasElements = true;
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
|     if (!hasElements)
 | |
|     {
 | |
|       mColumnStretchFactors.removeAt(col);
 | |
|       for (int row=0; row<rowCount(); ++row)
 | |
|         mElements[row].removeAt(col);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| QSize QCPLayoutGrid::minimumSizeHint() const
 | |
| {
 | |
|   QVector<int> minColWidths, minRowHeights;
 | |
|   getMinimumRowColSizes(&minColWidths, &minRowHeights);
 | |
|   QSize result(0, 0);
 | |
|   for (int i=0; i<minColWidths.size(); ++i)
 | |
|     result.rwidth() += minColWidths.at(i);
 | |
|   for (int i=0; i<minRowHeights.size(); ++i)
 | |
|     result.rheight() += minRowHeights.at(i);
 | |
|   result.rwidth() += qMax(0, columnCount()-1) * mColumnSpacing + mMargins.left() + mMargins.right();
 | |
|   result.rheight() += qMax(0, rowCount()-1) * mRowSpacing + mMargins.top() + mMargins.bottom();
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| QSize QCPLayoutGrid::maximumSizeHint() const
 | |
| {
 | |
|   QVector<int> maxColWidths, maxRowHeights;
 | |
|   getMaximumRowColSizes(&maxColWidths, &maxRowHeights);
 | |
|   
 | |
|   QSize result(0, 0);
 | |
|   for (int i=0; i<maxColWidths.size(); ++i)
 | |
|     result.setWidth(qMin(result.width()+maxColWidths.at(i), QWIDGETSIZE_MAX));
 | |
|   for (int i=0; i<maxRowHeights.size(); ++i)
 | |
|     result.setHeight(qMin(result.height()+maxRowHeights.at(i), QWIDGETSIZE_MAX));
 | |
|   result.rwidth() += qMax(0, columnCount()-1) * mColumnSpacing + mMargins.left() + mMargins.right();
 | |
|   result.rheight() += qMax(0, rowCount()-1) * mRowSpacing + mMargins.top() + mMargins.bottom();
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Places the minimum column widths and row heights into \a minColWidths and \a minRowHeights
 | |
|   respectively.
 | |
|   
 | |
|   The minimum height of a row is the largest minimum height of any element in that row. The minimum
 | |
|   width of a column is the largest minimum width of any element in that column.
 | |
|   
 | |
|   This is a helper function for \ref updateLayout.
 | |
|   
 | |
|   \see getMaximumRowColSizes
 | |
| */
 | |
| void QCPLayoutGrid::getMinimumRowColSizes(QVector<int> *minColWidths, QVector<int> *minRowHeights) const
 | |
| {
 | |
|   *minColWidths = QVector<int>(columnCount(), 0);
 | |
|   *minRowHeights = QVector<int>(rowCount(), 0);
 | |
|   for (int row=0; row<rowCount(); ++row)
 | |
|   {
 | |
|     for (int col=0; col<columnCount(); ++col)
 | |
|     {
 | |
|       if (mElements.at(row).at(col))
 | |
|       {
 | |
|         QSize minHint = mElements.at(row).at(col)->minimumSizeHint();
 | |
|         QSize min = mElements.at(row).at(col)->minimumSize();
 | |
|         QSize final(min.width() > 0 ? min.width() : minHint.width(), min.height() > 0 ? min.height() : minHint.height());
 | |
|         if (minColWidths->at(col) < final.width())
 | |
|           (*minColWidths)[col] = final.width();
 | |
|         if (minRowHeights->at(row) < final.height())
 | |
|           (*minRowHeights)[row] = final.height();
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Places the maximum column widths and row heights into \a maxColWidths and \a maxRowHeights
 | |
|   respectively.
 | |
|   
 | |
|   The maximum height of a row is the smallest maximum height of any element in that row. The
 | |
|   maximum width of a column is the smallest maximum width of any element in that column.
 | |
|   
 | |
|   This is a helper function for \ref updateLayout.
 | |
|   
 | |
|   \see getMinimumRowColSizes
 | |
| */
 | |
| void QCPLayoutGrid::getMaximumRowColSizes(QVector<int> *maxColWidths, QVector<int> *maxRowHeights) const
 | |
| {
 | |
|   *maxColWidths = QVector<int>(columnCount(), QWIDGETSIZE_MAX);
 | |
|   *maxRowHeights = QVector<int>(rowCount(), QWIDGETSIZE_MAX);
 | |
|   for (int row=0; row<rowCount(); ++row)
 | |
|   {
 | |
|     for (int col=0; col<columnCount(); ++col)
 | |
|     {
 | |
|       if (mElements.at(row).at(col))
 | |
|       {
 | |
|         QSize maxHint = mElements.at(row).at(col)->maximumSizeHint();
 | |
|         QSize max = mElements.at(row).at(col)->maximumSize();
 | |
|         QSize final(max.width() < QWIDGETSIZE_MAX ? max.width() : maxHint.width(), max.height() < QWIDGETSIZE_MAX ? max.height() : maxHint.height());
 | |
|         if (maxColWidths->at(col) > final.width())
 | |
|           (*maxColWidths)[col] = final.width();
 | |
|         if (maxRowHeights->at(row) > final.height())
 | |
|           (*maxRowHeights)[row] = final.height();
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPLayoutInset
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| /*! \class QCPLayoutInset
 | |
|   \brief A layout that places child elements aligned to the border or arbitrarily positioned
 | |
|   
 | |
|   Elements are placed either aligned to the border or at arbitrary position in the area of the
 | |
|   layout. Which placement applies is controlled with the \ref InsetPlacement (\ref
 | |
|   setInsetPlacement).
 | |
| 
 | |
|   Elements are added via \ref addElement(QCPLayoutElement *element, Qt::Alignment alignment) or
 | |
|   addElement(QCPLayoutElement *element, const QRectF &rect). If the first method is used, the inset
 | |
|   placement will default to \ref ipBorderAligned and the element will be aligned according to the
 | |
|   \a alignment parameter. The second method defaults to \ref ipFree and allows placing elements at
 | |
|   arbitrary position and size, defined by \a rect.
 | |
|   
 | |
|   The alignment or rect can be set via \ref setInsetAlignment or \ref setInsetRect, respectively.
 | |
|   
 | |
|   This is the layout that every QCPAxisRect has as \ref QCPAxisRect::insetLayout.
 | |
| */
 | |
| 
 | |
| /* start documentation of inline functions */
 | |
| 
 | |
| /*! \fn virtual void QCPLayoutInset::simplify()
 | |
|   
 | |
|   The QCPInsetLayout does not need simplification since it can never have empty cells due to its
 | |
|   linear index structure. This method does nothing.
 | |
| */
 | |
| 
 | |
| /* end documentation of inline functions */
 | |
| 
 | |
| /*!
 | |
|   Creates an instance of QCPLayoutInset and sets default values.
 | |
| */
 | |
| QCPLayoutInset::QCPLayoutInset()
 | |
| {
 | |
| }
 | |
| 
 | |
| QCPLayoutInset::~QCPLayoutInset()
 | |
| {
 | |
|   // clear all child layout elements. This is important because only the specific layouts know how
 | |
|   // to handle removing elements (clear calls virtual removeAt method to do that).
 | |
|   clear();
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the placement type of the element with the specified \a index.
 | |
| */
 | |
| QCPLayoutInset::InsetPlacement QCPLayoutInset::insetPlacement(int index) const
 | |
| {
 | |
|   if (elementAt(index))
 | |
|     return mInsetPlacement.at(index);
 | |
|   else
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "Invalid element index:" << index;
 | |
|     return ipFree;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the alignment of the element with the specified \a index. The alignment only has a
 | |
|   meaning, if the inset placement (\ref setInsetPlacement) is \ref ipBorderAligned.
 | |
| */
 | |
| Qt::Alignment QCPLayoutInset::insetAlignment(int index) const
 | |
| {
 | |
|   if (elementAt(index))
 | |
|     return mInsetAlignment.at(index);
 | |
|   else
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "Invalid element index:" << index;
 | |
|     return 0;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the rect of the element with the specified \a index. The rect only has a
 | |
|   meaning, if the inset placement (\ref setInsetPlacement) is \ref ipFree.
 | |
| */
 | |
| QRectF QCPLayoutInset::insetRect(int index) const
 | |
| {
 | |
|   if (elementAt(index))
 | |
|     return mInsetRect.at(index);
 | |
|   else
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "Invalid element index:" << index;
 | |
|     return QRectF();
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the inset placement type of the element with the specified \a index to \a placement.
 | |
|   
 | |
|   \see InsetPlacement
 | |
| */
 | |
| void QCPLayoutInset::setInsetPlacement(int index, QCPLayoutInset::InsetPlacement placement)
 | |
| {
 | |
|   if (elementAt(index))
 | |
|     mInsetPlacement[index] = placement;
 | |
|   else
 | |
|     qDebug() << Q_FUNC_INFO << "Invalid element index:" << index;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   If the inset placement (\ref setInsetPlacement) is \ref ipBorderAligned, this function
 | |
|   is used to set the alignment of the element with the specified \a index to \a alignment.
 | |
|   
 | |
|   \a alignment is an or combination of the following alignment flags: Qt::AlignLeft,
 | |
|   Qt::AlignHCenter, Qt::AlighRight, Qt::AlignTop, Qt::AlignVCenter, Qt::AlignBottom. Any other
 | |
|   alignment flags will be ignored.
 | |
| */
 | |
| void QCPLayoutInset::setInsetAlignment(int index, Qt::Alignment alignment)
 | |
| {
 | |
|   if (elementAt(index))
 | |
|     mInsetAlignment[index] = alignment;
 | |
|   else
 | |
|     qDebug() << Q_FUNC_INFO << "Invalid element index:" << index;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   If the inset placement (\ref setInsetPlacement) is \ref ipFree, this function is used to set the
 | |
|   position and size of the element with the specified \a index to \a rect.
 | |
|   
 | |
|   \a rect is given in fractions of the whole inset layout rect. So an inset with rect (0, 0, 1, 1)
 | |
|   will span the entire layout. An inset with rect (0.6, 0.1, 0.35, 0.35) will be in the top right
 | |
|   corner of the layout, with 35% width and height of the parent layout.
 | |
|   
 | |
|   Note that the minimum and maximum sizes of the embedded element (\ref
 | |
|   QCPLayoutElement::setMinimumSize, \ref QCPLayoutElement::setMaximumSize) are enforced.
 | |
| */
 | |
| void QCPLayoutInset::setInsetRect(int index, const QRectF &rect)
 | |
| {
 | |
|   if (elementAt(index))
 | |
|     mInsetRect[index] = rect;
 | |
|   else
 | |
|     qDebug() << Q_FUNC_INFO << "Invalid element index:" << index;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPLayoutInset::updateLayout()
 | |
| {
 | |
|   for (int i=0; i<mElements.size(); ++i)
 | |
|   {
 | |
|     QRect insetRect;
 | |
|     QSize finalMinSize, finalMaxSize;
 | |
|     QSize minSizeHint = mElements.at(i)->minimumSizeHint();
 | |
|     QSize maxSizeHint = mElements.at(i)->maximumSizeHint();
 | |
|     finalMinSize.setWidth(mElements.at(i)->minimumSize().width() > 0 ? mElements.at(i)->minimumSize().width() : minSizeHint.width());
 | |
|     finalMinSize.setHeight(mElements.at(i)->minimumSize().height() > 0 ? mElements.at(i)->minimumSize().height() : minSizeHint.height());
 | |
|     finalMaxSize.setWidth(mElements.at(i)->maximumSize().width() < QWIDGETSIZE_MAX ? mElements.at(i)->maximumSize().width() : maxSizeHint.width());
 | |
|     finalMaxSize.setHeight(mElements.at(i)->maximumSize().height() < QWIDGETSIZE_MAX ? mElements.at(i)->maximumSize().height() : maxSizeHint.height());
 | |
|     if (mInsetPlacement.at(i) == ipFree)
 | |
|     {
 | |
|       insetRect = QRect(rect().x()+rect().width()*mInsetRect.at(i).x(),
 | |
|                         rect().y()+rect().height()*mInsetRect.at(i).y(),
 | |
|                         rect().width()*mInsetRect.at(i).width(),
 | |
|                         rect().height()*mInsetRect.at(i).height());
 | |
|       if (insetRect.size().width() < finalMinSize.width())
 | |
|         insetRect.setWidth(finalMinSize.width());
 | |
|       if (insetRect.size().height() < finalMinSize.height())
 | |
|         insetRect.setHeight(finalMinSize.height());
 | |
|       if (insetRect.size().width() > finalMaxSize.width())
 | |
|         insetRect.setWidth(finalMaxSize.width());
 | |
|       if (insetRect.size().height() > finalMaxSize.height())
 | |
|         insetRect.setHeight(finalMaxSize.height());
 | |
|     } else if (mInsetPlacement.at(i) == ipBorderAligned)
 | |
|     {
 | |
|       insetRect.setSize(finalMinSize);
 | |
|       Qt::Alignment al = mInsetAlignment.at(i);
 | |
|       if (al.testFlag(Qt::AlignLeft)) insetRect.moveLeft(rect().x());
 | |
|       else if (al.testFlag(Qt::AlignRight)) insetRect.moveRight(rect().x()+rect().width());
 | |
|       else insetRect.moveLeft(rect().x()+rect().width()*0.5-finalMinSize.width()*0.5); // default to Qt::AlignHCenter
 | |
|       if (al.testFlag(Qt::AlignTop)) insetRect.moveTop(rect().y());
 | |
|       else if (al.testFlag(Qt::AlignBottom)) insetRect.moveBottom(rect().y()+rect().height());
 | |
|       else insetRect.moveTop(rect().y()+rect().height()*0.5-finalMinSize.height()*0.5); // default to Qt::AlignVCenter
 | |
|     }
 | |
|     mElements.at(i)->setOuterRect(insetRect);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| int QCPLayoutInset::elementCount() const
 | |
| {
 | |
|   return mElements.size();
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| QCPLayoutElement *QCPLayoutInset::elementAt(int index) const
 | |
| {
 | |
|   if (index >= 0 && index < mElements.size())
 | |
|     return mElements.at(index);
 | |
|   else
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| QCPLayoutElement *QCPLayoutInset::takeAt(int index)
 | |
| {
 | |
|   if (QCPLayoutElement *el = elementAt(index))
 | |
|   {
 | |
|     releaseElement(el);
 | |
|     mElements.removeAt(index);
 | |
|     mInsetPlacement.removeAt(index);
 | |
|     mInsetAlignment.removeAt(index);
 | |
|     mInsetRect.removeAt(index);
 | |
|     return el;
 | |
|   } else
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "Attempt to take invalid index:" << index;
 | |
|     return 0;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| bool QCPLayoutInset::take(QCPLayoutElement *element)
 | |
| {
 | |
|   if (element)
 | |
|   {
 | |
|     for (int i=0; i<elementCount(); ++i)
 | |
|     {
 | |
|       if (elementAt(i) == element)
 | |
|       {
 | |
|         takeAt(i);
 | |
|         return true;
 | |
|       }
 | |
|     }
 | |
|     qDebug() << Q_FUNC_INFO << "Element not in this layout, couldn't take";
 | |
|   } else
 | |
|     qDebug() << Q_FUNC_INFO << "Can't take null element";
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   The inset layout is sensitive to events only at areas where its (visible) child elements are
 | |
|   sensitive. If the selectTest method of any of the child elements returns a positive number for \a
 | |
|   pos, this method returns a value corresponding to 0.99 times the parent plot's selection
 | |
|   tolerance. The inset layout is not selectable itself by default. So if \a onlySelectable is true,
 | |
|   -1.0 is returned.
 | |
|   
 | |
|   See \ref QCPLayerable::selectTest for a general explanation of this virtual method.
 | |
| */
 | |
| double QCPLayoutInset::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
 | |
| {
 | |
|   Q_UNUSED(details)
 | |
|   if (onlySelectable)
 | |
|     return -1;
 | |
|   
 | |
|   for (int i=0; i<mElements.size(); ++i)
 | |
|   {
 | |
|     // inset layout shall only return positive selectTest, if actually an inset object is at pos
 | |
|     // else it would block the entire underlying QCPAxisRect with its surface.
 | |
|     if (mElements.at(i)->realVisibility() && mElements.at(i)->selectTest(pos, onlySelectable) >= 0)
 | |
|       return mParentPlot->selectionTolerance()*0.99;
 | |
|   }
 | |
|   return -1;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Adds the specified \a element to the layout as an inset aligned at the border (\ref
 | |
|   setInsetAlignment is initialized with \ref ipBorderAligned). The alignment is set to \a
 | |
|   alignment.
 | |
|   
 | |
|   \a alignment is an or combination of the following alignment flags: Qt::AlignLeft,
 | |
|   Qt::AlignHCenter, Qt::AlighRight, Qt::AlignTop, Qt::AlignVCenter, Qt::AlignBottom. Any other
 | |
|   alignment flags will be ignored.
 | |
|   
 | |
|   \see addElement(QCPLayoutElement *element, const QRectF &rect)
 | |
| */
 | |
| void QCPLayoutInset::addElement(QCPLayoutElement *element, Qt::Alignment alignment)
 | |
| {
 | |
|   if (element)
 | |
|   {
 | |
|     if (element->layout()) // remove from old layout first
 | |
|       element->layout()->take(element);
 | |
|     mElements.append(element);
 | |
|     mInsetPlacement.append(ipBorderAligned);
 | |
|     mInsetAlignment.append(alignment);
 | |
|     mInsetRect.append(QRectF(0.6, 0.6, 0.4, 0.4));
 | |
|     adoptElement(element);
 | |
|   } else
 | |
|     qDebug() << Q_FUNC_INFO << "Can't add null element";
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Adds the specified \a element to the layout as an inset with free positioning/sizing (\ref
 | |
|   setInsetAlignment is initialized with \ref ipFree). The position and size is set to \a
 | |
|   rect.
 | |
|   
 | |
|   \a rect is given in fractions of the whole inset layout rect. So an inset with rect (0, 0, 1, 1)
 | |
|   will span the entire layout. An inset with rect (0.6, 0.1, 0.35, 0.35) will be in the top right
 | |
|   corner of the layout, with 35% width and height of the parent layout.
 | |
|   
 | |
|   \see addElement(QCPLayoutElement *element, Qt::Alignment alignment)
 | |
| */
 | |
| void QCPLayoutInset::addElement(QCPLayoutElement *element, const QRectF &rect)
 | |
| {
 | |
|   if (element)
 | |
|   {
 | |
|     if (element->layout()) // remove from old layout first
 | |
|       element->layout()->take(element);
 | |
|     mElements.append(element);
 | |
|     mInsetPlacement.append(ipFree);
 | |
|     mInsetAlignment.append(Qt::AlignRight|Qt::AlignTop);
 | |
|     mInsetRect.append(rect);
 | |
|     adoptElement(element);
 | |
|   } else
 | |
|     qDebug() << Q_FUNC_INFO << "Can't add null element";
 | |
| }
 | |
| /* end of 'src/layout.cpp' */
 | |
| 
 | |
| 
 | |
| /* including file 'src/lineending.cpp', size 11536                           */
 | |
| /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPLineEnding
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPLineEnding
 | |
|   \brief Handles the different ending decorations for line-like items
 | |
|   
 | |
|   \image html QCPLineEnding.png "The various ending styles currently supported"
 | |
|   
 | |
|   For every ending a line-like item has, an instance of this class exists. For example, QCPItemLine
 | |
|   has two endings which can be set with QCPItemLine::setHead and QCPItemLine::setTail.
 | |
|  
 | |
|   The styles themselves are defined via the enum QCPLineEnding::EndingStyle. Most decorations can
 | |
|   be modified regarding width and length, see \ref setWidth and \ref setLength. The direction of
 | |
|   the ending decoration (e.g. direction an arrow is pointing) is controlled by the line-like item.
 | |
|   For example, when both endings of a QCPItemLine are set to be arrows, they will point to opposite
 | |
|   directions, e.g. "outward". This can be changed by \ref setInverted, which would make the
 | |
|   respective arrow point inward.
 | |
|   
 | |
|   Note that due to the overloaded QCPLineEnding constructor, you may directly specify a
 | |
|   QCPLineEnding::EndingStyle where actually a QCPLineEnding is expected, e.g.
 | |
|   \snippet documentation/doc-code-snippets/mainwindow.cpp qcplineending-sethead
 | |
| */
 | |
| 
 | |
| /*!
 | |
|   Creates a QCPLineEnding instance with default values (style \ref esNone).
 | |
| */
 | |
| QCPLineEnding::QCPLineEnding() :
 | |
|   mStyle(esNone),
 | |
|   mWidth(8),
 | |
|   mLength(10),
 | |
|   mInverted(false)
 | |
| {
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Creates a QCPLineEnding instance with the specified values.
 | |
| */
 | |
| QCPLineEnding::QCPLineEnding(QCPLineEnding::EndingStyle style, double width, double length, bool inverted) :
 | |
|   mStyle(style),
 | |
|   mWidth(width),
 | |
|   mLength(length),
 | |
|   mInverted(inverted)
 | |
| {
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the style of the ending decoration.
 | |
| */
 | |
| void QCPLineEnding::setStyle(QCPLineEnding::EndingStyle style)
 | |
| {
 | |
|   mStyle = style;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the width of the ending decoration, if the style supports it. On arrows, for example, the
 | |
|   width defines the size perpendicular to the arrow's pointing direction.
 | |
|   
 | |
|   \see setLength
 | |
| */
 | |
| void QCPLineEnding::setWidth(double width)
 | |
| {
 | |
|   mWidth = width;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the length of the ending decoration, if the style supports it. On arrows, for example, the
 | |
|   length defines the size in pointing direction.
 | |
|   
 | |
|   \see setWidth
 | |
| */
 | |
| void QCPLineEnding::setLength(double length)
 | |
| {
 | |
|   mLength = length;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets whether the ending decoration shall be inverted. For example, an arrow decoration will point
 | |
|   inward when \a inverted is set to true.
 | |
| 
 | |
|   Note that also the \a width direction is inverted. For symmetrical ending styles like arrows or
 | |
|   discs, this doesn't make a difference. However, asymmetric styles like \ref esHalfBar are
 | |
|   affected by it, which can be used to control to which side the half bar points to.
 | |
| */
 | |
| void QCPLineEnding::setInverted(bool inverted)
 | |
| {
 | |
|   mInverted = inverted;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns the maximum pixel radius the ending decoration might cover, starting from the position
 | |
|   the decoration is drawn at (typically a line ending/\ref QCPItemPosition of an item).
 | |
|   
 | |
|   This is relevant for clipping. Only omit painting of the decoration when the position where the
 | |
|   decoration is supposed to be drawn is farther away from the clipping rect than the returned
 | |
|   distance.
 | |
| */
 | |
| double QCPLineEnding::boundingDistance() const
 | |
| {
 | |
|   switch (mStyle)
 | |
|   {
 | |
|     case esNone:
 | |
|       return 0;
 | |
|       
 | |
|     case esFlatArrow:
 | |
|     case esSpikeArrow:
 | |
|     case esLineArrow:
 | |
|     case esSkewedBar:
 | |
|       return qSqrt(mWidth*mWidth+mLength*mLength); // items that have width and length
 | |
|       
 | |
|     case esDisc:
 | |
|     case esSquare:
 | |
|     case esDiamond:
 | |
|     case esBar:
 | |
|     case esHalfBar:
 | |
|       return mWidth*1.42; // items that only have a width -> width*sqrt(2)
 | |
| 
 | |
|   }
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Starting from the origin of this line ending (which is style specific), returns the length
 | |
|   covered by the line ending symbol, in backward direction.
 | |
|   
 | |
|   For example, the \ref esSpikeArrow has a shorter real length than a \ref esFlatArrow, even if
 | |
|   both have the same \ref setLength value, because the spike arrow has an inward curved back, which
 | |
|   reduces the length along its center axis (the drawing origin for arrows is at the tip).
 | |
|   
 | |
|   This function is used for precise, style specific placement of line endings, for example in
 | |
|   QCPAxes.
 | |
| */
 | |
| double QCPLineEnding::realLength() const
 | |
| {
 | |
|   switch (mStyle)
 | |
|   {
 | |
|     case esNone:
 | |
|     case esLineArrow:
 | |
|     case esSkewedBar:
 | |
|     case esBar:
 | |
|     case esHalfBar:
 | |
|       return 0;
 | |
|       
 | |
|     case esFlatArrow:
 | |
|       return mLength;
 | |
|       
 | |
|     case esDisc:
 | |
|     case esSquare:
 | |
|     case esDiamond:
 | |
|       return mWidth*0.5;
 | |
|       
 | |
|     case esSpikeArrow:
 | |
|       return mLength*0.8;
 | |
|   }
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Draws the line ending with the specified \a painter at the position \a pos. The direction of the
 | |
|   line ending is controlled with \a dir.
 | |
| */
 | |
| void QCPLineEnding::draw(QCPPainter *painter, const QCPVector2D &pos, const QCPVector2D &dir) const
 | |
| {
 | |
|   if (mStyle == esNone)
 | |
|     return;
 | |
|   
 | |
|   QCPVector2D lengthVec = dir.normalized() * mLength*(mInverted ? -1 : 1);
 | |
|   if (lengthVec.isNull())
 | |
|     lengthVec = QCPVector2D(1, 0);
 | |
|   QCPVector2D widthVec = dir.normalized().perpendicular() * mWidth*0.5*(mInverted ? -1 : 1);
 | |
|   
 | |
|   QPen penBackup = painter->pen();
 | |
|   QBrush brushBackup = painter->brush();
 | |
|   QPen miterPen = penBackup;
 | |
|   miterPen.setJoinStyle(Qt::MiterJoin); // to make arrow heads spikey
 | |
|   QBrush brush(painter->pen().color(), Qt::SolidPattern);
 | |
|   switch (mStyle)
 | |
|   {
 | |
|     case esNone: break;
 | |
|     case esFlatArrow:
 | |
|     {
 | |
|       QPointF points[3] = {pos.toPointF(),
 | |
|                            (pos-lengthVec+widthVec).toPointF(),
 | |
|                            (pos-lengthVec-widthVec).toPointF()
 | |
|                           };
 | |
|       painter->setPen(miterPen);
 | |
|       painter->setBrush(brush);
 | |
|       painter->drawConvexPolygon(points, 3);
 | |
|       painter->setBrush(brushBackup);
 | |
|       painter->setPen(penBackup);
 | |
|       break;
 | |
|     }
 | |
|     case esSpikeArrow:
 | |
|     {
 | |
|       QPointF points[4] = {pos.toPointF(),
 | |
|                            (pos-lengthVec+widthVec).toPointF(),
 | |
|                            (pos-lengthVec*0.8).toPointF(),
 | |
|                            (pos-lengthVec-widthVec).toPointF()
 | |
|                           };
 | |
|       painter->setPen(miterPen);
 | |
|       painter->setBrush(brush);
 | |
|       painter->drawConvexPolygon(points, 4);
 | |
|       painter->setBrush(brushBackup);
 | |
|       painter->setPen(penBackup);
 | |
|       break;
 | |
|     }
 | |
|     case esLineArrow:
 | |
|     {
 | |
|       QPointF points[3] = {(pos-lengthVec+widthVec).toPointF(),
 | |
|                            pos.toPointF(),
 | |
|                            (pos-lengthVec-widthVec).toPointF()
 | |
|                           };
 | |
|       painter->setPen(miterPen);
 | |
|       painter->drawPolyline(points, 3);
 | |
|       painter->setPen(penBackup);
 | |
|       break;
 | |
|     }
 | |
|     case esDisc:
 | |
|     {
 | |
|       painter->setBrush(brush);
 | |
|       painter->drawEllipse(pos.toPointF(),  mWidth*0.5, mWidth*0.5);
 | |
|       painter->setBrush(brushBackup);
 | |
|       break;
 | |
|     }
 | |
|     case esSquare:
 | |
|     {
 | |
|       QCPVector2D widthVecPerp = widthVec.perpendicular();
 | |
|       QPointF points[4] = {(pos-widthVecPerp+widthVec).toPointF(),
 | |
|                            (pos-widthVecPerp-widthVec).toPointF(),
 | |
|                            (pos+widthVecPerp-widthVec).toPointF(),
 | |
|                            (pos+widthVecPerp+widthVec).toPointF()
 | |
|                           };
 | |
|       painter->setPen(miterPen);
 | |
|       painter->setBrush(brush);
 | |
|       painter->drawConvexPolygon(points, 4);
 | |
|       painter->setBrush(brushBackup);
 | |
|       painter->setPen(penBackup);
 | |
|       break;
 | |
|     }
 | |
|     case esDiamond:
 | |
|     {
 | |
|       QCPVector2D widthVecPerp = widthVec.perpendicular();
 | |
|       QPointF points[4] = {(pos-widthVecPerp).toPointF(),
 | |
|                            (pos-widthVec).toPointF(),
 | |
|                            (pos+widthVecPerp).toPointF(),
 | |
|                            (pos+widthVec).toPointF()
 | |
|                           };
 | |
|       painter->setPen(miterPen);
 | |
|       painter->setBrush(brush);
 | |
|       painter->drawConvexPolygon(points, 4);
 | |
|       painter->setBrush(brushBackup);
 | |
|       painter->setPen(penBackup);
 | |
|       break;
 | |
|     }
 | |
|     case esBar:
 | |
|     {
 | |
|       painter->drawLine((pos+widthVec).toPointF(), (pos-widthVec).toPointF());
 | |
|       break;
 | |
|     }
 | |
|     case esHalfBar:
 | |
|     {
 | |
|       painter->drawLine((pos+widthVec).toPointF(), pos.toPointF());
 | |
|       break;
 | |
|     }
 | |
|     case esSkewedBar:
 | |
|     {
 | |
|       if (qFuzzyIsNull(painter->pen().widthF()) && !painter->modes().testFlag(QCPPainter::pmNonCosmetic))
 | |
|       {
 | |
|         // if drawing with cosmetic pen (perfectly thin stroke, happens only in vector exports), draw bar exactly on tip of line
 | |
|         painter->drawLine((pos+widthVec+lengthVec*0.2*(mInverted?-1:1)).toPointF(),
 | |
|                           (pos-widthVec-lengthVec*0.2*(mInverted?-1:1)).toPointF());
 | |
|       } else
 | |
|       {
 | |
|         // if drawing with thick (non-cosmetic) pen, shift bar a little in line direction to prevent line from sticking through bar slightly
 | |
|         painter->drawLine((pos+widthVec+lengthVec*0.2*(mInverted?-1:1)+dir.normalized()*qMax(1.0f, (float)painter->pen().widthF())*0.5f).toPointF(),
 | |
|                           (pos-widthVec-lengthVec*0.2*(mInverted?-1:1)+dir.normalized()*qMax(1.0f, (float)painter->pen().widthF())*0.5f).toPointF());
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   \overload
 | |
|   
 | |
|   Draws the line ending. The direction is controlled with the \a angle parameter in radians.
 | |
| */
 | |
| void QCPLineEnding::draw(QCPPainter *painter, const QCPVector2D &pos, double angle) const
 | |
| {
 | |
|   draw(painter, pos, QCPVector2D(qCos(angle), qSin(angle)));
 | |
| }
 | |
| /* end of 'src/lineending.cpp' */
 | |
| 
 | |
| 
 | |
| /* including file 'src/axis/axisticker.cpp', size 18664                      */
 | |
| /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPAxisTicker
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| /*! \class QCPAxisTicker
 | |
|   \brief The base class tick generator used by QCPAxis to create tick positions and tick labels
 | |
|   
 | |
|   Each QCPAxis has an internal QCPAxisTicker (or a subclass) in order to generate tick positions
 | |
|   and tick labels for the current axis range. The ticker of an axis can be set via \ref
 | |
|   QCPAxis::setTicker. Since that method takes a <tt>QSharedPointer<QCPAxisTicker></tt>, multiple
 | |
|   axes can share the same ticker instance.
 | |
|   
 | |
|   This base class generates normal tick coordinates and numeric labels for linear axes. It picks a
 | |
|   reasonable tick step (the separation between ticks) which results in readable tick labels. The
 | |
|   number of ticks that should be approximately generated can be set via \ref setTickCount.
 | |
|   Depending on the current tick step strategy (\ref setTickStepStrategy), the algorithm either
 | |
|   sacrifices readability to better match the specified tick count (\ref
 | |
|   QCPAxisTicker::tssMeetTickCount) or relaxes the tick count in favor of better tick steps (\ref
 | |
|   QCPAxisTicker::tssReadability), which is the default.
 | |
|   
 | |
|   The following more specialized axis ticker subclasses are available, see details in the
 | |
|   respective class documentation:
 | |
|   
 | |
|   <center>
 | |
|   <table>
 | |
|   <tr><td style="text-align:right; padding: 0 1em">QCPAxisTickerFixed</td><td>\image html axisticker-fixed.png</td></tr>
 | |
|   <tr><td style="text-align:right; padding: 0 1em">QCPAxisTickerLog</td><td>\image html axisticker-log.png</td></tr>
 | |
|   <tr><td style="text-align:right; padding: 0 1em">QCPAxisTickerPi</td><td>\image html axisticker-pi.png</td></tr>
 | |
|   <tr><td style="text-align:right; padding: 0 1em">QCPAxisTickerText</td><td>\image html axisticker-text.png</td></tr>
 | |
|   <tr><td style="text-align:right; padding: 0 1em">QCPAxisTickerDateTime</td><td>\image html axisticker-datetime.png</td></tr>
 | |
|   <tr><td style="text-align:right; padding: 0 1em">QCPAxisTickerTime</td><td>\image html axisticker-time.png
 | |
|     \image html axisticker-time2.png</td></tr>
 | |
|   </table>
 | |
|   </center>
 | |
|   
 | |
|   \section axisticker-subclassing Creating own axis tickers
 | |
|   
 | |
|   Creating own axis tickers can be achieved very easily by sublassing QCPAxisTicker and
 | |
|   reimplementing some or all of the available virtual methods.
 | |
| 
 | |
|   In the simplest case you might wish to just generate different tick steps than the other tickers,
 | |
|   so you only reimplement the method \ref getTickStep. If you additionally want control over the
 | |
|   string that will be shown as tick label, reimplement \ref getTickLabel.
 | |
|   
 | |
|   If you wish to have complete control, you can generate the tick vectors and tick label vectors
 | |
|   yourself by reimplementing \ref createTickVector and \ref createLabelVector. The default
 | |
|   implementations use the previously mentioned virtual methods \ref getTickStep and \ref
 | |
|   getTickLabel, but your reimplementations don't necessarily need to do so. For example in the case
 | |
|   of unequal tick steps, the method \ref getTickStep loses its usefulness and can be ignored.
 | |
|   
 | |
|   The sub tick count between major ticks can be controlled with \ref getSubTickCount. Full sub tick
 | |
|   placement control is obtained by reimplementing \ref createSubTickVector.
 | |
|   
 | |
|   See the documentation of all these virtual methods in QCPAxisTicker for detailed information
 | |
|   about the parameters and expected return values.
 | |
| */
 | |
| 
 | |
| /*!
 | |
|   Constructs the ticker and sets reasonable default values. Axis tickers are commonly created
 | |
|   managed by a QSharedPointer, which then can be passed to QCPAxis::setTicker.
 | |
| */
 | |
| QCPAxisTicker::QCPAxisTicker() :
 | |
|   mTickStepStrategy(tssReadability),
 | |
|   mTickCount(5),
 | |
|   mTickOrigin(0)
 | |
| {
 | |
| }
 | |
| 
 | |
| QCPAxisTicker::~QCPAxisTicker()
 | |
| {
 | |
|   
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets which strategy the axis ticker follows when choosing the size of the tick step. For the
 | |
|   available strategies, see \ref TickStepStrategy.
 | |
| */
 | |
| void QCPAxisTicker::setTickStepStrategy(QCPAxisTicker::TickStepStrategy strategy)
 | |
| {
 | |
|   mTickStepStrategy = strategy;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets how many ticks this ticker shall aim to generate across the axis range. Note that \a count
 | |
|   is not guaranteed to be matched exactly, as generating readable tick intervals may conflict with
 | |
|   the requested number of ticks.
 | |
| 
 | |
|   Whether the readability has priority over meeting the requested \a count can be specified with
 | |
|   \ref setTickStepStrategy.
 | |
| */
 | |
| void QCPAxisTicker::setTickCount(int count)
 | |
| {
 | |
|   if (count > 0)
 | |
|     mTickCount = count;
 | |
|   else
 | |
|     qDebug() << Q_FUNC_INFO << "tick count must be greater than zero:" << count;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the mathematical coordinate (or "offset") of the zeroth tick. This tick coordinate is just a
 | |
|   concept and doesn't need to be inside the currently visible axis range.
 | |
|   
 | |
|   By default \a origin is zero, which for example yields ticks {-5, 0, 5, 10, 15,...} when the tick
 | |
|   step is five. If \a origin is now set to 1 instead, the correspondingly generated ticks would be
 | |
|   {-4, 1, 6, 11, 16,...}.
 | |
| */
 | |
| void QCPAxisTicker::setTickOrigin(double origin)
 | |
| {
 | |
|   mTickOrigin = origin;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   This is the method called by QCPAxis in order to actually generate tick coordinates (\a ticks),
 | |
|   tick label strings (\a tickLabels) and sub tick coordinates (\a subTicks).
 | |
|   
 | |
|   The ticks are generated for the specified \a range. The generated labels typically follow the
 | |
|   specified \a locale, \a formatChar and number \a precision, however this might be different (or
 | |
|   even irrelevant) for certain QCPAxisTicker subclasses.
 | |
|   
 | |
|   The output parameter \a ticks is filled with the generated tick positions in axis coordinates.
 | |
|   The output parameters \a subTicks and \a tickLabels are optional (set them to 0 if not needed)
 | |
|   and are respectively filled with sub tick coordinates, and tick label strings belonging to \a
 | |
|   ticks by index.
 | |
| */
 | |
| void QCPAxisTicker::generate(const QCPRange &range, const QLocale &locale, QChar formatChar, int precision, QVector<double> &ticks, QVector<double> *subTicks, QVector<QString> *tickLabels)
 | |
| {
 | |
|   // generate (major) ticks:
 | |
|   double tickStep = getTickStep(range);
 | |
|   ticks = createTickVector(tickStep, range);
 | |
|   trimTicks(range, ticks, true); // trim ticks to visible range plus one outer tick on each side (incase a subclass createTickVector creates more)
 | |
|   
 | |
|   // generate sub ticks between major ticks:
 | |
|   if (subTicks)
 | |
|   {
 | |
|     if (ticks.size() > 0)
 | |
|     {
 | |
|       *subTicks = createSubTickVector(getSubTickCount(tickStep), ticks);
 | |
|       trimTicks(range, *subTicks, false);
 | |
|     } else
 | |
|       *subTicks = QVector<double>();
 | |
|   }
 | |
|   
 | |
|   // finally trim also outliers (no further clipping happens in axis drawing):
 | |
|   trimTicks(range, ticks, false);
 | |
|   // generate labels for visible ticks if requested:
 | |
|   if (tickLabels)
 | |
|     *tickLabels = createLabelVector(ticks, locale, formatChar, precision);
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Takes the entire currently visible axis range and returns a sensible tick step in
 | |
|   order to provide readable tick labels as well as a reasonable number of tick counts (see \ref
 | |
|   setTickCount, \ref setTickStepStrategy).
 | |
|   
 | |
|   If a QCPAxisTicker subclass only wants a different tick step behaviour than the default
 | |
|   implementation, it should reimplement this method. See \ref cleanMantissa for a possible helper
 | |
|   function.
 | |
| */
 | |
| double QCPAxisTicker::getTickStep(const QCPRange &range)
 | |
| {
 | |
|   double exactStep = range.size()/(double)(mTickCount+1e-10); // mTickCount ticks on average, the small addition is to prevent jitter on exact integers
 | |
|   return cleanMantissa(exactStep);
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Takes the \a tickStep, i.e. the distance between two consecutive ticks, and returns
 | |
|   an appropriate number of sub ticks for that specific tick step.
 | |
|   
 | |
|   Note that a returned sub tick count of e.g. 4 will split each tick interval into 5 sections.
 | |
| */
 | |
| int QCPAxisTicker::getSubTickCount(double tickStep)
 | |
| {
 | |
|   int result = 1; // default to 1, if no proper value can be found
 | |
|   
 | |
|   // separate integer and fractional part of mantissa:
 | |
|   double epsilon = 0.01;
 | |
|   double intPartf;
 | |
|   int intPart;
 | |
|   double fracPart = modf(getMantissa(tickStep), &intPartf);
 | |
|   intPart = intPartf;
 | |
|   
 | |
|   // handle cases with (almost) integer mantissa:
 | |
|   if (fracPart < epsilon || 1.0-fracPart < epsilon)
 | |
|   {
 | |
|     if (1.0-fracPart < epsilon)
 | |
|       ++intPart;
 | |
|     switch (intPart)
 | |
|     {
 | |
|       case 1: result = 4; break; // 1.0 -> 0.2 substep
 | |
|       case 2: result = 3; break; // 2.0 -> 0.5 substep
 | |
|       case 3: result = 2; break; // 3.0 -> 1.0 substep
 | |
|       case 4: result = 3; break; // 4.0 -> 1.0 substep
 | |
|       case 5: result = 4; break; // 5.0 -> 1.0 substep
 | |
|       case 6: result = 2; break; // 6.0 -> 2.0 substep
 | |
|       case 7: result = 6; break; // 7.0 -> 1.0 substep
 | |
|       case 8: result = 3; break; // 8.0 -> 2.0 substep
 | |
|       case 9: result = 2; break; // 9.0 -> 3.0 substep
 | |
|     }
 | |
|   } else
 | |
|   {
 | |
|     // handle cases with significantly fractional mantissa:
 | |
|     if (qAbs(fracPart-0.5) < epsilon) // *.5 mantissa
 | |
|     {
 | |
|       switch (intPart)
 | |
|       {
 | |
|         case 1: result = 2; break; // 1.5 -> 0.5 substep
 | |
|         case 2: result = 4; break; // 2.5 -> 0.5 substep
 | |
|         case 3: result = 4; break; // 3.5 -> 0.7 substep
 | |
|         case 4: result = 2; break; // 4.5 -> 1.5 substep
 | |
|         case 5: result = 4; break; // 5.5 -> 1.1 substep (won't occur with default getTickStep from here on)
 | |
|         case 6: result = 4; break; // 6.5 -> 1.3 substep
 | |
|         case 7: result = 2; break; // 7.5 -> 2.5 substep
 | |
|         case 8: result = 4; break; // 8.5 -> 1.7 substep
 | |
|         case 9: result = 4; break; // 9.5 -> 1.9 substep
 | |
|       }
 | |
|     }
 | |
|     // if mantissa fraction isn't 0.0 or 0.5, don't bother finding good sub tick marks, leave default
 | |
|   }
 | |
|   
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   This method returns the tick label string as it should be printed under the \a tick coordinate.
 | |
|   If a textual number is returned, it should respect the provided \a locale, \a formatChar and \a
 | |
|   precision.
 | |
|   
 | |
|   If the returned value contains exponentials of the form "2e5" and beautifully typeset powers is
 | |
|   enabled in the QCPAxis number format (\ref QCPAxis::setNumberFormat), the exponential part will
 | |
|   be formatted accordingly using multiplication symbol and superscript during rendering of the
 | |
|   label automatically.
 | |
| */
 | |
| QString QCPAxisTicker::getTickLabel(double tick, const QLocale &locale, QChar formatChar, int precision)
 | |
| {
 | |
|   return locale.toString(tick, formatChar.toLatin1(), precision);
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns a vector containing all coordinates of sub ticks that should be drawn. It generates \a
 | |
|   subTickCount sub ticks between each tick pair given in \a ticks.
 | |
|   
 | |
|   If a QCPAxisTicker subclass needs maximal control over the generated sub ticks, it should
 | |
|   reimplement this method. Depending on the purpose of the subclass it doesn't necessarily need to
 | |
|   base its result on \a subTickCount or \a ticks.
 | |
| */
 | |
| QVector<double> QCPAxisTicker::createSubTickVector(int subTickCount, const QVector<double> &ticks)
 | |
| {
 | |
|   QVector<double> result;
 | |
|   if (subTickCount <= 0 || ticks.size() < 2)
 | |
|     return result;
 | |
|   
 | |
|   result.reserve((ticks.size()-1)*subTickCount);
 | |
|   for (int i=1; i<ticks.size(); ++i)
 | |
|   {
 | |
|     double subTickStep = (ticks.at(i)-ticks.at(i-1))/(double)(subTickCount+1);
 | |
|     for (int k=1; k<=subTickCount; ++k)
 | |
|       result.append(ticks.at(i-1) + k*subTickStep);
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns a vector containing all coordinates of ticks that should be drawn. The default
 | |
|   implementation generates ticks with a spacing of \a tickStep (mathematically starting at the tick
 | |
|   step origin, see \ref setTickOrigin) distributed over the passed \a range.
 | |
|   
 | |
|   In order for the axis ticker to generate proper sub ticks, it is necessary that the first and
 | |
|   last tick coordinates returned by this method are just below/above the provided \a range.
 | |
|   Otherwise the outer intervals won't contain any sub ticks.
 | |
|   
 | |
|   If a QCPAxisTicker subclass needs maximal control over the generated ticks, it should reimplement
 | |
|   this method. Depending on the purpose of the subclass it doesn't necessarily need to base its
 | |
|   result on \a tickStep, e.g. when the ticks are spaced unequally like in the case of
 | |
|   QCPAxisTickerLog.
 | |
| */
 | |
| QVector<double> QCPAxisTicker::createTickVector(double tickStep, const QCPRange &range)
 | |
| {
 | |
|   QVector<double> result;
 | |
|   // Generate tick positions according to tickStep:
 | |
|   qint64 firstStep = floor((range.lower-mTickOrigin)/tickStep); // do not use qFloor here, or we'll lose 64 bit precision
 | |
|   qint64 lastStep = ceil((range.upper-mTickOrigin)/tickStep); // do not use qCeil here, or we'll lose 64 bit precision
 | |
|   int tickcount = lastStep-firstStep+1;
 | |
|   if (tickcount < 0) tickcount = 0;
 | |
|   result.resize(tickcount);
 | |
|   for (int i=0; i<tickcount; ++i)
 | |
|     result[i] = mTickOrigin + (firstStep+i)*tickStep;
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns a vector containing all tick label strings corresponding to the tick coordinates provided
 | |
|   in \a ticks. The default implementation calls \ref getTickLabel to generate the respective
 | |
|   strings.
 | |
|   
 | |
|   It is possible but uncommon for QCPAxisTicker subclasses to reimplement this method, as
 | |
|   reimplementing \ref getTickLabel often achieves the intended result easier.
 | |
| */
 | |
| QVector<QString> QCPAxisTicker::createLabelVector(const QVector<double> &ticks, const QLocale &locale, QChar formatChar, int precision)
 | |
| {
 | |
|   QVector<QString> result;
 | |
|   result.reserve(ticks.size());
 | |
|   for (int i=0; i<ticks.size(); ++i)
 | |
|     result.append(getTickLabel(ticks.at(i), locale, formatChar, precision));
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Removes tick coordinates from \a ticks which lie outside the specified \a range. If \a
 | |
|   keepOneOutlier is true, it preserves one tick just outside the range on both sides, if present.
 | |
|   
 | |
|   The passed \a ticks must be sorted in ascending order.
 | |
| */
 | |
| void QCPAxisTicker::trimTicks(const QCPRange &range, QVector<double> &ticks, bool keepOneOutlier) const
 | |
| {
 | |
|   bool lowFound = false;
 | |
|   bool highFound = false;
 | |
|   int lowIndex = 0;
 | |
|   int highIndex = -1;
 | |
|   
 | |
|   for (int i=0; i < ticks.size(); ++i)
 | |
|   {
 | |
|     if (ticks.at(i) >= range.lower)
 | |
|     {
 | |
|       lowFound = true;
 | |
|       lowIndex = i;
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
|   for (int i=ticks.size()-1; i >= 0; --i)
 | |
|   {
 | |
|     if (ticks.at(i) <= range.upper)
 | |
|     {
 | |
|       highFound = true;
 | |
|       highIndex = i;
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   if (highFound && lowFound)
 | |
|   {
 | |
|     int trimFront = qMax(0, lowIndex-(keepOneOutlier ? 1 : 0));
 | |
|     int trimBack = qMax(0, ticks.size()-(keepOneOutlier ? 2 : 1)-highIndex);
 | |
|     if (trimFront > 0 || trimBack > 0)
 | |
|       ticks = ticks.mid(trimFront, ticks.size()-trimFront-trimBack);
 | |
|   } else // all ticks are either all below or all above the range
 | |
|     ticks.clear();
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns the coordinate contained in \a candidates which is closest to the provided \a target.
 | |
|   
 | |
|   This method assumes \a candidates is not empty and sorted in ascending order.
 | |
| */
 | |
| double QCPAxisTicker::pickClosest(double target, const QVector<double> &candidates) const
 | |
| {
 | |
|   if (candidates.size() == 1)
 | |
|     return candidates.first();
 | |
|   QVector<double>::const_iterator it = std::lower_bound(candidates.constBegin(), candidates.constEnd(), target);
 | |
|   if (it == candidates.constEnd())
 | |
|     return *(it-1);
 | |
|   else if (it == candidates.constBegin())
 | |
|     return *it;
 | |
|   else
 | |
|     return target-*(it-1) < *it-target ? *(it-1) : *it;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns the decimal mantissa of \a input. Optionally, if \a magnitude is not set to zero, it also
 | |
|   returns the magnitude of \a input as a power of 10.
 | |
|   
 | |
|   For example, an input of 142.6 will return a mantissa of 1.426 and a magnitude of 100.
 | |
| */
 | |
| double QCPAxisTicker::getMantissa(double input, double *magnitude) const
 | |
| {
 | |
|   const double mag = qPow(10.0, qFloor(qLn(input)/qLn(10.0)));
 | |
|   if (magnitude) *magnitude = mag;
 | |
|   return input/mag;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns a number that is close to \a input but has a clean, easier human readable mantissa. How
 | |
|   strongly the mantissa is altered, and thus how strong the result deviates from the original \a
 | |
|   input, depends on the current tick step strategy (see \ref setTickStepStrategy).
 | |
| */
 | |
| double QCPAxisTicker::cleanMantissa(double input) const
 | |
| {
 | |
|   double magnitude;
 | |
|   const double mantissa = getMantissa(input, &magnitude);
 | |
|   switch (mTickStepStrategy)
 | |
|   {
 | |
|     case tssReadability:
 | |
|     {
 | |
|       return pickClosest(mantissa, QVector<double>() << 1.0 << 2.0 << 2.5 << 5.0 << 10.0)*magnitude;
 | |
|     }
 | |
|     case tssMeetTickCount:
 | |
|     {
 | |
|       // 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
 | |
|       if (mantissa <= 5.0)
 | |
|         return (int)(mantissa*2)/2.0*magnitude; // round digit after decimal point to 0.5
 | |
|       else
 | |
|         return (int)(mantissa/2.0)*2.0*magnitude; // round to first digit in multiples of 2
 | |
|     }
 | |
|   }
 | |
|   return input;
 | |
| }
 | |
| /* end of 'src/axis/axisticker.cpp' */
 | |
| 
 | |
| 
 | |
| /* including file 'src/axis/axistickerdatetime.cpp', size 14443              */
 | |
| /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPAxisTickerDateTime
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| /*! \class QCPAxisTickerDateTime
 | |
|   \brief Specialized axis ticker for calendar dates and times as axis ticks
 | |
|   
 | |
|   \image html axisticker-datetime.png
 | |
|   
 | |
|   This QCPAxisTicker subclass generates ticks that correspond to real calendar dates and times. The
 | |
|   plot axis coordinate is interpreted as Unix Time, so seconds since Epoch (January 1, 1970, 00:00
 | |
|   UTC). This is also used for example by QDateTime in the <tt>toTime_t()/setTime_t()</tt> methods
 | |
|   with a precision of one second. Since Qt 4.7, millisecond accuracy can be obtained from QDateTime
 | |
|   by using <tt>QDateTime::fromMSecsSinceEpoch()/1000.0</tt>. The static methods \ref dateTimeToKey
 | |
|   and \ref keyToDateTime conveniently perform this conversion achieving a precision of one
 | |
|   millisecond on all Qt versions.
 | |
|   
 | |
|   The format of the date/time display in the tick labels is controlled with \ref setDateTimeFormat.
 | |
|   If a different time spec (time zone) shall be used, see \ref setDateTimeSpec.
 | |
|   
 | |
|   This ticker produces unequal tick spacing in order to provide intuitive date and time-of-day
 | |
|   ticks. For example, if the axis range spans a few years such that there is one tick per year,
 | |
|   ticks will be positioned on 1. January of every year. This is intuitive but, due to leap years,
 | |
|   will result in slightly unequal tick intervals (visually unnoticeable). The same can be seen in
 | |
|   the image above: even though the number of days varies month by month, this ticker generates
 | |
|   ticks on the same day of each month.
 | |
|   
 | |
|   If you would like to change the date/time that is used as a (mathematical) starting date for the
 | |
|   ticks, use the \ref setTickOrigin(const QDateTime &origin) method overload, which takes a
 | |
|   QDateTime. If you pass 15. July, 9:45 to this method, the yearly ticks will end up on 15. July at
 | |
|   9:45 of every year.
 | |
|   
 | |
|   The ticker can be created and assigned to an axis like this:
 | |
|   \snippet documentation/doc-image-generator/mainwindow.cpp axistickerdatetime-creation
 | |
|   
 | |
|   \note If you rather wish to display relative times in terms of days, hours, minutes, seconds and
 | |
|   milliseconds, and are not interested in the intricacies of real calendar dates with months and
 | |
|   (leap) years, have a look at QCPAxisTickerTime instead.
 | |
| */
 | |
| 
 | |
| /*!
 | |
|   Constructs the ticker and sets reasonable default values. Axis tickers are commonly created
 | |
|   managed by a QSharedPointer, which then can be passed to QCPAxis::setTicker.
 | |
| */
 | |
| QCPAxisTickerDateTime::QCPAxisTickerDateTime() :
 | |
|   mDateTimeFormat(QLatin1String("hh:mm:ss\ndd.MM.yy")),
 | |
|   mDateTimeSpec(Qt::LocalTime),
 | |
|   mDateStrategy(dsNone)
 | |
| {
 | |
|   setTickCount(4);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the format in which dates and times are displayed as tick labels. For details about the \a
 | |
|   format string, see the documentation of QDateTime::toString().
 | |
|   
 | |
|   Newlines can be inserted with "\n".
 | |
|   
 | |
|   \see setDateTimeSpec
 | |
| */
 | |
| void QCPAxisTickerDateTime::setDateTimeFormat(const QString &format)
 | |
| {
 | |
|   mDateTimeFormat = format;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the time spec that is used for creating the tick labels from corresponding dates/times.
 | |
| 
 | |
|   The default value of QDateTime objects (and also QCPAxisTickerDateTime) is
 | |
|   <tt>Qt::LocalTime</tt>. However, if the date time values passed to QCustomPlot (e.g. in the form
 | |
|   of axis ranges or keys of a plottable) are given in the UTC spec, set \a spec to <tt>Qt::UTC</tt>
 | |
|   to get the correct axis labels.
 | |
|   
 | |
|   \see setDateTimeFormat
 | |
| */
 | |
| void QCPAxisTickerDateTime::setDateTimeSpec(Qt::TimeSpec spec)
 | |
| {
 | |
|   mDateTimeSpec = spec;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the tick origin (see \ref QCPAxisTicker::setTickOrigin) in seconds since Epoch (1. Jan 1970,
 | |
|   00:00 UTC). For the date time ticker it might be more intuitive to use the overload which
 | |
|   directly takes a QDateTime, see \ref setTickOrigin(const QDateTime &origin).
 | |
|   
 | |
|   This is useful to define the month/day/time recurring at greater tick interval steps. For
 | |
|   example, If you pass 15. July, 9:45 to this method and the tick interval happens to be one tick
 | |
|   per year, the ticks will end up on 15. July at 9:45 of every year.
 | |
| */
 | |
| void QCPAxisTickerDateTime::setTickOrigin(double origin)
 | |
| {
 | |
|   QCPAxisTicker::setTickOrigin(origin);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the tick origin (see \ref QCPAxisTicker::setTickOrigin) as a QDateTime \a origin.
 | |
|   
 | |
|   This is useful to define the month/day/time recurring at greater tick interval steps. For
 | |
|   example, If you pass 15. July, 9:45 to this method and the tick interval happens to be one tick
 | |
|   per year, the ticks will end up on 15. July at 9:45 of every year.
 | |
| */
 | |
| void QCPAxisTickerDateTime::setTickOrigin(const QDateTime &origin)
 | |
| {
 | |
|   setTickOrigin(dateTimeToKey(origin));
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns a sensible tick step with intervals appropriate for a date-time-display, such as weekly,
 | |
|   monthly, bi-monthly, etc.
 | |
|   
 | |
|   Note that this tick step isn't used exactly when generating the tick vector in \ref
 | |
|   createTickVector, but only as a guiding value requiring some correction for each individual tick
 | |
|   interval. Otherwise this would lead to unintuitive date displays, e.g. jumping between first day
 | |
|   in the month to the last day in the previous month from tick to tick, due to the non-uniform
 | |
|   length of months. The same problem arises with leap years.
 | |
|   
 | |
|   \seebaseclassmethod
 | |
| */
 | |
| double QCPAxisTickerDateTime::getTickStep(const QCPRange &range)
 | |
| {
 | |
|   double result = range.size()/(double)(mTickCount+1e-10); // mTickCount ticks on average, the small addition is to prevent jitter on exact integers
 | |
|   
 | |
|   mDateStrategy = dsNone;
 | |
|   if (result < 1) // ideal tick step is below 1 second -> use normal clean mantissa algorithm in units of seconds
 | |
|   {
 | |
|     result = cleanMantissa(result);
 | |
|   } else if (result < 86400*30.4375*12) // below a year
 | |
|   {
 | |
|     result = pickClosest(result, QVector<double>()
 | |
|                              << 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
 | |
|                              << 3600*2 << 3600*3 << 3600*6 << 3600*12 << 3600*24 // hour to day range
 | |
|                              << 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)
 | |
|     if (result > 86400*30.4375-1) // month tick intervals or larger
 | |
|       mDateStrategy = dsUniformDayInMonth;
 | |
|     else if (result > 3600*24-1) // day tick intervals or larger
 | |
|       mDateStrategy = dsUniformTimeInDay;
 | |
|   } else // more than a year, go back to normal clean mantissa algorithm but in units of years
 | |
|   {
 | |
|     const double secondsPerYear = 86400*30.4375*12; // average including leap years
 | |
|     result = cleanMantissa(result/secondsPerYear)*secondsPerYear;
 | |
|     mDateStrategy = dsUniformDayInMonth;
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns a sensible sub tick count with intervals appropriate for a date-time-display, such as weekly,
 | |
|   monthly, bi-monthly, etc.
 | |
|   
 | |
|   \seebaseclassmethod
 | |
| */
 | |
| int QCPAxisTickerDateTime::getSubTickCount(double tickStep)
 | |
| {
 | |
|   int result = QCPAxisTicker::getSubTickCount(tickStep);
 | |
|   switch (qRound(tickStep)) // hand chosen subticks for specific minute/hour/day/week/month range (as specified in getTickStep)
 | |
|   {
 | |
|     case 5*60: result = 4; break;
 | |
|     case 10*60: result = 1; break;
 | |
|     case 15*60: result = 2; break;
 | |
|     case 30*60: result = 1; break;
 | |
|     case 60*60: result = 3; break;
 | |
|     case 3600*2: result = 3; break;
 | |
|     case 3600*3: result = 2; break;
 | |
|     case 3600*6: result = 1; break;
 | |
|     case 3600*12: result = 3; break;
 | |
|     case 3600*24: result = 3; break;
 | |
|     case 86400*2: result = 1; break;
 | |
|     case 86400*5: result = 4; break;
 | |
|     case 86400*7: result = 6; break;
 | |
|     case 86400*14: result = 1; break;
 | |
|     case (int)(86400*30.4375+0.5): result = 3; break;
 | |
|     case (int)(86400*30.4375*2+0.5): result = 1; break;
 | |
|     case (int)(86400*30.4375*3+0.5): result = 2; break;
 | |
|     case (int)(86400*30.4375*6+0.5): result = 5; break;
 | |
|     case (int)(86400*30.4375*12+0.5): result = 3; break;
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Generates a date/time tick label for tick coordinate \a tick, based on the currently set format
 | |
|   (\ref setDateTimeFormat) and time spec (\ref setDateTimeSpec).
 | |
|   
 | |
|   \seebaseclassmethod
 | |
| */
 | |
| QString QCPAxisTickerDateTime::getTickLabel(double tick, const QLocale &locale, QChar formatChar, int precision)
 | |
| {
 | |
|   Q_UNUSED(precision)
 | |
|   Q_UNUSED(formatChar)
 | |
|   return locale.toString(keyToDateTime(tick).toTimeSpec(mDateTimeSpec), mDateTimeFormat);
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Uses the passed \a tickStep as a guiding value and applies corrections in order to obtain
 | |
|   non-uniform tick intervals but intuitive tick labels, e.g. falling on the same day of each month.
 | |
|   
 | |
|   \seebaseclassmethod
 | |
| */
 | |
| QVector<double> QCPAxisTickerDateTime::createTickVector(double tickStep, const QCPRange &range)
 | |
| {
 | |
|   QVector<double> result = QCPAxisTicker::createTickVector(tickStep, range);
 | |
|   if (!result.isEmpty())
 | |
|   {
 | |
|     if (mDateStrategy == dsUniformTimeInDay)
 | |
|     {
 | |
|       QDateTime uniformDateTime = keyToDateTime(mTickOrigin); // the time of this datetime will be set for all other ticks, if possible
 | |
|       QDateTime tickDateTime;
 | |
|       for (int i=0; i<result.size(); ++i)
 | |
|       {
 | |
|         tickDateTime = keyToDateTime(result.at(i));
 | |
|         tickDateTime.setTime(uniformDateTime.time());
 | |
|         result[i] = dateTimeToKey(tickDateTime);
 | |
|       }
 | |
|     } else if (mDateStrategy == dsUniformDayInMonth)
 | |
|     {
 | |
|       QDateTime uniformDateTime = keyToDateTime(mTickOrigin); // this day (in month) and time will be set for all other ticks, if possible
 | |
|       QDateTime tickDateTime;
 | |
|       for (int i=0; i<result.size(); ++i)
 | |
|       {
 | |
|         tickDateTime = keyToDateTime(result.at(i));
 | |
|         tickDateTime.setTime(uniformDateTime.time());
 | |
|         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)
 | |
|         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
 | |
|           tickDateTime = tickDateTime.addMonths(1);
 | |
|         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
 | |
|           tickDateTime = tickDateTime.addMonths(-1);
 | |
|         tickDateTime.setDate(QDate(tickDateTime.date().year(), tickDateTime.date().month(), thisUniformDay));
 | |
|         result[i] = dateTimeToKey(tickDateTime);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   A convenience method which turns \a key (in seconds since Epoch 1. Jan 1970, 00:00 UTC) into a
 | |
|   QDateTime object. This can be used to turn axis coordinates to actual QDateTimes.
 | |
|   
 | |
|   The accuracy achieved by this method is one millisecond, irrespective of the used Qt version (it
 | |
|   works around the lack of a QDateTime::fromMSecsSinceEpoch in Qt 4.6)
 | |
|   
 | |
|   \see dateTimeToKey
 | |
| */
 | |
| QDateTime QCPAxisTickerDateTime::keyToDateTime(double key)
 | |
| {
 | |
| # if QT_VERSION < QT_VERSION_CHECK(4, 7, 0)
 | |
|   return QDateTime::fromTime_t(key).addMSecs((key-(qint64)key)*1000);
 | |
| # else
 | |
|   return QDateTime::fromMSecsSinceEpoch(key*1000.0);
 | |
| # endif
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   A convenience method which turns a QDateTime object into a double value that corresponds to
 | |
|   seconds since Epoch (1. Jan 1970, 00:00 UTC). This is the format used as axis coordinates by
 | |
|   QCPAxisTickerDateTime.
 | |
|   
 | |
|   The accuracy achieved by this method is one millisecond, irrespective of the used Qt version (it
 | |
|   works around the lack of a QDateTime::toMSecsSinceEpoch in Qt 4.6)
 | |
|   
 | |
|   \see keyToDateTime
 | |
| */
 | |
| double QCPAxisTickerDateTime::dateTimeToKey(const QDateTime dateTime)
 | |
| {
 | |
| # if QT_VERSION < QT_VERSION_CHECK(4, 7, 0)
 | |
|   return dateTime.toTime_t()+dateTime.time().msec()/1000.0;
 | |
| # else
 | |
|   return dateTime.toMSecsSinceEpoch()/1000.0;
 | |
| # endif
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   A convenience method which turns a QDate object into a double value that corresponds to
 | |
|   seconds since Epoch (1. Jan 1970, 00:00 UTC). This is the format used as axis coordinates by
 | |
|   QCPAxisTickerDateTime.
 | |
|   
 | |
|   \see keyToDateTime
 | |
| */
 | |
| double QCPAxisTickerDateTime::dateTimeToKey(const QDate date)
 | |
| {
 | |
| # if QT_VERSION < QT_VERSION_CHECK(4, 7, 0)
 | |
|   return QDateTime(date).toTime_t();
 | |
| # else
 | |
|   return QDateTime(date).toMSecsSinceEpoch()/1000.0;
 | |
| # endif
 | |
| }
 | |
| /* end of 'src/axis/axistickerdatetime.cpp' */
 | |
| 
 | |
| 
 | |
| /* including file 'src/axis/axistickertime.cpp', size 11747                  */
 | |
| /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPAxisTickerTime
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| /*! \class QCPAxisTickerTime
 | |
|   \brief Specialized axis ticker for time spans in units of milliseconds to days
 | |
|   
 | |
|   \image html axisticker-time.png
 | |
|   
 | |
|   This QCPAxisTicker subclass generates ticks that corresponds to time intervals.
 | |
|   
 | |
|   The format of the time display in the tick labels is controlled with \ref setTimeFormat and \ref
 | |
|   setFieldWidth. The time coordinate is in the unit of seconds with respect to the time coordinate
 | |
|   zero. Unlike with QCPAxisTickerDateTime, the ticks don't correspond to a specific calendar date
 | |
|   and time.
 | |
|   
 | |
|   The time can be displayed in milliseconds, seconds, minutes, hours and days. Depending on the
 | |
|   largest available unit in the format specified with \ref setTimeFormat, any time spans above will
 | |
|   be carried in that largest unit. So for example if the format string is "%m:%s" and a tick at
 | |
|   coordinate value 7815 (being 2 hours, 10 minutes and 15 seconds) is created, the resulting tick
 | |
|   label will show "130:15" (130 minutes, 15 seconds). If the format string is "%h:%m:%s", the hour
 | |
|   unit will be used and the label will thus be "02:10:15". Negative times with respect to the axis
 | |
|   zero will carry a leading minus sign.
 | |
|   
 | |
|   The ticker can be created and assigned to an axis like this:
 | |
|   \snippet documentation/doc-image-generator/mainwindow.cpp axistickertime-creation
 | |
|   
 | |
|   Here is an example of a time axis providing time information in days, hours and minutes. Due to
 | |
|   the axis range spanning a few days and the wanted tick count (\ref setTickCount), the ticker
 | |
|   decided to use tick steps of 12 hours:
 | |
|   
 | |
|   \image html axisticker-time2.png
 | |
|   
 | |
|   The format string for this example is
 | |
|   \snippet documentation/doc-image-generator/mainwindow.cpp axistickertime-creation-2
 | |
|   
 | |
|   \note If you rather wish to display calendar dates and times, have a look at QCPAxisTickerDateTime
 | |
|   instead.
 | |
| */
 | |
| 
 | |
| /*!
 | |
|   Constructs the ticker and sets reasonable default values. Axis tickers are commonly created
 | |
|   managed by a QSharedPointer, which then can be passed to QCPAxis::setTicker.
 | |
| */
 | |
| QCPAxisTickerTime::QCPAxisTickerTime() :
 | |
|   mTimeFormat(QLatin1String("%h:%m:%s")),
 | |
|   mSmallestUnit(tuSeconds),
 | |
|   mBiggestUnit(tuHours)
 | |
| {
 | |
|   setTickCount(4);
 | |
|   mFieldWidth[tuMilliseconds] = 3;
 | |
|   mFieldWidth[tuSeconds] = 2;
 | |
|   mFieldWidth[tuMinutes] = 2;
 | |
|   mFieldWidth[tuHours] = 2;
 | |
|   mFieldWidth[tuDays] = 1;
 | |
|   
 | |
|   mFormatPattern[tuMilliseconds] = QLatin1String("%z");
 | |
|   mFormatPattern[tuSeconds] = QLatin1String("%s");
 | |
|   mFormatPattern[tuMinutes] = QLatin1String("%m");
 | |
|   mFormatPattern[tuHours] = QLatin1String("%h");
 | |
|   mFormatPattern[tuDays] = QLatin1String("%d");
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the format that will be used to display time in the tick labels.
 | |
|   
 | |
|   The available patterns are:
 | |
|   - %%z for milliseconds
 | |
|   - %%s for seconds
 | |
|   - %%m for minutes
 | |
|   - %%h for hours
 | |
|   - %%d for days
 | |
|   
 | |
|   The field width (zero padding) can be controlled for each unit with \ref setFieldWidth.
 | |
|   
 | |
|   The largest unit that appears in \a format will carry all the remaining time of a certain tick
 | |
|   coordinate, even if it overflows the natural limit of the unit. For example, if %%m is the
 | |
|   largest unit it might become larger than 59 in order to consume larger time values. If on the
 | |
|   other hand %%h is available, the minutes will wrap around to zero after 59 and the time will
 | |
|   carry to the hour digit.
 | |
| */
 | |
| void QCPAxisTickerTime::setTimeFormat(const QString &format)
 | |
| {
 | |
|   mTimeFormat = format;
 | |
|   
 | |
|   // determine smallest and biggest unit in format, to optimize unit replacement and allow biggest
 | |
|   // unit to consume remaining time of a tick value and grow beyond its modulo (e.g. min > 59)
 | |
|   mSmallestUnit = tuMilliseconds;
 | |
|   mBiggestUnit = tuMilliseconds;
 | |
|   bool hasSmallest = false;
 | |
|   for (int i = tuMilliseconds; i <= tuDays; ++i)
 | |
|   {
 | |
|     TimeUnit unit = static_cast<TimeUnit>(i);
 | |
|     if (mTimeFormat.contains(mFormatPattern.value(unit)))
 | |
|     {
 | |
|       if (!hasSmallest)
 | |
|       {
 | |
|         mSmallestUnit = unit;
 | |
|         hasSmallest = true;
 | |
|       }
 | |
|       mBiggestUnit = unit;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the field widh of the specified \a unit to be \a width digits, when displayed in the tick
 | |
|   label. If the number for the specific unit is shorter than \a width, it will be padded with an
 | |
|   according number of zeros to the left in order to reach the field width.
 | |
|   
 | |
|   \see setTimeFormat
 | |
| */
 | |
| void QCPAxisTickerTime::setFieldWidth(QCPAxisTickerTime::TimeUnit unit, int width)
 | |
| {
 | |
|   mFieldWidth[unit] = qMax(width, 1);
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Returns the tick step appropriate for time displays, depending on the provided \a range and the
 | |
|   smallest available time unit in the current format (\ref setTimeFormat). For example if the unit
 | |
|   of seconds isn't available in the format, this method will not generate steps (like 2.5 minutes)
 | |
|   that require sub-minute precision to be displayed correctly.
 | |
|   
 | |
|   \seebaseclassmethod
 | |
| */
 | |
| double QCPAxisTickerTime::getTickStep(const QCPRange &range)
 | |
| {
 | |
|   double result = range.size()/(double)(mTickCount+1e-10); // mTickCount ticks on average, the small addition is to prevent jitter on exact integers
 | |
|   
 | |
|   if (result < 1) // ideal tick step is below 1 second -> use normal clean mantissa algorithm in units of seconds
 | |
|   {
 | |
|     if (mSmallestUnit == tuMilliseconds)
 | |
|       result = qMax(cleanMantissa(result), 0.001); // smallest tick step is 1 millisecond
 | |
|     else // have no milliseconds available in format, so stick with 1 second tickstep
 | |
|       result = 1.0;
 | |
|   } else if (result < 3600*24) // below a day
 | |
|   {
 | |
|     // the filling of availableSteps seems a bit contorted but it fills in a sorted fashion and thus saves a post-fill sorting run
 | |
|     QVector<double> availableSteps;
 | |
|     // seconds range:
 | |
|     if (mSmallestUnit <= tuSeconds)
 | |
|       availableSteps << 1;
 | |
|     if (mSmallestUnit == tuMilliseconds)
 | |
|       availableSteps << 2.5; // only allow half second steps if milliseconds are there to display it
 | |
|     else if (mSmallestUnit == tuSeconds)
 | |
|       availableSteps << 2;
 | |
|     if (mSmallestUnit <= tuSeconds)
 | |
|       availableSteps << 5 << 10 << 15 << 30;
 | |
|     // minutes range:
 | |
|     if (mSmallestUnit <= tuMinutes)
 | |
|       availableSteps << 1*60;
 | |
|     if (mSmallestUnit <= tuSeconds)
 | |
|       availableSteps << 2.5*60; // only allow half minute steps if seconds are there to display it
 | |
|     else if (mSmallestUnit == tuMinutes)
 | |
|       availableSteps << 2*60;
 | |
|     if (mSmallestUnit <= tuMinutes)
 | |
|       availableSteps << 5*60 << 10*60 << 15*60 << 30*60;
 | |
|     // hours range:
 | |
|     if (mSmallestUnit <= tuHours)
 | |
|       availableSteps << 1*3600 << 2*3600 << 3*3600 << 6*3600 << 12*3600 << 24*3600;
 | |
|     // pick available step that is most appropriate to approximate ideal step:
 | |
|     result = pickClosest(result, availableSteps);
 | |
|   } else // more than a day, go back to normal clean mantissa algorithm but in units of days
 | |
|   {
 | |
|     const double secondsPerDay = 3600*24;
 | |
|     result = cleanMantissa(result/secondsPerDay)*secondsPerDay;
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Returns the sub tick count appropriate for the provided \a tickStep and time displays.
 | |
|   
 | |
|   \seebaseclassmethod
 | |
| */
 | |
| int QCPAxisTickerTime::getSubTickCount(double tickStep)
 | |
| {
 | |
|   int result = QCPAxisTicker::getSubTickCount(tickStep);
 | |
|   switch (qRound(tickStep)) // hand chosen subticks for specific minute/hour/day range (as specified in getTickStep)
 | |
|   {
 | |
|     case 5*60: result = 4; break;
 | |
|     case 10*60: result = 1; break;
 | |
|     case 15*60: result = 2; break;
 | |
|     case 30*60: result = 1; break;
 | |
|     case 60*60: result = 3; break;
 | |
|     case 3600*2: result = 3; break;
 | |
|     case 3600*3: result = 2; break;
 | |
|     case 3600*6: result = 1; break;
 | |
|     case 3600*12: result = 3; break;
 | |
|     case 3600*24: result = 3; break;
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns the tick label corresponding to the provided \a tick and the configured format and field
 | |
|   widths (\ref setTimeFormat, \ref setFieldWidth).
 | |
|   
 | |
|   \seebaseclassmethod
 | |
| */
 | |
| QString QCPAxisTickerTime::getTickLabel(double tick, const QLocale &locale, QChar formatChar, int precision)
 | |
| {
 | |
|   Q_UNUSED(precision)
 | |
|   Q_UNUSED(formatChar)
 | |
|   Q_UNUSED(locale)
 | |
|   bool negative = tick < 0;
 | |
|   if (negative) tick *= -1;
 | |
|   double values[tuDays+1]; // contains the msec/sec/min/... value with its respective modulo (e.g. minute 0..59)
 | |
|   double restValues[tuDays+1]; // contains the msec/sec/min/... value as if it's the largest available unit and thus consumes the remaining time
 | |
|   
 | |
|   restValues[tuMilliseconds] = tick*1000;
 | |
|   values[tuMilliseconds] = modf(restValues[tuMilliseconds]/1000, &restValues[tuSeconds])*1000;
 | |
|   values[tuSeconds] = modf(restValues[tuSeconds]/60, &restValues[tuMinutes])*60;
 | |
|   values[tuMinutes] = modf(restValues[tuMinutes]/60, &restValues[tuHours])*60;
 | |
|   values[tuHours] = modf(restValues[tuHours]/24, &restValues[tuDays])*24;
 | |
|   // no need to set values[tuDays] because days are always a rest value (there is no higher unit so it consumes all remaining time)
 | |
|   
 | |
|   QString result = mTimeFormat;
 | |
|   for (int i = mSmallestUnit; i <= mBiggestUnit; ++i)
 | |
|   {
 | |
|     TimeUnit iUnit = static_cast<TimeUnit>(i);
 | |
|     replaceUnit(result, iUnit, qRound(iUnit == mBiggestUnit ? restValues[iUnit] : values[iUnit]));
 | |
|   }
 | |
|   if (negative)
 | |
|     result.prepend(QLatin1Char('-'));
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Replaces all occurrences of the format pattern belonging to \a unit in \a text with the specified
 | |
|   \a value, using the field width as specified with \ref setFieldWidth for the \a unit.
 | |
| */
 | |
| void QCPAxisTickerTime::replaceUnit(QString &text, QCPAxisTickerTime::TimeUnit unit, int value) const
 | |
| {
 | |
|   QString valueStr = QString::number(value);
 | |
|   while (valueStr.size() < mFieldWidth.value(unit))
 | |
|     valueStr.prepend(QLatin1Char('0'));
 | |
|   
 | |
|   text.replace(mFormatPattern.value(unit), valueStr);
 | |
| }
 | |
| /* end of 'src/axis/axistickertime.cpp' */
 | |
| 
 | |
| 
 | |
| /* including file 'src/axis/axistickerfixed.cpp', size 5583                  */
 | |
| /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPAxisTickerFixed
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| /*! \class QCPAxisTickerFixed
 | |
|   \brief Specialized axis ticker with a fixed tick step
 | |
|   
 | |
|   \image html axisticker-fixed.png
 | |
|   
 | |
|   This QCPAxisTicker subclass generates ticks with a fixed tick step set with \ref setTickStep. It
 | |
|   is also possible to allow integer multiples and integer powers of the specified tick step with
 | |
|   \ref setScaleStrategy.
 | |
|   
 | |
|   A typical application of this ticker is to make an axis only display integers, by setting the
 | |
|   tick step of the ticker to 1.0 and the scale strategy to \ref ssMultiples.
 | |
|   
 | |
|   Another case is when a certain number has a special meaning and axis ticks should only appear at
 | |
|   multiples of that value. In this case you might also want to consider \ref QCPAxisTickerPi
 | |
|   because despite the name it is not limited to only pi symbols/values.
 | |
|   
 | |
|   The ticker can be created and assigned to an axis like this:
 | |
|   \snippet documentation/doc-image-generator/mainwindow.cpp axistickerfixed-creation
 | |
| */
 | |
| 
 | |
| /*!
 | |
|   Constructs the ticker and sets reasonable default values. Axis tickers are commonly created
 | |
|   managed by a QSharedPointer, which then can be passed to QCPAxis::setTicker.
 | |
| */
 | |
| QCPAxisTickerFixed::QCPAxisTickerFixed() :
 | |
|   mTickStep(1.0),
 | |
|   mScaleStrategy(ssNone)
 | |
| {
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the fixed tick interval to \a step.
 | |
|   
 | |
|   The axis ticker will only use this tick step when generating axis ticks. This might cause a very
 | |
|   high tick density and overlapping labels if the axis range is zoomed out. Using \ref
 | |
|   setScaleStrategy it is possible to relax the fixed step and also allow multiples or powers of \a
 | |
|   step. This will enable the ticker to reduce the number of ticks to a reasonable amount (see \ref
 | |
|   setTickCount).
 | |
| */
 | |
| void QCPAxisTickerFixed::setTickStep(double step)
 | |
| {
 | |
|   if (step > 0)
 | |
|     mTickStep = step;
 | |
|   else
 | |
|     qDebug() << Q_FUNC_INFO << "tick step must be greater than zero:" << step;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets whether the specified tick step (\ref setTickStep) is absolutely fixed or whether
 | |
|   modifications may be applied to it before calculating the finally used tick step, such as
 | |
|   permitting multiples or powers. See \ref ScaleStrategy for details.
 | |
|   
 | |
|   The default strategy is \ref ssNone, which means the tick step is absolutely fixed.
 | |
| */
 | |
| void QCPAxisTickerFixed::setScaleStrategy(QCPAxisTickerFixed::ScaleStrategy strategy)
 | |
| {
 | |
|   mScaleStrategy = strategy;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Determines the actually used tick step from the specified tick step and scale strategy (\ref
 | |
|   setTickStep, \ref setScaleStrategy).
 | |
|   
 | |
|   This method either returns the specified tick step exactly, or, if the scale strategy is not \ref
 | |
|   ssNone, a modification of it to allow varying the number of ticks in the current axis range.
 | |
|   
 | |
|   \seebaseclassmethod
 | |
| */
 | |
| double QCPAxisTickerFixed::getTickStep(const QCPRange &range)
 | |
| {
 | |
|   switch (mScaleStrategy)
 | |
|   {
 | |
|     case ssNone:
 | |
|     {
 | |
|       return mTickStep;
 | |
|     }
 | |
|     case ssMultiples:
 | |
|     {
 | |
|       double exactStep = range.size()/(double)(mTickCount+1e-10); // mTickCount ticks on average, the small addition is to prevent jitter on exact integers
 | |
|       if (exactStep < mTickStep)
 | |
|         return mTickStep;
 | |
|       else
 | |
|         return (qint64)(cleanMantissa(exactStep/mTickStep)+0.5)*mTickStep;
 | |
|     }
 | |
|     case ssPowers:
 | |
|     {
 | |
|       double exactStep = range.size()/(double)(mTickCount+1e-10); // mTickCount ticks on average, the small addition is to prevent jitter on exact integers
 | |
|       return qPow(mTickStep, (int)(qLn(exactStep)/qLn(mTickStep)+0.5));
 | |
|     }
 | |
|   }
 | |
|   return mTickStep;
 | |
| }
 | |
| /* end of 'src/axis/axistickerfixed.cpp' */
 | |
| 
 | |
| 
 | |
| /* including file 'src/axis/axistickertext.cpp', size 8653                   */
 | |
| /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPAxisTickerText
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| /*! \class QCPAxisTickerText
 | |
|   \brief Specialized axis ticker which allows arbitrary labels at specified coordinates
 | |
|   
 | |
|   \image html axisticker-text.png
 | |
|   
 | |
|   This QCPAxisTicker subclass generates ticks which can be directly specified by the user as
 | |
|   coordinates and associated strings. They can be passed as a whole with \ref setTicks or one at a
 | |
|   time with \ref addTick. Alternatively you can directly access the internal storage via \ref ticks
 | |
|   and modify the tick/label data there.
 | |
|   
 | |
|   This is useful for cases where the axis represents categories rather than numerical values.
 | |
|   
 | |
|   If you are updating the ticks of this ticker regularly and in a dynamic fasion (e.g. dependent on
 | |
|   the axis range), it is a sign that you should probably create an own ticker by subclassing
 | |
|   QCPAxisTicker, instead of using this one.
 | |
|   
 | |
|   The ticker can be created and assigned to an axis like this:
 | |
|   \snippet documentation/doc-image-generator/mainwindow.cpp axistickertext-creation
 | |
| */
 | |
| 
 | |
| /* start of documentation of inline functions */
 | |
| 
 | |
| /*! \fn QMap<double, QString> &QCPAxisTickerText::ticks()
 | |
|   
 | |
|   Returns a non-const reference to the internal map which stores the tick coordinates and their
 | |
|   labels.
 | |
| 
 | |
|   You can access the map directly in order to add, remove or manipulate ticks, as an alternative to
 | |
|   using the methods provided by QCPAxisTickerText, such as \ref setTicks and \ref addTick.
 | |
| */
 | |
| 
 | |
| /* end of documentation of inline functions */
 | |
| 
 | |
| /*!
 | |
|   Constructs the ticker and sets reasonable default values. Axis tickers are commonly created
 | |
|   managed by a QSharedPointer, which then can be passed to QCPAxis::setTicker.
 | |
| */
 | |
| QCPAxisTickerText::QCPAxisTickerText() :
 | |
|   mSubTickCount(0)
 | |
| {
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Sets the ticks that shall appear on the axis. The map key of \a ticks corresponds to the axis
 | |
|   coordinate, and the map value is the string that will appear as tick label.
 | |
|   
 | |
|   An alternative to manipulate ticks is to directly access the internal storage with the \ref ticks
 | |
|   getter.
 | |
|   
 | |
|   \see addTicks, addTick, clear
 | |
| */
 | |
| void QCPAxisTickerText::setTicks(const QMap<double, QString> &ticks)
 | |
| {
 | |
|   mTicks = ticks;
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Sets the ticks that shall appear on the axis. The entries of \a positions correspond to the axis
 | |
|   coordinates, and the entries of \a labels are the respective strings that will appear as tick
 | |
|   labels.
 | |
|   
 | |
|   \see addTicks, addTick, clear
 | |
| */
 | |
| void QCPAxisTickerText::setTicks(const QVector<double> &positions, const QVector<QString> labels)
 | |
| {
 | |
|   clear();
 | |
|   addTicks(positions, labels);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the number of sub ticks that shall appear between ticks. For QCPAxisTickerText, there is no
 | |
|   automatic sub tick count calculation. So if sub ticks are needed, they must be configured with this
 | |
|   method.
 | |
| */
 | |
| void QCPAxisTickerText::setSubTickCount(int subTicks)
 | |
| {
 | |
|   if (subTicks >= 0)
 | |
|     mSubTickCount = subTicks;
 | |
|   else
 | |
|     qDebug() << Q_FUNC_INFO << "sub tick count can't be negative:" << subTicks;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Clears all ticks.
 | |
|   
 | |
|   An alternative to manipulate ticks is to directly access the internal storage with the \ref ticks
 | |
|   getter.
 | |
|   
 | |
|   \see setTicks, addTicks, addTick
 | |
| */
 | |
| void QCPAxisTickerText::clear()
 | |
| {
 | |
|   mTicks.clear();
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Adds a single tick to the axis at the given axis coordinate \a position, with the provided tick \a
 | |
|   label.
 | |
|   
 | |
|   \see addTicks, setTicks, clear
 | |
| */
 | |
| void QCPAxisTickerText::addTick(double position, QString label)
 | |
| {
 | |
|   mTicks.insert(position, label);
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Adds the provided \a ticks to the ones already existing. The map key of \a ticks corresponds to
 | |
|   the axis coordinate, and the map value is the string that will appear as tick label.
 | |
|   
 | |
|   An alternative to manipulate ticks is to directly access the internal storage with the \ref ticks
 | |
|   getter.
 | |
|   
 | |
|   \see addTick, setTicks, clear
 | |
| */
 | |
| void QCPAxisTickerText::addTicks(const QMap<double, QString> &ticks)
 | |
| {
 | |
|   mTicks.unite(ticks);
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Adds the provided ticks to the ones already existing. The entries of \a positions correspond to
 | |
|   the axis coordinates, and the entries of \a labels are the respective strings that will appear as
 | |
|   tick labels.
 | |
|   
 | |
|   An alternative to manipulate ticks is to directly access the internal storage with the \ref ticks
 | |
|   getter.
 | |
|   
 | |
|   \see addTick, setTicks, clear
 | |
| */
 | |
| void QCPAxisTickerText::addTicks(const QVector<double> &positions, const QVector<QString> &labels)
 | |
| {
 | |
|   if (positions.size() != labels.size())
 | |
|     qDebug() << Q_FUNC_INFO << "passed unequal length vectors for positions and labels:" << positions.size() << labels.size();
 | |
|   int n = qMin(positions.size(), labels.size());
 | |
|   for (int i=0; i<n; ++i)
 | |
|     mTicks.insert(positions.at(i), labels.at(i));
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Since the tick coordinates are provided externally, this method implementation does nothing.
 | |
|   
 | |
|   \seebaseclassmethod
 | |
| */
 | |
| double QCPAxisTickerText::getTickStep(const QCPRange &range)
 | |
| {
 | |
|   // text axis ticker has manual tick positions, so doesn't need this method
 | |
|   Q_UNUSED(range)
 | |
|   return 1.0;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the sub tick count that was configured with \ref setSubTickCount.
 | |
|   
 | |
|   \seebaseclassmethod
 | |
| */
 | |
| int QCPAxisTickerText::getSubTickCount(double tickStep)
 | |
| {
 | |
|   Q_UNUSED(tickStep)
 | |
|   return mSubTickCount;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the tick label which corresponds to the key \a tick in the internal tick storage. Since
 | |
|   the labels are provided externally, \a locale, \a formatChar, and \a precision are ignored.
 | |
|   
 | |
|   \seebaseclassmethod
 | |
| */
 | |
| QString QCPAxisTickerText::getTickLabel(double tick, const QLocale &locale, QChar formatChar, int precision)
 | |
| {
 | |
|   Q_UNUSED(locale)
 | |
|   Q_UNUSED(formatChar)
 | |
|   Q_UNUSED(precision)
 | |
|   return mTicks.value(tick);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the externally provided tick coordinates which are in the specified \a range. If
 | |
|   available, one tick above and below the range is provided in addition, to allow possible sub tick
 | |
|   calculation. The parameter \a tickStep is ignored.
 | |
|   
 | |
|   \seebaseclassmethod
 | |
| */
 | |
| QVector<double> QCPAxisTickerText::createTickVector(double tickStep, const QCPRange &range)
 | |
| {
 | |
|   Q_UNUSED(tickStep)
 | |
|   QVector<double> result;
 | |
|   if (mTicks.isEmpty())
 | |
|     return result;
 | |
|   
 | |
|   QMap<double, QString>::const_iterator start = mTicks.lowerBound(range.lower);
 | |
|   QMap<double, QString>::const_iterator end = mTicks.upperBound(range.upper);
 | |
|   // this method should try to give one tick outside of range so proper subticks can be generated:
 | |
|   if (start != mTicks.constBegin()) --start;
 | |
|   if (end != mTicks.constEnd()) ++end;
 | |
|   for (QMap<double, QString>::const_iterator it = start; it != end; ++it)
 | |
|     result.append(it.key());
 | |
|   
 | |
|   return result;
 | |
| }
 | |
| /* end of 'src/axis/axistickertext.cpp' */
 | |
| 
 | |
| 
 | |
| /* including file 'src/axis/axistickerpi.cpp', size 11170                    */
 | |
| /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPAxisTickerPi
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| /*! \class QCPAxisTickerPi
 | |
|   \brief Specialized axis ticker to display ticks in units of an arbitrary constant, for example pi
 | |
|   
 | |
|   \image html axisticker-pi.png
 | |
|   
 | |
|   This QCPAxisTicker subclass generates ticks that are expressed with respect to a given symbolic
 | |
|   constant with a numerical value specified with \ref setPiValue and an appearance in the tick
 | |
|   labels specified with \ref setPiSymbol.
 | |
|   
 | |
|   Ticks may be generated at fractions of the symbolic constant. How these fractions appear in the
 | |
|   tick label can be configured with \ref setFractionStyle.
 | |
|   
 | |
|   The ticker can be created and assigned to an axis like this:
 | |
|   \snippet documentation/doc-image-generator/mainwindow.cpp axistickerpi-creation
 | |
| */
 | |
| 
 | |
| /*!
 | |
|   Constructs the ticker and sets reasonable default values. Axis tickers are commonly created
 | |
|   managed by a QSharedPointer, which then can be passed to QCPAxis::setTicker.
 | |
| */
 | |
| QCPAxisTickerPi::QCPAxisTickerPi() :
 | |
|   mPiSymbol(QLatin1String(" ")+QChar(0x03C0)),
 | |
|   mPiValue(M_PI),
 | |
|   mPeriodicity(0),
 | |
|   mFractionStyle(fsUnicodeFractions),
 | |
|   mPiTickStep(0)
 | |
| {
 | |
|   setTickCount(4);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets how the symbol part (which is always a suffix to the number) shall appear in the axis tick
 | |
|   label.
 | |
|   
 | |
|   If a space shall appear between the number and the symbol, make sure the space is contained in \a
 | |
|   symbol.
 | |
| */
 | |
| void QCPAxisTickerPi::setPiSymbol(QString symbol)
 | |
| {
 | |
|   mPiSymbol = symbol;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the numerical value that the symbolic constant has.
 | |
| 
 | |
|   This will be used to place the appropriate fractions of the symbol at the respective axis
 | |
|   coordinates.
 | |
| */
 | |
| void QCPAxisTickerPi::setPiValue(double pi)
 | |
| {
 | |
|   mPiValue = pi;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets whether the axis labels shall appear periodicly and if so, at which multiplicity of the
 | |
|   symbolic constant.
 | |
|   
 | |
|   To disable periodicity, set \a multiplesOfPi to zero.
 | |
|   
 | |
|   For example, an axis that identifies 0 with 2pi would set \a multiplesOfPi to two.
 | |
| */
 | |
| void QCPAxisTickerPi::setPeriodicity(int multiplesOfPi)
 | |
| {
 | |
|   mPeriodicity = qAbs(multiplesOfPi);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets how the numerical/fractional part preceding the symbolic constant is displayed in tick
 | |
|   labels. See \ref FractionStyle for the various options.
 | |
| */
 | |
| void QCPAxisTickerPi::setFractionStyle(QCPAxisTickerPi::FractionStyle style)
 | |
| {
 | |
|   mFractionStyle = style;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns the tick step, using the constant's value (\ref setPiValue) as base unit. In consequence
 | |
|   the numerical/fractional part preceding the symbolic constant is made to have a readable
 | |
|   mantissa.
 | |
|   
 | |
|   \seebaseclassmethod
 | |
| */
 | |
| double QCPAxisTickerPi::getTickStep(const QCPRange &range)
 | |
| {
 | |
|   mPiTickStep = range.size()/mPiValue/(double)(mTickCount+1e-10); // mTickCount ticks on average, the small addition is to prevent jitter on exact integers
 | |
|   mPiTickStep = cleanMantissa(mPiTickStep);
 | |
|   return mPiTickStep*mPiValue;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns the sub tick count, using the constant's value (\ref setPiValue) as base unit. In
 | |
|   consequence the sub ticks divide the numerical/fractional part preceding the symbolic constant
 | |
|   reasonably, and not the total tick coordinate.
 | |
|   
 | |
|   \seebaseclassmethod
 | |
| */
 | |
| int QCPAxisTickerPi::getSubTickCount(double tickStep)
 | |
| {
 | |
|   return QCPAxisTicker::getSubTickCount(tickStep/mPiValue);
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns the tick label as a fractional/numerical part and a symbolic string as suffix. The
 | |
|   formatting of the fraction is done according to the specified \ref setFractionStyle. The appended
 | |
|   symbol is specified with \ref setPiSymbol.
 | |
|   
 | |
|   \seebaseclassmethod
 | |
| */
 | |
| QString QCPAxisTickerPi::getTickLabel(double tick, const QLocale &locale, QChar formatChar, int precision)
 | |
| {
 | |
|   double tickInPis = tick/mPiValue;
 | |
|   if (mPeriodicity > 0)
 | |
|     tickInPis = fmod(tickInPis, mPeriodicity);
 | |
|   
 | |
|   if (mFractionStyle != fsFloatingPoint && mPiTickStep > 0.09 && mPiTickStep < 50)
 | |
|   {
 | |
|     // simply construct fraction from decimal like 1.234 -> 1234/1000 and then simplify fraction, smaller digits are irrelevant due to mPiTickStep conditional above
 | |
|     int denominator = 1000;
 | |
|     int numerator = qRound(tickInPis*denominator);
 | |
|     simplifyFraction(numerator, denominator);
 | |
|     if (qAbs(numerator) == 1 && denominator == 1)
 | |
|       return (numerator < 0 ? QLatin1String("-") : QLatin1String("")) + mPiSymbol.trimmed();
 | |
|     else if (numerator == 0)
 | |
|       return QLatin1String("0");
 | |
|     else
 | |
|       return fractionToString(numerator, denominator) + mPiSymbol;
 | |
|   } else
 | |
|   {
 | |
|     if (qFuzzyIsNull(tickInPis))
 | |
|       return QLatin1String("0");
 | |
|     else if (qFuzzyCompare(qAbs(tickInPis), 1.0))
 | |
|       return (tickInPis < 0 ? QLatin1String("-") : QLatin1String("")) + mPiSymbol.trimmed();
 | |
|     else
 | |
|       return QCPAxisTicker::getTickLabel(tickInPis, locale, formatChar, precision) + mPiSymbol;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Takes the fraction given by \a numerator and \a denominator and modifies the values to make sure
 | |
|   the fraction is in irreducible form, i.e. numerator and denominator don't share any common
 | |
|   factors which could be cancelled.
 | |
| */
 | |
| void QCPAxisTickerPi::simplifyFraction(int &numerator, int &denominator) const
 | |
| {
 | |
|   if (numerator == 0 || denominator == 0)
 | |
|     return;
 | |
|   
 | |
|   int num = numerator;
 | |
|   int denom = denominator;
 | |
|   while (denom != 0) // euclidean gcd algorithm
 | |
|   {
 | |
|     int oldDenom = denom;
 | |
|     denom = num % denom;
 | |
|     num = oldDenom;
 | |
|   }
 | |
|   // num is now gcd of numerator and denominator
 | |
|   numerator /= num;
 | |
|   denominator /= num;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Takes the fraction given by \a numerator and \a denominator and returns a string representation.
 | |
|   The result depends on the configured fraction style (\ref setFractionStyle).
 | |
|   
 | |
|   This method is used to format the numerical/fractional part when generating tick labels. It
 | |
|   simplifies the passed fraction to an irreducible form using \ref simplifyFraction and factors out
 | |
|   any integer parts of the fraction (e.g. "10/4" becomes "2 1/2").
 | |
| */
 | |
| QString QCPAxisTickerPi::fractionToString(int numerator, int denominator) const
 | |
| {
 | |
|   if (denominator == 0)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "called with zero denominator";
 | |
|     return QString();
 | |
|   }
 | |
|   if (mFractionStyle == fsFloatingPoint) // should never be the case when calling this function
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "shouldn't be called with fraction style fsDecimal";
 | |
|     return QString::number(numerator/(double)denominator); // failsafe
 | |
|   }
 | |
|   int sign = numerator*denominator < 0 ? -1 : 1;
 | |
|   numerator = qAbs(numerator);
 | |
|   denominator = qAbs(denominator);
 | |
|   
 | |
|   if (denominator == 1)
 | |
|   {
 | |
|     return QString::number(sign*numerator);
 | |
|   } else
 | |
|   {
 | |
|     int integerPart = numerator/denominator;
 | |
|     int remainder = numerator%denominator;
 | |
|     if (remainder == 0)
 | |
|     {
 | |
|       return QString::number(sign*integerPart);
 | |
|     } else
 | |
|     {
 | |
|       if (mFractionStyle == fsAsciiFractions)
 | |
|       {
 | |
|         return QString(QLatin1String("%1%2%3/%4"))
 | |
|             .arg(sign == -1 ? QLatin1String("-") : QLatin1String(""))
 | |
|             .arg(integerPart > 0 ? QString::number(integerPart)+QLatin1String(" ") : QLatin1String(""))
 | |
|             .arg(remainder)
 | |
|             .arg(denominator);
 | |
|       } else if (mFractionStyle == fsUnicodeFractions)
 | |
|       {
 | |
|         return QString(QLatin1String("%1%2%3"))
 | |
|             .arg(sign == -1 ? QLatin1String("-") : QLatin1String(""))
 | |
|             .arg(integerPart > 0 ? QString::number(integerPart) : QLatin1String(""))
 | |
|             .arg(unicodeFraction(remainder, denominator));
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   return QString();
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns the unicode string representation of the fraction given by \a numerator and \a
 | |
|   denominator. This is the representation used in \ref fractionToString when the fraction style
 | |
|   (\ref setFractionStyle) is \ref fsUnicodeFractions.
 | |
|   
 | |
|   This method doesn't use the single-character common fractions but builds each fraction from a
 | |
|   superscript unicode number, the unicode fraction character, and a subscript unicode number.
 | |
| */
 | |
| QString QCPAxisTickerPi::unicodeFraction(int numerator, int denominator) const
 | |
| {
 | |
|   return unicodeSuperscript(numerator)+QChar(0x2044)+unicodeSubscript(denominator);
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns the unicode string representing \a number as superscript. This is used to build
 | |
|   unicode fractions in \ref unicodeFraction.
 | |
| */
 | |
| QString QCPAxisTickerPi::unicodeSuperscript(int number) const
 | |
| {
 | |
|   if (number == 0)
 | |
|     return QString(QChar(0x2070));
 | |
|   
 | |
|   QString result;
 | |
|   while (number > 0)
 | |
|   {
 | |
|     const int digit = number%10;
 | |
|     switch (digit)
 | |
|     {
 | |
|       case 1: { result.prepend(QChar(0x00B9)); break; }
 | |
|       case 2: { result.prepend(QChar(0x00B2)); break; }
 | |
|       case 3: { result.prepend(QChar(0x00B3)); break; }
 | |
|       default: { result.prepend(QChar(0x2070+digit)); break; }
 | |
|     }
 | |
|     number /= 10;
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns the unicode string representing \a number as subscript. This is used to build unicode
 | |
|   fractions in \ref unicodeFraction.
 | |
| */
 | |
| QString QCPAxisTickerPi::unicodeSubscript(int number) const
 | |
| {
 | |
|   if (number == 0)
 | |
|     return QString(QChar(0x2080));
 | |
|   
 | |
|   QString result;
 | |
|   while (number > 0)
 | |
|   {
 | |
|     result.prepend(QChar(0x2080+number%10));
 | |
|     number /= 10;
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| /* end of 'src/axis/axistickerpi.cpp' */
 | |
| 
 | |
| 
 | |
| /* including file 'src/axis/axistickerlog.cpp', size 7106                    */
 | |
| /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPAxisTickerLog
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| /*! \class QCPAxisTickerLog
 | |
|   \brief Specialized axis ticker suited for logarithmic axes
 | |
|   
 | |
|   \image html axisticker-log.png
 | |
|   
 | |
|   This QCPAxisTicker subclass generates ticks with unequal tick intervals suited for logarithmic
 | |
|   axis scales. The ticks are placed at powers of the specified log base (\ref setLogBase).
 | |
|   
 | |
|   Especially in the case of a log base equal to 10 (the default), it might be desirable to have
 | |
|   tick labels in the form of powers of ten without mantissa display. To achieve this, set the
 | |
|   number precision (\ref QCPAxis::setNumberPrecision) to zero and the number format (\ref
 | |
|   QCPAxis::setNumberFormat) to scientific (exponential) display with beautifully typeset decimal
 | |
|   powers, so a format string of <tt>"eb"</tt>. This will result in the following axis tick labels:
 | |
|   
 | |
|   \image html axisticker-log-powers.png
 | |
| 
 | |
|   The ticker can be created and assigned to an axis like this:
 | |
|   \snippet documentation/doc-image-generator/mainwindow.cpp axistickerlog-creation
 | |
| */
 | |
| 
 | |
| /*!
 | |
|   Constructs the ticker and sets reasonable default values. Axis tickers are commonly created
 | |
|   managed by a QSharedPointer, which then can be passed to QCPAxis::setTicker.
 | |
| */
 | |
| QCPAxisTickerLog::QCPAxisTickerLog() :
 | |
|   mLogBase(10.0),
 | |
|   mSubTickCount(8), // generates 10 intervals
 | |
|   mLogBaseLnInv(1.0/qLn(mLogBase))
 | |
| {
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the logarithm base used for tick coordinate generation. The ticks will be placed at integer
 | |
|   powers of \a base.
 | |
| */
 | |
| void QCPAxisTickerLog::setLogBase(double base)
 | |
| {
 | |
|   if (base > 0)
 | |
|   {
 | |
|     mLogBase = base;
 | |
|     mLogBaseLnInv = 1.0/qLn(mLogBase);
 | |
|   } else
 | |
|     qDebug() << Q_FUNC_INFO << "log base has to be greater than zero:" << base;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the number of sub ticks in a tick interval. Within each interval, the sub ticks are spaced
 | |
|   linearly to provide a better visual guide, so the sub tick density increases toward the higher
 | |
|   tick.
 | |
|   
 | |
|   Note that \a subTicks is the number of sub ticks (not sub intervals) in one tick interval. So in
 | |
|   the case of logarithm base 10 an intuitive sub tick spacing would be achieved with eight sub
 | |
|   ticks (the default). This means e.g. between the ticks 10 and 100 there will be eight ticks,
 | |
|   namely at 20, 30, 40, 50, 60, 70, 80 and 90.
 | |
| */
 | |
| void QCPAxisTickerLog::setSubTickCount(int subTicks)
 | |
| {
 | |
|   if (subTicks >= 0)
 | |
|     mSubTickCount = subTicks;
 | |
|   else
 | |
|     qDebug() << Q_FUNC_INFO << "sub tick count can't be negative:" << subTicks;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Since logarithmic tick steps are necessarily different for each tick interval, this method does
 | |
|   nothing in the case of QCPAxisTickerLog
 | |
|   
 | |
|   \seebaseclassmethod
 | |
| */
 | |
| double QCPAxisTickerLog::getTickStep(const QCPRange &range)
 | |
| {
 | |
|   // Logarithmic axis ticker has unequal tick spacing, so doesn't need this method
 | |
|   Q_UNUSED(range)
 | |
|   return 1.0;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns the sub tick count specified in \ref setSubTickCount. For QCPAxisTickerLog, there is no
 | |
|   automatic sub tick count calculation necessary.
 | |
|   
 | |
|   \seebaseclassmethod
 | |
| */
 | |
| int QCPAxisTickerLog::getSubTickCount(double tickStep)
 | |
| {
 | |
|   Q_UNUSED(tickStep)
 | |
|   return mSubTickCount;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Creates ticks with a spacing given by the logarithm base and an increasing integer power in the
 | |
|   provided \a range. The step in which the power increases tick by tick is chosen in order to keep
 | |
|   the total number of ticks as close as possible to the tick count (\ref setTickCount). The
 | |
|   parameter \a tickStep is ignored for QCPAxisTickerLog
 | |
|   
 | |
|   \seebaseclassmethod
 | |
| */
 | |
| QVector<double> QCPAxisTickerLog::createTickVector(double tickStep, const QCPRange &range)
 | |
| {
 | |
|   Q_UNUSED(tickStep)
 | |
|   QVector<double> result;
 | |
|   if (range.lower > 0 && range.upper > 0) // positive range
 | |
|   {
 | |
|     double exactPowerStep =  qLn(range.upper/range.lower)*mLogBaseLnInv/(double)(mTickCount+1e-10);
 | |
|     double newLogBase = qPow(mLogBase, qMax((int)cleanMantissa(exactPowerStep), 1));
 | |
|     double currentTick = qPow(newLogBase, qFloor(qLn(range.lower)/qLn(newLogBase)));
 | |
|     result.append(currentTick);
 | |
|     while (currentTick < range.upper && currentTick > 0) // currentMag might be zero for ranges ~1e-300, just cancel in that case
 | |
|     {
 | |
|       currentTick *= newLogBase;
 | |
|       result.append(currentTick);
 | |
|     }
 | |
|   } else if (range.lower < 0 && range.upper < 0) // negative range
 | |
|   {
 | |
|     double exactPowerStep =  qLn(range.lower/range.upper)*mLogBaseLnInv/(double)(mTickCount+1e-10);
 | |
|     double newLogBase = qPow(mLogBase, qMax((int)cleanMantissa(exactPowerStep), 1));
 | |
|     double currentTick = -qPow(newLogBase, qCeil(qLn(-range.lower)/qLn(newLogBase)));
 | |
|     result.append(currentTick);
 | |
|     while (currentTick < range.upper && currentTick < 0) // currentMag might be zero for ranges ~1e-300, just cancel in that case
 | |
|     {
 | |
|       currentTick /= newLogBase;
 | |
|       result.append(currentTick);
 | |
|     }
 | |
|   } else // invalid range for logarithmic scale, because lower and upper have different sign
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "Invalid range for logarithmic plot: " << range.lower << ".." << range.upper;
 | |
|   }
 | |
|   
 | |
|   return result;
 | |
| }
 | |
| /* end of 'src/axis/axistickerlog.cpp' */
 | |
| 
 | |
| 
 | |
| /* including file 'src/axis/axis.cpp', size 94458                            */
 | |
| /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */
 | |
| 
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPGrid
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPGrid
 | |
|   \brief Responsible for drawing the grid of a QCPAxis.
 | |
|   
 | |
|   This class is tightly bound to QCPAxis. Every axis owns a grid instance and uses it to draw the
 | |
|   grid lines, sub grid lines and zero-line. You can interact with the grid of an axis via \ref
 | |
|   QCPAxis::grid. Normally, you don't need to create an instance of QCPGrid yourself.
 | |
|   
 | |
|   The axis and grid drawing was split into two classes to allow them to be placed on different
 | |
|   layers (both QCPAxis and QCPGrid inherit from QCPLayerable). Thus it is possible to have the grid
 | |
|   in the background and the axes in the foreground, and any plottables/items in between. This
 | |
|   described situation is the default setup, see the QCPLayer documentation.
 | |
| */
 | |
| 
 | |
| /*!
 | |
|   Creates a QCPGrid instance and sets default values.
 | |
|   
 | |
|   You shouldn't instantiate grids on their own, since every QCPAxis brings its own QCPGrid.
 | |
| */
 | |
| QCPGrid::QCPGrid(QCPAxis *parentAxis) :
 | |
|   QCPLayerable(parentAxis->parentPlot(), QString(), parentAxis),
 | |
|   mParentAxis(parentAxis)
 | |
| {
 | |
|   // warning: this is called in QCPAxis constructor, so parentAxis members should not be accessed/called
 | |
|   setParent(parentAxis);
 | |
|   setPen(QPen(QColor(200,200,200), 0, Qt::DotLine));
 | |
|   setSubGridPen(QPen(QColor(220,220,220), 0, Qt::DotLine));
 | |
|   setZeroLinePen(QPen(QColor(200,200,200), 0, Qt::SolidLine));
 | |
|   setSubGridVisible(false);
 | |
|   setAntialiased(false);
 | |
|   setAntialiasedSubGrid(false);
 | |
|   setAntialiasedZeroLine(false);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets whether grid lines at sub tick marks are drawn.
 | |
|   
 | |
|   \see setSubGridPen
 | |
| */
 | |
| void QCPGrid::setSubGridVisible(bool visible)
 | |
| {
 | |
|   mSubGridVisible = visible;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets whether sub grid lines are drawn antialiased.
 | |
| */
 | |
| void QCPGrid::setAntialiasedSubGrid(bool enabled)
 | |
| {
 | |
|   mAntialiasedSubGrid = enabled;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets whether zero lines are drawn antialiased.
 | |
| */
 | |
| void QCPGrid::setAntialiasedZeroLine(bool enabled)
 | |
| {
 | |
|   mAntialiasedZeroLine = enabled;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the pen with which (major) grid lines are drawn.
 | |
| */
 | |
| void QCPGrid::setPen(const QPen &pen)
 | |
| {
 | |
|   mPen = pen;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the pen with which sub grid lines are drawn.
 | |
| */
 | |
| void QCPGrid::setSubGridPen(const QPen &pen)
 | |
| {
 | |
|   mSubGridPen = pen;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the pen with which zero lines are drawn.
 | |
|   
 | |
|   Zero lines are lines at value coordinate 0 which may be drawn with a different pen than other grid
 | |
|   lines. To disable zero lines and just draw normal grid lines at zero, set \a pen to Qt::NoPen.
 | |
| */
 | |
| void QCPGrid::setZeroLinePen(const QPen &pen)
 | |
| {
 | |
|   mZeroLinePen = pen;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   A convenience function to easily set the QPainter::Antialiased hint on the provided \a painter
 | |
|   before drawing the major grid lines.
 | |
| 
 | |
|   This is the antialiasing state the painter passed to the \ref draw method is in by default.
 | |
|   
 | |
|   This function takes into account the local setting of the antialiasing flag as well as the
 | |
|   overrides set with \ref QCustomPlot::setAntialiasedElements and \ref
 | |
|   QCustomPlot::setNotAntialiasedElements.
 | |
|   
 | |
|   \see setAntialiased
 | |
| */
 | |
| void QCPGrid::applyDefaultAntialiasingHint(QCPPainter *painter) const
 | |
| {
 | |
|   applyAntialiasingHint(painter, mAntialiased, QCP::aeGrid);
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Draws grid lines and sub grid lines at the positions of (sub) ticks of the parent axis, spanning
 | |
|   over the complete axis rect. Also draws the zero line, if appropriate (\ref setZeroLinePen).
 | |
| */
 | |
| void QCPGrid::draw(QCPPainter *painter)
 | |
| {
 | |
|   if (!mParentAxis) { qDebug() << Q_FUNC_INFO << "invalid parent axis"; return; }
 | |
|   
 | |
|   if (mParentAxis->subTicks() && mSubGridVisible)
 | |
|     drawSubGridLines(painter);
 | |
|   drawGridLines(painter);
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Draws the main grid lines and possibly a zero line with the specified painter.
 | |
|   
 | |
|   This is a helper function called by \ref draw.
 | |
| */
 | |
| void QCPGrid::drawGridLines(QCPPainter *painter) const
 | |
| {
 | |
|   if (!mParentAxis) { qDebug() << Q_FUNC_INFO << "invalid parent axis"; return; }
 | |
|   
 | |
|   const int tickCount = mParentAxis->mTickVector.size();
 | |
|   double t; // helper variable, result of coordinate-to-pixel transforms
 | |
|   if (mParentAxis->orientation() == Qt::Horizontal)
 | |
|   {
 | |
|     // draw zeroline:
 | |
|     int zeroLineIndex = -1;
 | |
|     if (mZeroLinePen.style() != Qt::NoPen && mParentAxis->mRange.lower < 0 && mParentAxis->mRange.upper > 0)
 | |
|     {
 | |
|       applyAntialiasingHint(painter, mAntialiasedZeroLine, QCP::aeZeroLine);
 | |
|       painter->setPen(mZeroLinePen);
 | |
|       double epsilon = mParentAxis->range().size()*1E-6; // for comparing double to zero
 | |
|       for (int i=0; i<tickCount; ++i)
 | |
|       {
 | |
|         if (qAbs(mParentAxis->mTickVector.at(i)) < epsilon)
 | |
|         {
 | |
|           zeroLineIndex = i;
 | |
|           t = mParentAxis->coordToPixel(mParentAxis->mTickVector.at(i)); // x
 | |
|           painter->drawLine(QLineF(t, mParentAxis->mAxisRect->bottom(), t, mParentAxis->mAxisRect->top()));
 | |
|           break;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     // draw grid lines:
 | |
|     applyDefaultAntialiasingHint(painter);
 | |
|     painter->setPen(mPen);
 | |
|     for (int i=0; i<tickCount; ++i)
 | |
|     {
 | |
|       if (i == zeroLineIndex) continue; // don't draw a gridline on top of the zeroline
 | |
|       t = mParentAxis->coordToPixel(mParentAxis->mTickVector.at(i)); // x
 | |
|       painter->drawLine(QLineF(t, mParentAxis->mAxisRect->bottom(), t, mParentAxis->mAxisRect->top()));
 | |
|     }
 | |
|   } else
 | |
|   {
 | |
|     // draw zeroline:
 | |
|     int zeroLineIndex = -1;
 | |
|     if (mZeroLinePen.style() != Qt::NoPen && mParentAxis->mRange.lower < 0 && mParentAxis->mRange.upper > 0)
 | |
|     {
 | |
|       applyAntialiasingHint(painter, mAntialiasedZeroLine, QCP::aeZeroLine);
 | |
|       painter->setPen(mZeroLinePen);
 | |
|       double epsilon = mParentAxis->mRange.size()*1E-6; // for comparing double to zero
 | |
|       for (int i=0; i<tickCount; ++i)
 | |
|       {
 | |
|         if (qAbs(mParentAxis->mTickVector.at(i)) < epsilon)
 | |
|         {
 | |
|           zeroLineIndex = i;
 | |
|           t = mParentAxis->coordToPixel(mParentAxis->mTickVector.at(i)); // y
 | |
|           painter->drawLine(QLineF(mParentAxis->mAxisRect->left(), t, mParentAxis->mAxisRect->right(), t));
 | |
|           break;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     // draw grid lines:
 | |
|     applyDefaultAntialiasingHint(painter);
 | |
|     painter->setPen(mPen);
 | |
|     for (int i=0; i<tickCount; ++i)
 | |
|     {
 | |
|       if (i == zeroLineIndex) continue; // don't draw a gridline on top of the zeroline
 | |
|       t = mParentAxis->coordToPixel(mParentAxis->mTickVector.at(i)); // y
 | |
|       painter->drawLine(QLineF(mParentAxis->mAxisRect->left(), t, mParentAxis->mAxisRect->right(), t));
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Draws the sub grid lines with the specified painter.
 | |
|   
 | |
|   This is a helper function called by \ref draw.
 | |
| */
 | |
| void QCPGrid::drawSubGridLines(QCPPainter *painter) const
 | |
| {
 | |
|   if (!mParentAxis) { qDebug() << Q_FUNC_INFO << "invalid parent axis"; return; }
 | |
|   
 | |
|   applyAntialiasingHint(painter, mAntialiasedSubGrid, QCP::aeSubGrid);
 | |
|   double t; // helper variable, result of coordinate-to-pixel transforms
 | |
|   painter->setPen(mSubGridPen);
 | |
|   if (mParentAxis->orientation() == Qt::Horizontal)
 | |
|   {
 | |
|     for (int i=0; i<mParentAxis->mSubTickVector.size(); ++i)
 | |
|     {
 | |
|       t = mParentAxis->coordToPixel(mParentAxis->mSubTickVector.at(i)); // x
 | |
|       painter->drawLine(QLineF(t, mParentAxis->mAxisRect->bottom(), t, mParentAxis->mAxisRect->top()));
 | |
|     }
 | |
|   } else
 | |
|   {
 | |
|     for (int i=0; i<mParentAxis->mSubTickVector.size(); ++i)
 | |
|     {
 | |
|       t = mParentAxis->coordToPixel(mParentAxis->mSubTickVector.at(i)); // y
 | |
|       painter->drawLine(QLineF(mParentAxis->mAxisRect->left(), t, mParentAxis->mAxisRect->right(), t));
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPAxis
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPAxis
 | |
|   \brief Manages a single axis inside a QCustomPlot.
 | |
| 
 | |
|   Usually doesn't need to be instantiated externally. Access %QCustomPlot's default four axes via
 | |
|   QCustomPlot::xAxis (bottom), QCustomPlot::yAxis (left), QCustomPlot::xAxis2 (top) and
 | |
|   QCustomPlot::yAxis2 (right).
 | |
|   
 | |
|   Axes are always part of an axis rect, see QCPAxisRect.
 | |
|   \image html AxisNamesOverview.png
 | |
|   <center>Naming convention of axis parts</center>
 | |
|   \n
 | |
|     
 | |
|   \image html AxisRectSpacingOverview.png
 | |
|   <center>Overview of the spacings and paddings that define the geometry of an axis. The dashed gray line
 | |
|   on the left represents the QCustomPlot widget border.</center>
 | |
|   
 | |
|   Each axis holds an instance of QCPAxisTicker which is used to generate the tick coordinates and
 | |
|   tick labels. You can access the currently installed \ref ticker or set a new one (possibly one of
 | |
|   the specialized subclasses, or your own subclass) via \ref setTicker. For details, see the
 | |
|   documentation of QCPAxisTicker.
 | |
| */
 | |
| 
 | |
| /* start of documentation of inline functions */
 | |
| 
 | |
| /*! \fn Qt::Orientation QCPAxis::orientation() const
 | |
| 
 | |
|   Returns the orientation of this axis. The axis orientation (horizontal or vertical) is deduced
 | |
|   from the axis type (left, top, right or bottom).
 | |
| 
 | |
|   \see orientation(AxisType type), pixelOrientation
 | |
| */
 | |
| 
 | |
| /*! \fn QCPGrid *QCPAxis::grid() const
 | |
|   
 | |
|   Returns the \ref QCPGrid instance belonging to this axis. Access it to set details about the way the
 | |
|   grid is displayed.
 | |
| */
 | |
| 
 | |
| /*! \fn static Qt::Orientation QCPAxis::orientation(AxisType type)
 | |
| 
 | |
|   Returns the orientation of the specified axis type
 | |
| 
 | |
|   \see orientation(), pixelOrientation
 | |
| */
 | |
| 
 | |
| /*! \fn int QCPAxis::pixelOrientation() const
 | |
| 
 | |
|   Returns which direction points towards higher coordinate values/keys, in pixel space.
 | |
| 
 | |
|   This method returns either 1 or -1. If it returns 1, then going in the positive direction along
 | |
|   the orientation of the axis in pixels corresponds to going from lower to higher axis coordinates.
 | |
|   On the other hand, if this method returns -1, going to smaller pixel values corresponds to going
 | |
|   from lower to higher axis coordinates.
 | |
| 
 | |
|   For example, this is useful to easily shift axis coordinates by a certain amount given in pixels,
 | |
|   without having to care about reversed or vertically aligned axes:
 | |
| 
 | |
|   \code
 | |
|   double newKey = keyAxis->pixelToCoord(keyAxis->coordToPixel(oldKey)+10*keyAxis->pixelOrientation());
 | |
|   \endcode
 | |
| 
 | |
|   \a newKey will then contain a key that is ten pixels towards higher keys, starting from \a oldKey.
 | |
| */
 | |
| 
 | |
| /*! \fn QSharedPointer<QCPAxisTicker> QCPAxis::ticker() const
 | |
| 
 | |
|   Returns a modifiable shared pointer to the currently installed axis ticker. The axis ticker is
 | |
|   responsible for generating the tick positions and tick labels of this axis. You can access the
 | |
|   \ref QCPAxisTicker with this method and modify basic properties such as the approximate tick count
 | |
|   (\ref QCPAxisTicker::setTickCount).
 | |
| 
 | |
|   You can gain more control over the axis ticks by setting a different \ref QCPAxisTicker subclass, see
 | |
|   the documentation there. A new axis ticker can be set with \ref setTicker.
 | |
| 
 | |
|   Since the ticker is stored in the axis as a shared pointer, multiple axes may share the same axis
 | |
|   ticker simply by passing the same shared pointer to multiple axes.
 | |
| 
 | |
|   \see setTicker
 | |
| */
 | |
| 
 | |
| /* end of documentation of inline functions */
 | |
| /* start of documentation of signals */
 | |
| 
 | |
| /*! \fn void QCPAxis::rangeChanged(const QCPRange &newRange)
 | |
| 
 | |
|   This signal is emitted when the range of this axis has changed. You can connect it to the \ref
 | |
|   setRange slot of another axis to communicate the new range to the other axis, in order for it to
 | |
|   be synchronized.
 | |
|   
 | |
|   You may also manipulate/correct the range with \ref setRange in a slot connected to this signal.
 | |
|   This is useful if for example a maximum range span shall not be exceeded, or if the lower/upper
 | |
|   range shouldn't go beyond certain values (see \ref QCPRange::bounded). For example, the following
 | |
|   slot would limit the x axis to ranges between 0 and 10:
 | |
|   \code
 | |
|   customPlot->xAxis->setRange(newRange.bounded(0, 10))
 | |
|   \endcode
 | |
| */
 | |
| 
 | |
| /*! \fn void QCPAxis::rangeChanged(const QCPRange &newRange, const QCPRange &oldRange)
 | |
|   \overload
 | |
|   
 | |
|   Additionally to the new range, this signal also provides the previous range held by the axis as
 | |
|   \a oldRange.
 | |
| */
 | |
| 
 | |
| /*! \fn void QCPAxis::scaleTypeChanged(QCPAxis::ScaleType scaleType);
 | |
|   
 | |
|   This signal is emitted when the scale type changes, by calls to \ref setScaleType
 | |
| */
 | |
| 
 | |
| /*! \fn void QCPAxis::selectionChanged(QCPAxis::SelectableParts selection)
 | |
|   
 | |
|   This signal is emitted when the selection state of this axis has changed, either by user interaction
 | |
|   or by a direct call to \ref setSelectedParts.
 | |
| */
 | |
| 
 | |
| /*! \fn void QCPAxis::selectableChanged(const QCPAxis::SelectableParts &parts);
 | |
|   
 | |
|   This signal is emitted when the selectability changes, by calls to \ref setSelectableParts
 | |
| */
 | |
| 
 | |
| /* end of documentation of signals */
 | |
| 
 | |
| /*!
 | |
|   Constructs an Axis instance of Type \a type for the axis rect \a parent.
 | |
|   
 | |
|   Usually it isn't necessary to instantiate axes directly, because you can let QCustomPlot create
 | |
|   them for you with \ref QCPAxisRect::addAxis. If you want to use own QCPAxis-subclasses however,
 | |
|   create them manually and then inject them also via \ref QCPAxisRect::addAxis.
 | |
| */
 | |
| QCPAxis::QCPAxis(QCPAxisRect *parent, AxisType type) :
 | |
|   QCPLayerable(parent->parentPlot(), QString(), parent),
 | |
|   // axis base:
 | |
|   mAxisType(type),
 | |
|   mAxisRect(parent),
 | |
|   mPadding(5),
 | |
|   mOrientation(orientation(type)),
 | |
|   mSelectableParts(spAxis | spTickLabels | spAxisLabel),
 | |
|   mSelectedParts(spNone),
 | |
|   mBasePen(QPen(Qt::black, 0, Qt::SolidLine, Qt::SquareCap)),
 | |
|   mSelectedBasePen(QPen(Qt::blue, 2)),
 | |
|   // axis label:
 | |
|   mLabel(),
 | |
|   mLabelFont(mParentPlot->font()),
 | |
|   mSelectedLabelFont(QFont(mLabelFont.family(), mLabelFont.pointSize(), QFont::Bold)),
 | |
|   mLabelColor(Qt::black),
 | |
|   mSelectedLabelColor(Qt::blue),
 | |
|   // tick labels:
 | |
|   mTickLabels(true),
 | |
|   mTickLabelFont(mParentPlot->font()),
 | |
|   mSelectedTickLabelFont(QFont(mTickLabelFont.family(), mTickLabelFont.pointSize(), QFont::Bold)),
 | |
|   mTickLabelColor(Qt::black),
 | |
|   mSelectedTickLabelColor(Qt::blue),
 | |
|   mNumberPrecision(6),
 | |
|   mNumberFormatChar('g'),
 | |
|   mNumberBeautifulPowers(true),
 | |
|   // ticks and subticks:
 | |
|   mTicks(true),
 | |
|   mSubTicks(true),
 | |
|   mTickPen(QPen(Qt::black, 0, Qt::SolidLine, Qt::SquareCap)),
 | |
|   mSelectedTickPen(QPen(Qt::blue, 2)),
 | |
|   mSubTickPen(QPen(Qt::black, 0, Qt::SolidLine, Qt::SquareCap)),
 | |
|   mSelectedSubTickPen(QPen(Qt::blue, 2)),
 | |
|   // scale and range:
 | |
|   mRange(0, 5),
 | |
|   mRangeReversed(false),
 | |
|   mScaleType(stLinear),
 | |
|   // internal members:
 | |
|   mGrid(new QCPGrid(this)),
 | |
|   mAxisPainter(new QCPAxisPainterPrivate(parent->parentPlot())),
 | |
|   mTicker(new QCPAxisTicker),
 | |
|   mCachedMarginValid(false),
 | |
|   mCachedMargin(0)
 | |
| {
 | |
|   setParent(parent);
 | |
|   mGrid->setVisible(false);
 | |
|   setAntialiased(false);
 | |
|   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
 | |
|   
 | |
|   if (type == atTop)
 | |
|   {
 | |
|     setTickLabelPadding(3);
 | |
|     setLabelPadding(6);
 | |
|   } else if (type == atRight)
 | |
|   {
 | |
|     setTickLabelPadding(7);
 | |
|     setLabelPadding(12);
 | |
|   } else if (type == atBottom)
 | |
|   {
 | |
|     setTickLabelPadding(3);
 | |
|     setLabelPadding(3);
 | |
|   } else if (type == atLeft)
 | |
|   {
 | |
|     setTickLabelPadding(5);
 | |
|     setLabelPadding(10);
 | |
|   }
 | |
| }
 | |
| 
 | |
| QCPAxis::~QCPAxis()
 | |
| {
 | |
|   delete mAxisPainter;
 | |
|   delete mGrid; // delete grid here instead of via parent ~QObject for better defined deletion order
 | |
| }
 | |
| 
 | |
| /* No documentation as it is a property getter */
 | |
| int QCPAxis::tickLabelPadding() const
 | |
| {
 | |
|   return mAxisPainter->tickLabelPadding;
 | |
| }
 | |
| 
 | |
| /* No documentation as it is a property getter */
 | |
| double QCPAxis::tickLabelRotation() const
 | |
| {
 | |
|   return mAxisPainter->tickLabelRotation;
 | |
| }
 | |
| 
 | |
| /* No documentation as it is a property getter */
 | |
| QCPAxis::LabelSide QCPAxis::tickLabelSide() const
 | |
| {
 | |
|   return mAxisPainter->tickLabelSide;
 | |
| }
 | |
| 
 | |
| /* No documentation as it is a property getter */
 | |
| QString QCPAxis::numberFormat() const
 | |
| {
 | |
|   QString result;
 | |
|   result.append(mNumberFormatChar);
 | |
|   if (mNumberBeautifulPowers)
 | |
|   {
 | |
|     result.append(QLatin1Char('b'));
 | |
|     if (mAxisPainter->numberMultiplyCross)
 | |
|       result.append(QLatin1Char('c'));
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /* No documentation as it is a property getter */
 | |
| int QCPAxis::tickLengthIn() const
 | |
| {
 | |
|   return mAxisPainter->tickLengthIn;
 | |
| }
 | |
| 
 | |
| /* No documentation as it is a property getter */
 | |
| int QCPAxis::tickLengthOut() const
 | |
| {
 | |
|   return mAxisPainter->tickLengthOut;
 | |
| }
 | |
| 
 | |
| /* No documentation as it is a property getter */
 | |
| int QCPAxis::subTickLengthIn() const
 | |
| {
 | |
|   return mAxisPainter->subTickLengthIn;
 | |
| }
 | |
| 
 | |
| /* No documentation as it is a property getter */
 | |
| int QCPAxis::subTickLengthOut() const
 | |
| {
 | |
|   return mAxisPainter->subTickLengthOut;
 | |
| }
 | |
| 
 | |
| /* No documentation as it is a property getter */
 | |
| int QCPAxis::labelPadding() const
 | |
| {
 | |
|   return mAxisPainter->labelPadding;
 | |
| }
 | |
| 
 | |
| /* No documentation as it is a property getter */
 | |
| int QCPAxis::offset() const
 | |
| {
 | |
|   return mAxisPainter->offset;
 | |
| }
 | |
| 
 | |
| /* No documentation as it is a property getter */
 | |
| QCPLineEnding QCPAxis::lowerEnding() const
 | |
| {
 | |
|   return mAxisPainter->lowerEnding;
 | |
| }
 | |
| 
 | |
| /* No documentation as it is a property getter */
 | |
| QCPLineEnding QCPAxis::upperEnding() const
 | |
| {
 | |
|   return mAxisPainter->upperEnding;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets whether the axis uses a linear scale or a logarithmic scale.
 | |
|   
 | |
|   Note that this method controls the coordinate transformation. You will likely also want to use a
 | |
|   logarithmic tick spacing and labeling, which can be achieved by setting an instance of \ref
 | |
|   QCPAxisTickerLog via \ref setTicker. See the documentation of \ref QCPAxisTickerLog about the
 | |
|   details of logarithmic axis tick creation.
 | |
|   
 | |
|   \ref setNumberPrecision
 | |
| */
 | |
| void QCPAxis::setScaleType(QCPAxis::ScaleType type)
 | |
| {
 | |
|   if (mScaleType != type)
 | |
|   {
 | |
|     mScaleType = type;
 | |
|     if (mScaleType == stLogarithmic)
 | |
|       setRange(mRange.sanitizedForLogScale());
 | |
|     mCachedMarginValid = false;
 | |
|     emit scaleTypeChanged(mScaleType);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the range of the axis.
 | |
|   
 | |
|   This slot may be connected with the \ref rangeChanged signal of another axis so this axis
 | |
|   is always synchronized with the other axis range, when it changes.
 | |
|   
 | |
|   To invert the direction of an axis, use \ref setRangeReversed.
 | |
| */
 | |
| void QCPAxis::setRange(const QCPRange &range)
 | |
| {
 | |
|   if (range.lower == mRange.lower && range.upper == mRange.upper)
 | |
|     return;
 | |
|   
 | |
|   if (!QCPRange::validRange(range)) return;
 | |
|   QCPRange oldRange = mRange;
 | |
|   if (mScaleType == stLogarithmic)
 | |
|   {
 | |
|     mRange = range.sanitizedForLogScale();
 | |
|   } else
 | |
|   {
 | |
|     mRange = range.sanitizedForLinScale();
 | |
|   }
 | |
|   emit rangeChanged(mRange);
 | |
|   emit rangeChanged(mRange, oldRange);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets whether the user can (de-)select the parts in \a selectable by clicking on the QCustomPlot surface.
 | |
|   (When \ref QCustomPlot::setInteractions contains iSelectAxes.)
 | |
|   
 | |
|   However, even when \a selectable is set to a value not allowing the selection of a specific part,
 | |
|   it is still possible to set the selection of this part manually, by calling \ref setSelectedParts
 | |
|   directly.
 | |
|   
 | |
|   \see SelectablePart, setSelectedParts
 | |
| */
 | |
| void QCPAxis::setSelectableParts(const SelectableParts &selectable)
 | |
| {
 | |
|   if (mSelectableParts != selectable)
 | |
|   {
 | |
|     mSelectableParts = selectable;
 | |
|     emit selectableChanged(mSelectableParts);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the selected state of the respective axis parts described by \ref SelectablePart. When a part
 | |
|   is selected, it uses a different pen/font.
 | |
|   
 | |
|   The entire selection mechanism for axes is handled automatically when \ref
 | |
|   QCustomPlot::setInteractions contains iSelectAxes. You only need to call this function when you
 | |
|   wish to change the selection state manually.
 | |
|   
 | |
|   This function can change the selection state of a part, independent of the \ref setSelectableParts setting.
 | |
|   
 | |
|   emits the \ref selectionChanged signal when \a selected is different from the previous selection state.
 | |
|   
 | |
|   \see SelectablePart, setSelectableParts, selectTest, setSelectedBasePen, setSelectedTickPen, setSelectedSubTickPen,
 | |
|   setSelectedTickLabelFont, setSelectedLabelFont, setSelectedTickLabelColor, setSelectedLabelColor
 | |
| */
 | |
| void QCPAxis::setSelectedParts(const SelectableParts &selected)
 | |
| {
 | |
|   if (mSelectedParts != selected)
 | |
|   {
 | |
|     mSelectedParts = selected;
 | |
|     emit selectionChanged(mSelectedParts);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   \overload
 | |
|   
 | |
|   Sets the lower and upper bound of the axis range.
 | |
|   
 | |
|   To invert the direction of an axis, use \ref setRangeReversed.
 | |
|   
 | |
|   There is also a slot to set a range, see \ref setRange(const QCPRange &range).
 | |
| */
 | |
| void QCPAxis::setRange(double lower, double upper)
 | |
| {
 | |
|   if (lower == mRange.lower && upper == mRange.upper)
 | |
|     return;
 | |
|   
 | |
|   if (!QCPRange::validRange(lower, upper)) return;
 | |
|   QCPRange oldRange = mRange;
 | |
|   mRange.lower = lower;
 | |
|   mRange.upper = upper;
 | |
|   if (mScaleType == stLogarithmic)
 | |
|   {
 | |
|     mRange = mRange.sanitizedForLogScale();
 | |
|   } else
 | |
|   {
 | |
|     mRange = mRange.sanitizedForLinScale();
 | |
|   }
 | |
|   emit rangeChanged(mRange);
 | |
|   emit rangeChanged(mRange, oldRange);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   \overload
 | |
|   
 | |
|   Sets the range of the axis.
 | |
|   
 | |
|   The \a position coordinate indicates together with the \a alignment parameter, where the new
 | |
|   range will be positioned. \a size defines the size of the new axis range. \a alignment may be
 | |
|   Qt::AlignLeft, Qt::AlignRight or Qt::AlignCenter. This will cause the left border, right border,
 | |
|   or center of the range to be aligned with \a position. Any other values of \a alignment will
 | |
|   default to Qt::AlignCenter.
 | |
| */
 | |
| void QCPAxis::setRange(double position, double size, Qt::AlignmentFlag alignment)
 | |
| {
 | |
|   if (alignment == Qt::AlignLeft)
 | |
|     setRange(position, position+size);
 | |
|   else if (alignment == Qt::AlignRight)
 | |
|     setRange(position-size, position);
 | |
|   else // alignment == Qt::AlignCenter
 | |
|     setRange(position-size/2.0, position+size/2.0);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the lower bound of the axis range. The upper bound is not changed.
 | |
|   \see setRange
 | |
| */
 | |
| void QCPAxis::setRangeLower(double lower)
 | |
| {
 | |
|   if (mRange.lower == lower)
 | |
|     return;
 | |
|   
 | |
|   QCPRange oldRange = mRange;
 | |
|   mRange.lower = lower;
 | |
|   if (mScaleType == stLogarithmic)
 | |
|   {
 | |
|     mRange = mRange.sanitizedForLogScale();
 | |
|   } else
 | |
|   {
 | |
|     mRange = mRange.sanitizedForLinScale();
 | |
|   }
 | |
|   emit rangeChanged(mRange);
 | |
|   emit rangeChanged(mRange, oldRange);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the upper bound of the axis range. The lower bound is not changed.
 | |
|   \see setRange
 | |
| */
 | |
| void QCPAxis::setRangeUpper(double upper)
 | |
| {
 | |
|   if (mRange.upper == upper)
 | |
|     return;
 | |
|   
 | |
|   QCPRange oldRange = mRange;
 | |
|   mRange.upper = upper;
 | |
|   if (mScaleType == stLogarithmic)
 | |
|   {
 | |
|     mRange = mRange.sanitizedForLogScale();
 | |
|   } else
 | |
|   {
 | |
|     mRange = mRange.sanitizedForLinScale();
 | |
|   }
 | |
|   emit rangeChanged(mRange);
 | |
|   emit rangeChanged(mRange, oldRange);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets whether the axis range (direction) is displayed reversed. Normally, the values on horizontal
 | |
|   axes increase left to right, on vertical axes bottom to top. When \a reversed is set to true, the
 | |
|   direction of increasing values is inverted.
 | |
| 
 | |
|   Note that the range and data interface stays the same for reversed axes, e.g. the \a lower part
 | |
|   of the \ref setRange interface will still reference the mathematically smaller number than the \a
 | |
|   upper part.
 | |
| */
 | |
| void QCPAxis::setRangeReversed(bool reversed)
 | |
| {
 | |
|   mRangeReversed = reversed;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   The axis ticker is responsible for generating the tick positions and tick labels. See the
 | |
|   documentation of QCPAxisTicker for details on how to work with axis tickers.
 | |
|   
 | |
|   You can change the tick positioning/labeling behaviour of this axis by setting a different
 | |
|   QCPAxisTicker subclass using this method. If you only wish to modify the currently installed axis
 | |
|   ticker, access it via \ref ticker.
 | |
|   
 | |
|   Since the ticker is stored in the axis as a shared pointer, multiple axes may share the same axis
 | |
|   ticker simply by passing the same shared pointer to multiple axes.
 | |
|   
 | |
|   \see ticker
 | |
| */
 | |
| void QCPAxis::setTicker(QSharedPointer<QCPAxisTicker> ticker)
 | |
| {
 | |
|   if (ticker)
 | |
|     mTicker = ticker;
 | |
|   else
 | |
|     qDebug() << Q_FUNC_INFO << "can not set 0 as axis ticker";
 | |
|   // no need to invalidate margin cache here because produced tick labels are checked for changes in setupTickVector
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets whether tick marks are displayed.
 | |
| 
 | |
|   Note that setting \a show to false does not imply that tick labels are invisible, too. To achieve
 | |
|   that, see \ref setTickLabels.
 | |
|   
 | |
|   \see setSubTicks
 | |
| */
 | |
| void QCPAxis::setTicks(bool show)
 | |
| {
 | |
|   if (mTicks != show)
 | |
|   {
 | |
|     mTicks = show;
 | |
|     mCachedMarginValid = false;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets whether tick labels are displayed. Tick labels are the numbers drawn next to tick marks.
 | |
| */
 | |
| void QCPAxis::setTickLabels(bool show)
 | |
| {
 | |
|   if (mTickLabels != show)
 | |
|   {
 | |
|     mTickLabels = show;
 | |
|     mCachedMarginValid = false;
 | |
|     if (!mTickLabels)
 | |
|       mTickVectorLabels.clear();
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the distance between the axis base line (including any outward ticks) and the tick labels.
 | |
|   \see setLabelPadding, setPadding
 | |
| */
 | |
| void QCPAxis::setTickLabelPadding(int padding)
 | |
| {
 | |
|   if (mAxisPainter->tickLabelPadding != padding)
 | |
|   {
 | |
|     mAxisPainter->tickLabelPadding = padding;
 | |
|     mCachedMarginValid = false;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the font of the tick labels.
 | |
|   
 | |
|   \see setTickLabels, setTickLabelColor
 | |
| */
 | |
| void QCPAxis::setTickLabelFont(const QFont &font)
 | |
| {
 | |
|   if (font != mTickLabelFont)
 | |
|   {
 | |
|     mTickLabelFont = font;
 | |
|     mCachedMarginValid = false;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the color of the tick labels.
 | |
|   
 | |
|   \see setTickLabels, setTickLabelFont
 | |
| */
 | |
| void QCPAxis::setTickLabelColor(const QColor &color)
 | |
| {
 | |
|   mTickLabelColor = color;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the rotation of the tick labels. If \a degrees is zero, the labels are drawn normally. Else,
 | |
|   the tick labels are drawn rotated by \a degrees clockwise. The specified angle is bound to values
 | |
|   from -90 to 90 degrees.
 | |
|   
 | |
|   If \a degrees is exactly -90, 0 or 90, the tick labels are centered on the tick coordinate. For
 | |
|   other angles, the label is drawn with an offset such that it seems to point toward or away from
 | |
|   the tick mark.
 | |
| */
 | |
| void QCPAxis::setTickLabelRotation(double degrees)
 | |
| {
 | |
|   if (!qFuzzyIsNull(degrees-mAxisPainter->tickLabelRotation))
 | |
|   {
 | |
|     mAxisPainter->tickLabelRotation = qBound(-90.0, degrees, 90.0);
 | |
|     mCachedMarginValid = false;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets whether the tick labels (numbers) shall appear inside or outside the axis rect.
 | |
|   
 | |
|   The usual and default setting is \ref lsOutside. Very compact plots sometimes require tick labels
 | |
|   to be inside the axis rect, to save space. If \a side is set to \ref lsInside, the tick labels
 | |
|   appear on the inside are additionally clipped to the axis rect.
 | |
| */
 | |
| void QCPAxis::setTickLabelSide(LabelSide side)
 | |
| {
 | |
|   mAxisPainter->tickLabelSide = side;
 | |
|   mCachedMarginValid = false;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the number format for the numbers in tick labels. This \a formatCode is an extended version
 | |
|   of the format code used e.g. by QString::number() and QLocale::toString(). For reference about
 | |
|   that, see the "Argument Formats" section in the detailed description of the QString class.
 | |
|   
 | |
|   \a formatCode is a string of one, two or three characters. The first character is identical to
 | |
|   the normal format code used by Qt. In short, this means: 'e'/'E' scientific format, 'f' fixed
 | |
|   format, 'g'/'G' scientific or fixed, whichever is shorter.
 | |
|   
 | |
|   The second and third characters are optional and specific to QCustomPlot:\n
 | |
|   If the first char was 'e' or 'g', numbers are/might be displayed in the scientific format, e.g.
 | |
|   "5.5e9", which is ugly in a plot. So when the second char of \a formatCode is set to 'b' (for
 | |
|   "beautiful"), those exponential numbers are formatted in a more natural way, i.e. "5.5
 | |
|   [multiplication sign] 10 [superscript] 9". By default, the multiplication sign is a centered dot.
 | |
|   If instead a cross should be shown (as is usual in the USA), the third char of \a formatCode can
 | |
|   be set to 'c'. The inserted multiplication signs are the UTF-8 characters 215 (0xD7) for the
 | |
|   cross and 183 (0xB7) for the dot.
 | |
|   
 | |
|   Examples for \a formatCode:
 | |
|   \li \c g normal format code behaviour. If number is small, fixed format is used, if number is large,
 | |
|   normal scientific format is used
 | |
|   \li \c gb If number is small, fixed format is used, if number is large, scientific format is used with
 | |
|   beautifully typeset decimal powers and a dot as multiplication sign
 | |
|   \li \c ebc All numbers are in scientific format with beautifully typeset decimal power and a cross as
 | |
|   multiplication sign
 | |
|   \li \c fb illegal format code, since fixed format doesn't support (or need) beautifully typeset decimal
 | |
|   powers. Format code will be reduced to 'f'.
 | |
|   \li \c hello illegal format code, since first char is not 'e', 'E', 'f', 'g' or 'G'. Current format
 | |
|   code will not be changed.
 | |
| */
 | |
| void QCPAxis::setNumberFormat(const QString &formatCode)
 | |
| {
 | |
|   if (formatCode.isEmpty())
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "Passed formatCode is empty";
 | |
|     return;
 | |
|   }
 | |
|   mCachedMarginValid = false;
 | |
|   
 | |
|   // interpret first char as number format char:
 | |
|   QString allowedFormatChars(QLatin1String("eEfgG"));
 | |
|   if (allowedFormatChars.contains(formatCode.at(0)))
 | |
|   {
 | |
|     mNumberFormatChar = QLatin1Char(formatCode.at(0).toLatin1());
 | |
|   } else
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "Invalid number format code (first char not in 'eEfgG'):" << formatCode;
 | |
|     return;
 | |
|   }
 | |
|   if (formatCode.length() < 2)
 | |
|   {
 | |
|     mNumberBeautifulPowers = false;
 | |
|     mAxisPainter->numberMultiplyCross = false;
 | |
|     return;
 | |
|   }
 | |
|   
 | |
|   // interpret second char as indicator for beautiful decimal powers:
 | |
|   if (formatCode.at(1) == QLatin1Char('b') && (mNumberFormatChar == QLatin1Char('e') || mNumberFormatChar == QLatin1Char('g')))
 | |
|   {
 | |
|     mNumberBeautifulPowers = true;
 | |
|   } else
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "Invalid number format code (second char not 'b' or first char neither 'e' nor 'g'):" << formatCode;
 | |
|     return;
 | |
|   }
 | |
|   if (formatCode.length() < 3)
 | |
|   {
 | |
|     mAxisPainter->numberMultiplyCross = false;
 | |
|     return;
 | |
|   }
 | |
|   
 | |
|   // interpret third char as indicator for dot or cross multiplication symbol:
 | |
|   if (formatCode.at(2) == QLatin1Char('c'))
 | |
|   {
 | |
|     mAxisPainter->numberMultiplyCross = true;
 | |
|   } else if (formatCode.at(2) == QLatin1Char('d'))
 | |
|   {
 | |
|     mAxisPainter->numberMultiplyCross = false;
 | |
|   } else
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "Invalid number format code (third char neither 'c' nor 'd'):" << formatCode;
 | |
|     return;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the precision of the tick label numbers. See QLocale::toString(double i, char f, int prec)
 | |
|   for details. The effect of precisions are most notably for number Formats starting with 'e', see
 | |
|   \ref setNumberFormat
 | |
| */
 | |
| void QCPAxis::setNumberPrecision(int precision)
 | |
| {
 | |
|   if (mNumberPrecision != precision)
 | |
|   {
 | |
|     mNumberPrecision = precision;
 | |
|     mCachedMarginValid = false;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the length of the ticks in pixels. \a inside is the length the ticks will reach inside the
 | |
|   plot and \a outside is the length they will reach outside the plot. If \a outside is greater than
 | |
|   zero, the tick labels and axis label will increase their distance to the axis accordingly, so
 | |
|   they won't collide with the ticks.
 | |
|   
 | |
|   \see setSubTickLength, setTickLengthIn, setTickLengthOut
 | |
| */
 | |
| void QCPAxis::setTickLength(int inside, int outside)
 | |
| {
 | |
|   setTickLengthIn(inside);
 | |
|   setTickLengthOut(outside);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the length of the inward ticks in pixels. \a inside is the length the ticks will reach
 | |
|   inside the plot.
 | |
|   
 | |
|   \see setTickLengthOut, setTickLength, setSubTickLength
 | |
| */
 | |
| void QCPAxis::setTickLengthIn(int inside)
 | |
| {
 | |
|   if (mAxisPainter->tickLengthIn != inside)
 | |
|   {
 | |
|     mAxisPainter->tickLengthIn = inside;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the length of the outward ticks in pixels. \a outside is the length the ticks will reach
 | |
|   outside the plot. If \a outside is greater than zero, the tick labels and axis label will
 | |
|   increase their distance to the axis accordingly, so they won't collide with the ticks.
 | |
|   
 | |
|   \see setTickLengthIn, setTickLength, setSubTickLength
 | |
| */
 | |
| void QCPAxis::setTickLengthOut(int outside)
 | |
| {
 | |
|   if (mAxisPainter->tickLengthOut != outside)
 | |
|   {
 | |
|     mAxisPainter->tickLengthOut = outside;
 | |
|     mCachedMarginValid = false; // only outside tick length can change margin
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets whether sub tick marks are displayed.
 | |
|   
 | |
|   Sub ticks are only potentially visible if (major) ticks are also visible (see \ref setTicks)
 | |
|   
 | |
|   \see setTicks
 | |
| */
 | |
| void QCPAxis::setSubTicks(bool show)
 | |
| {
 | |
|   if (mSubTicks != show)
 | |
|   {
 | |
|     mSubTicks = show;
 | |
|     mCachedMarginValid = false;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the length of the subticks in pixels. \a inside is the length the subticks will reach inside
 | |
|   the plot and \a outside is the length they will reach outside the plot. If \a outside is greater
 | |
|   than zero, the tick labels and axis label will increase their distance to the axis accordingly,
 | |
|   so they won't collide with the ticks.
 | |
|   
 | |
|   \see setTickLength, setSubTickLengthIn, setSubTickLengthOut
 | |
| */
 | |
| void QCPAxis::setSubTickLength(int inside, int outside)
 | |
| {
 | |
|   setSubTickLengthIn(inside);
 | |
|   setSubTickLengthOut(outside);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the length of the inward subticks in pixels. \a inside is the length the subticks will reach inside
 | |
|   the plot.
 | |
|   
 | |
|   \see setSubTickLengthOut, setSubTickLength, setTickLength
 | |
| */
 | |
| void QCPAxis::setSubTickLengthIn(int inside)
 | |
| {
 | |
|   if (mAxisPainter->subTickLengthIn != inside)
 | |
|   {
 | |
|     mAxisPainter->subTickLengthIn = inside;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the length of the outward subticks in pixels. \a outside is the length the subticks will reach
 | |
|   outside the plot. If \a outside is greater than zero, the tick labels will increase their
 | |
|   distance to the axis accordingly, so they won't collide with the ticks.
 | |
|   
 | |
|   \see setSubTickLengthIn, setSubTickLength, setTickLength
 | |
| */
 | |
| void QCPAxis::setSubTickLengthOut(int outside)
 | |
| {
 | |
|   if (mAxisPainter->subTickLengthOut != outside)
 | |
|   {
 | |
|     mAxisPainter->subTickLengthOut = outside;
 | |
|     mCachedMarginValid = false; // only outside tick length can change margin
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the pen, the axis base line is drawn with.
 | |
|   
 | |
|   \see setTickPen, setSubTickPen
 | |
| */
 | |
| void QCPAxis::setBasePen(const QPen &pen)
 | |
| {
 | |
|   mBasePen = pen;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the pen, tick marks will be drawn with.
 | |
|   
 | |
|   \see setTickLength, setBasePen
 | |
| */
 | |
| void QCPAxis::setTickPen(const QPen &pen)
 | |
| {
 | |
|   mTickPen = pen;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the pen, subtick marks will be drawn with.
 | |
|   
 | |
|   \see setSubTickCount, setSubTickLength, setBasePen
 | |
| */
 | |
| void QCPAxis::setSubTickPen(const QPen &pen)
 | |
| {
 | |
|   mSubTickPen = pen;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the font of the axis label.
 | |
|   
 | |
|   \see setLabelColor
 | |
| */
 | |
| void QCPAxis::setLabelFont(const QFont &font)
 | |
| {
 | |
|   if (mLabelFont != font)
 | |
|   {
 | |
|     mLabelFont = font;
 | |
|     mCachedMarginValid = false;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the color of the axis label.
 | |
|   
 | |
|   \see setLabelFont
 | |
| */
 | |
| void QCPAxis::setLabelColor(const QColor &color)
 | |
| {
 | |
|   mLabelColor = color;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the text of the axis label that will be shown below/above or next to the axis, depending on
 | |
|   its orientation. To disable axis labels, pass an empty string as \a str.
 | |
| */
 | |
| void QCPAxis::setLabel(const QString &str)
 | |
| {
 | |
|   if (mLabel != str)
 | |
|   {
 | |
|     mLabel = str;
 | |
|     mCachedMarginValid = false;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the distance between the tick labels and the axis label.
 | |
|   
 | |
|   \see setTickLabelPadding, setPadding
 | |
| */
 | |
| void QCPAxis::setLabelPadding(int padding)
 | |
| {
 | |
|   if (mAxisPainter->labelPadding != padding)
 | |
|   {
 | |
|     mAxisPainter->labelPadding = padding;
 | |
|     mCachedMarginValid = false;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the padding of the axis.
 | |
| 
 | |
|   When \ref QCPAxisRect::setAutoMargins is enabled, the padding is the additional outer most space,
 | |
|   that is left blank.
 | |
|   
 | |
|   The axis padding has no meaning if \ref QCPAxisRect::setAutoMargins is disabled.
 | |
|   
 | |
|   \see setLabelPadding, setTickLabelPadding
 | |
| */
 | |
| void QCPAxis::setPadding(int padding)
 | |
| {
 | |
|   if (mPadding != padding)
 | |
|   {
 | |
|     mPadding = padding;
 | |
|     mCachedMarginValid = false;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the offset the axis has to its axis rect side.
 | |
|   
 | |
|   If an axis rect side has multiple axes and automatic margin calculation is enabled for that side,
 | |
|   only the offset of the inner most axis has meaning (even if it is set to be invisible). The
 | |
|   offset of the other, outer axes is controlled automatically, to place them at appropriate
 | |
|   positions.
 | |
| */
 | |
| void QCPAxis::setOffset(int offset)
 | |
| {
 | |
|   mAxisPainter->offset = offset;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the font that is used for tick labels when they are selected.
 | |
|   
 | |
|   \see setTickLabelFont, setSelectableParts, setSelectedParts, QCustomPlot::setInteractions
 | |
| */
 | |
| void QCPAxis::setSelectedTickLabelFont(const QFont &font)
 | |
| {
 | |
|   if (font != mSelectedTickLabelFont)
 | |
|   {
 | |
|     mSelectedTickLabelFont = font;
 | |
|     // don't set mCachedMarginValid to false here because margin calculation is always done with non-selected fonts
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the font that is used for the axis label when it is selected.
 | |
|   
 | |
|   \see setLabelFont, setSelectableParts, setSelectedParts, QCustomPlot::setInteractions
 | |
| */
 | |
| void QCPAxis::setSelectedLabelFont(const QFont &font)
 | |
| {
 | |
|   mSelectedLabelFont = font;
 | |
|   // don't set mCachedMarginValid to false here because margin calculation is always done with non-selected fonts
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the color that is used for tick labels when they are selected.
 | |
|   
 | |
|   \see setTickLabelColor, setSelectableParts, setSelectedParts, QCustomPlot::setInteractions
 | |
| */
 | |
| void QCPAxis::setSelectedTickLabelColor(const QColor &color)
 | |
| {
 | |
|   if (color != mSelectedTickLabelColor)
 | |
|   {
 | |
|     mSelectedTickLabelColor = color;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the color that is used for the axis label when it is selected.
 | |
|   
 | |
|   \see setLabelColor, setSelectableParts, setSelectedParts, QCustomPlot::setInteractions
 | |
| */
 | |
| void QCPAxis::setSelectedLabelColor(const QColor &color)
 | |
| {
 | |
|   mSelectedLabelColor = color;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the pen that is used to draw the axis base line when selected.
 | |
|   
 | |
|   \see setBasePen, setSelectableParts, setSelectedParts, QCustomPlot::setInteractions
 | |
| */
 | |
| void QCPAxis::setSelectedBasePen(const QPen &pen)
 | |
| {
 | |
|   mSelectedBasePen = pen;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the pen that is used to draw the (major) ticks when selected.
 | |
|   
 | |
|   \see setTickPen, setSelectableParts, setSelectedParts, QCustomPlot::setInteractions
 | |
| */
 | |
| void QCPAxis::setSelectedTickPen(const QPen &pen)
 | |
| {
 | |
|   mSelectedTickPen = pen;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the pen that is used to draw the subticks when selected.
 | |
|   
 | |
|   \see setSubTickPen, setSelectableParts, setSelectedParts, QCustomPlot::setInteractions
 | |
| */
 | |
| void QCPAxis::setSelectedSubTickPen(const QPen &pen)
 | |
| {
 | |
|   mSelectedSubTickPen = pen;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the style for the lower axis ending. See the documentation of QCPLineEnding for available
 | |
|   styles.
 | |
|   
 | |
|   For horizontal axes, this method refers to the left ending, for vertical axes the bottom ending.
 | |
|   Note that this meaning does not change when the axis range is reversed with \ref
 | |
|   setRangeReversed.
 | |
|   
 | |
|   \see setUpperEnding
 | |
| */
 | |
| void QCPAxis::setLowerEnding(const QCPLineEnding &ending)
 | |
| {
 | |
|   mAxisPainter->lowerEnding = ending;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the style for the upper axis ending. See the documentation of QCPLineEnding for available
 | |
|   styles.
 | |
|   
 | |
|   For horizontal axes, this method refers to the right ending, for vertical axes the top ending.
 | |
|   Note that this meaning does not change when the axis range is reversed with \ref
 | |
|   setRangeReversed.
 | |
|   
 | |
|   \see setLowerEnding
 | |
| */
 | |
| void QCPAxis::setUpperEnding(const QCPLineEnding &ending)
 | |
| {
 | |
|   mAxisPainter->upperEnding = ending;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   If the scale type (\ref setScaleType) is \ref stLinear, \a diff is added to the lower and upper
 | |
|   bounds of the range. The range is simply moved by \a diff.
 | |
|   
 | |
|   If the scale type is \ref stLogarithmic, the range bounds are multiplied by \a diff. This
 | |
|   corresponds to an apparent "linear" move in logarithmic scaling by a distance of log(diff).
 | |
| */
 | |
| void QCPAxis::moveRange(double diff)
 | |
| {
 | |
|   QCPRange oldRange = mRange;
 | |
|   if (mScaleType == stLinear)
 | |
|   {
 | |
|     mRange.lower += diff;
 | |
|     mRange.upper += diff;
 | |
|   } else // mScaleType == stLogarithmic
 | |
|   {
 | |
|     mRange.lower *= diff;
 | |
|     mRange.upper *= diff;
 | |
|   }
 | |
|   emit rangeChanged(mRange);
 | |
|   emit rangeChanged(mRange, oldRange);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Scales the range of this axis by \a factor around the center of the current axis range. For
 | |
|   example, if \a factor is 2.0, then the axis range will double its size, and the point at the axis
 | |
|   range center won't have changed its position in the QCustomPlot widget (i.e. coordinates around
 | |
|   the center will have moved symmetrically closer).
 | |
| 
 | |
|   If you wish to scale around a different coordinate than the current axis range center, use the
 | |
|   overload \ref scaleRange(double factor, double center).
 | |
| */
 | |
| void QCPAxis::scaleRange(double factor)
 | |
| {
 | |
|   scaleRange(factor, range().center());
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
| 
 | |
|   Scales the range of this axis by \a factor around the coordinate \a center. For example, if \a
 | |
|   factor is 2.0, \a center is 1.0, then the axis range will double its size, and the point at
 | |
|   coordinate 1.0 won't have changed its position in the QCustomPlot widget (i.e. coordinates
 | |
|   around 1.0 will have moved symmetrically closer to 1.0).
 | |
| 
 | |
|   \see scaleRange(double factor)
 | |
| */
 | |
| void QCPAxis::scaleRange(double factor, double center)
 | |
| {
 | |
|   QCPRange oldRange = mRange;
 | |
|   if (mScaleType == stLinear)
 | |
|   {
 | |
|     QCPRange newRange;
 | |
|     newRange.lower = (mRange.lower-center)*factor + center;
 | |
|     newRange.upper = (mRange.upper-center)*factor + center;
 | |
|     if (QCPRange::validRange(newRange))
 | |
|       mRange = newRange.sanitizedForLinScale();
 | |
|   } else // mScaleType == stLogarithmic
 | |
|   {
 | |
|     if ((mRange.upper < 0 && center < 0) || (mRange.upper > 0 && center > 0)) // make sure center has same sign as range
 | |
|     {
 | |
|       QCPRange newRange;
 | |
|       newRange.lower = qPow(mRange.lower/center, factor)*center;
 | |
|       newRange.upper = qPow(mRange.upper/center, factor)*center;
 | |
|       if (QCPRange::validRange(newRange))
 | |
|         mRange = newRange.sanitizedForLogScale();
 | |
|     } else
 | |
|       qDebug() << Q_FUNC_INFO << "Center of scaling operation doesn't lie in same logarithmic sign domain as range:" << center;
 | |
|   }
 | |
|   emit rangeChanged(mRange);
 | |
|   emit rangeChanged(mRange, oldRange);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Scales the range of this axis to have a certain scale \a ratio to \a otherAxis. The scaling will
 | |
|   be done around the center of the current axis range.
 | |
| 
 | |
|   For example, if \a ratio is 1, this axis is the \a yAxis and \a otherAxis is \a xAxis, graphs
 | |
|   plotted with those axes will appear in a 1:1 aspect ratio, independent of the aspect ratio the
 | |
|   axis rect has.
 | |
| 
 | |
|   This is an operation that changes the range of this axis once, it doesn't fix the scale ratio
 | |
|   indefinitely. Note that calling this function in the constructor of the QCustomPlot's parent
 | |
|   won't have the desired effect, since the widget dimensions aren't defined yet, and a resizeEvent
 | |
|   will follow.
 | |
| */
 | |
| void QCPAxis::setScaleRatio(const QCPAxis *otherAxis, double ratio)
 | |
| {
 | |
|   int otherPixelSize, ownPixelSize;
 | |
|   
 | |
|   if (otherAxis->orientation() == Qt::Horizontal)
 | |
|     otherPixelSize = otherAxis->axisRect()->width();
 | |
|   else
 | |
|     otherPixelSize = otherAxis->axisRect()->height();
 | |
|   
 | |
|   if (orientation() == Qt::Horizontal)
 | |
|     ownPixelSize = axisRect()->width();
 | |
|   else
 | |
|     ownPixelSize = axisRect()->height();
 | |
|   
 | |
|   double newRangeSize = ratio*otherAxis->range().size()*ownPixelSize/(double)otherPixelSize;
 | |
|   setRange(range().center(), newRangeSize, Qt::AlignCenter);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Changes the axis range such that all plottables associated with this axis are fully visible in
 | |
|   that dimension.
 | |
|   
 | |
|   \see QCPAbstractPlottable::rescaleAxes, QCustomPlot::rescaleAxes
 | |
| */
 | |
| void QCPAxis::rescale(bool onlyVisiblePlottables)
 | |
| {
 | |
|   QList<QCPAbstractPlottable*> p = plottables();
 | |
|   QCPRange newRange;
 | |
|   bool haveRange = false;
 | |
|   for (int i=0; i<p.size(); ++i)
 | |
|   {
 | |
|     if (!p.at(i)->realVisibility() && onlyVisiblePlottables)
 | |
|       continue;
 | |
|     QCPRange plottableRange;
 | |
|     bool currentFoundRange;
 | |
|     QCP::SignDomain signDomain = QCP::sdBoth;
 | |
|     if (mScaleType == stLogarithmic)
 | |
|       signDomain = (mRange.upper < 0 ? QCP::sdNegative : QCP::sdPositive);
 | |
|     if (p.at(i)->keyAxis() == this)
 | |
|       plottableRange = p.at(i)->getKeyRange(currentFoundRange, signDomain);
 | |
|     else
 | |
|       plottableRange = p.at(i)->getValueRange(currentFoundRange, signDomain);
 | |
|     if (currentFoundRange)
 | |
|     {
 | |
|       if (!haveRange)
 | |
|         newRange = plottableRange;
 | |
|       else
 | |
|         newRange.expand(plottableRange);
 | |
|       haveRange = true;
 | |
|     }
 | |
|   }
 | |
|   if (haveRange)
 | |
|   {
 | |
|     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
 | |
|     {
 | |
|       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
 | |
|       if (mScaleType == stLinear)
 | |
|       {
 | |
|         newRange.lower = center-mRange.size()/2.0;
 | |
|         newRange.upper = center+mRange.size()/2.0;
 | |
|       } else // mScaleType == stLogarithmic
 | |
|       {
 | |
|         newRange.lower = center/qSqrt(mRange.upper/mRange.lower);
 | |
|         newRange.upper = center*qSqrt(mRange.upper/mRange.lower);
 | |
|       }
 | |
|     }
 | |
|     setRange(newRange);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Transforms \a value, in pixel coordinates of the QCustomPlot widget, to axis coordinates.
 | |
| */
 | |
| double QCPAxis::pixelToCoord(double value) const
 | |
| {
 | |
|   if (orientation() == Qt::Horizontal)
 | |
|   {
 | |
|     if (mScaleType == stLinear)
 | |
|     {
 | |
|       if (!mRangeReversed)
 | |
|         return (value-mAxisRect->left())/(double)mAxisRect->width()*mRange.size()+mRange.lower;
 | |
|       else
 | |
|         return -(value-mAxisRect->left())/(double)mAxisRect->width()*mRange.size()+mRange.upper;
 | |
|     } else // mScaleType == stLogarithmic
 | |
|     {
 | |
|       if (!mRangeReversed)
 | |
|         return qPow(mRange.upper/mRange.lower, (value-mAxisRect->left())/(double)mAxisRect->width())*mRange.lower;
 | |
|       else
 | |
|         return qPow(mRange.upper/mRange.lower, (mAxisRect->left()-value)/(double)mAxisRect->width())*mRange.upper;
 | |
|     }
 | |
|   } else // orientation() == Qt::Vertical
 | |
|   {
 | |
|     if (mScaleType == stLinear)
 | |
|     {
 | |
|       if (!mRangeReversed)
 | |
|         return (mAxisRect->bottom()-value)/(double)mAxisRect->height()*mRange.size()+mRange.lower;
 | |
|       else
 | |
|         return -(mAxisRect->bottom()-value)/(double)mAxisRect->height()*mRange.size()+mRange.upper;
 | |
|     } else // mScaleType == stLogarithmic
 | |
|     {
 | |
|       if (!mRangeReversed)
 | |
|         return qPow(mRange.upper/mRange.lower, (mAxisRect->bottom()-value)/(double)mAxisRect->height())*mRange.lower;
 | |
|       else
 | |
|         return qPow(mRange.upper/mRange.lower, (value-mAxisRect->bottom())/(double)mAxisRect->height())*mRange.upper;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Transforms \a value, in coordinates of the axis, to pixel coordinates of the QCustomPlot widget.
 | |
| */
 | |
| double QCPAxis::coordToPixel(double value) const
 | |
| {
 | |
|   if (orientation() == Qt::Horizontal)
 | |
|   {
 | |
|     if (mScaleType == stLinear)
 | |
|     {
 | |
|       if (!mRangeReversed)
 | |
|         return (value-mRange.lower)/mRange.size()*mAxisRect->width()+mAxisRect->left();
 | |
|       else
 | |
|         return (mRange.upper-value)/mRange.size()*mAxisRect->width()+mAxisRect->left();
 | |
|     } else // mScaleType == stLogarithmic
 | |
|     {
 | |
|       if (value >= 0 && mRange.upper < 0) // invalid value for logarithmic scale, just draw it outside visible range
 | |
|         return !mRangeReversed ? mAxisRect->right()+200 : mAxisRect->left()-200;
 | |
|       else if (value <= 0 && mRange.upper > 0) // invalid value for logarithmic scale, just draw it outside visible range
 | |
|         return !mRangeReversed ? mAxisRect->left()-200 : mAxisRect->right()+200;
 | |
|       else
 | |
|       {
 | |
|         if (!mRangeReversed)
 | |
|           return qLn(value/mRange.lower)/qLn(mRange.upper/mRange.lower)*mAxisRect->width()+mAxisRect->left();
 | |
|         else
 | |
|           return qLn(mRange.upper/value)/qLn(mRange.upper/mRange.lower)*mAxisRect->width()+mAxisRect->left();
 | |
|       }
 | |
|     }
 | |
|   } else // orientation() == Qt::Vertical
 | |
|   {
 | |
|     if (mScaleType == stLinear)
 | |
|     {
 | |
|       if (!mRangeReversed)
 | |
|         return mAxisRect->bottom()-(value-mRange.lower)/mRange.size()*mAxisRect->height();
 | |
|       else
 | |
|         return mAxisRect->bottom()-(mRange.upper-value)/mRange.size()*mAxisRect->height();
 | |
|     } else // mScaleType == stLogarithmic
 | |
|     {
 | |
|       if (value >= 0 && mRange.upper < 0) // invalid value for logarithmic scale, just draw it outside visible range
 | |
|         return !mRangeReversed ? mAxisRect->top()-200 : mAxisRect->bottom()+200;
 | |
|       else if (value <= 0 && mRange.upper > 0) // invalid value for logarithmic scale, just draw it outside visible range
 | |
|         return !mRangeReversed ? mAxisRect->bottom()+200 : mAxisRect->top()-200;
 | |
|       else
 | |
|       {
 | |
|         if (!mRangeReversed)
 | |
|           return mAxisRect->bottom()-qLn(value/mRange.lower)/qLn(mRange.upper/mRange.lower)*mAxisRect->height();
 | |
|         else
 | |
|           return mAxisRect->bottom()-qLn(mRange.upper/value)/qLn(mRange.upper/mRange.lower)*mAxisRect->height();
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the part of the axis that is hit by \a pos (in pixels). The return value of this function
 | |
|   is independent of the user-selectable parts defined with \ref setSelectableParts. Further, this
 | |
|   function does not change the current selection state of the axis.
 | |
|   
 | |
|   If the axis is not visible (\ref setVisible), this function always returns \ref spNone.
 | |
|   
 | |
|   \see setSelectedParts, setSelectableParts, QCustomPlot::setInteractions
 | |
| */
 | |
| QCPAxis::SelectablePart QCPAxis::getPartAt(const QPointF &pos) const
 | |
| {
 | |
|   if (!mVisible)
 | |
|     return spNone;
 | |
|   
 | |
|   if (mAxisPainter->axisSelectionBox().contains(pos.toPoint()))
 | |
|     return spAxis;
 | |
|   else if (mAxisPainter->tickLabelsSelectionBox().contains(pos.toPoint()))
 | |
|     return spTickLabels;
 | |
|   else if (mAxisPainter->labelSelectionBox().contains(pos.toPoint()))
 | |
|     return spAxisLabel;
 | |
|   else
 | |
|     return spNone;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| double QCPAxis::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
 | |
| {
 | |
|   if (!mParentPlot) return -1;
 | |
|   SelectablePart part = getPartAt(pos);
 | |
|   if ((onlySelectable && !mSelectableParts.testFlag(part)) || part == spNone)
 | |
|     return -1;
 | |
|   
 | |
|   if (details)
 | |
|     details->setValue(part);
 | |
|   return mParentPlot->selectionTolerance()*0.99;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns a list of all the plottables that have this axis as key or value axis.
 | |
|   
 | |
|   If you are only interested in plottables of type QCPGraph, see \ref graphs.
 | |
|   
 | |
|   \see graphs, items
 | |
| */
 | |
| QList<QCPAbstractPlottable*> QCPAxis::plottables() const
 | |
| {
 | |
|   QList<QCPAbstractPlottable*> result;
 | |
|   if (!mParentPlot) return result;
 | |
|   
 | |
|   for (int i=0; i<mParentPlot->mPlottables.size(); ++i)
 | |
|   {
 | |
|     if (mParentPlot->mPlottables.at(i)->keyAxis() == this ||mParentPlot->mPlottables.at(i)->valueAxis() == this)
 | |
|       result.append(mParentPlot->mPlottables.at(i));
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns a list of all the graphs that have this axis as key or value axis.
 | |
|   
 | |
|   \see plottables, items
 | |
| */
 | |
| QList<QCPGraph*> QCPAxis::graphs() const
 | |
| {
 | |
|   QList<QCPGraph*> result;
 | |
|   if (!mParentPlot) return result;
 | |
|   
 | |
|   for (int i=0; i<mParentPlot->mGraphs.size(); ++i)
 | |
|   {
 | |
|     if (mParentPlot->mGraphs.at(i)->keyAxis() == this || mParentPlot->mGraphs.at(i)->valueAxis() == this)
 | |
|       result.append(mParentPlot->mGraphs.at(i));
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns a list of all the items that are associated with this axis. An item is considered
 | |
|   associated with an axis if at least one of its positions uses the axis as key or value axis.
 | |
|   
 | |
|   \see plottables, graphs
 | |
| */
 | |
| QList<QCPAbstractItem*> QCPAxis::items() const
 | |
| {
 | |
|   QList<QCPAbstractItem*> result;
 | |
|   if (!mParentPlot) return result;
 | |
|   
 | |
|   for (int itemId=0; itemId<mParentPlot->mItems.size(); ++itemId)
 | |
|   {
 | |
|     QList<QCPItemPosition*> positions = mParentPlot->mItems.at(itemId)->positions();
 | |
|     for (int posId=0; posId<positions.size(); ++posId)
 | |
|     {
 | |
|       if (positions.at(posId)->keyAxis() == this || positions.at(posId)->valueAxis() == this)
 | |
|       {
 | |
|         result.append(mParentPlot->mItems.at(itemId));
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Transforms a margin side to the logically corresponding axis type. (QCP::msLeft to
 | |
|   QCPAxis::atLeft, QCP::msRight to QCPAxis::atRight, etc.)
 | |
| */
 | |
| QCPAxis::AxisType QCPAxis::marginSideToAxisType(QCP::MarginSide side)
 | |
| {
 | |
|   switch (side)
 | |
|   {
 | |
|     case QCP::msLeft: return atLeft;
 | |
|     case QCP::msRight: return atRight;
 | |
|     case QCP::msTop: return atTop;
 | |
|     case QCP::msBottom: return atBottom;
 | |
|     default: break;
 | |
|   }
 | |
|   qDebug() << Q_FUNC_INFO << "Invalid margin side passed:" << (int)side;
 | |
|   return atLeft;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the axis type that describes the opposite axis of an axis with the specified \a type.
 | |
| */
 | |
| QCPAxis::AxisType QCPAxis::opposite(QCPAxis::AxisType type)
 | |
| {
 | |
|   switch (type)
 | |
|   {
 | |
|     case atLeft: return atRight; break;
 | |
|     case atRight: return atLeft; break;
 | |
|     case atBottom: return atTop; break;
 | |
|     case atTop: return atBottom; break;
 | |
|     default: qDebug() << Q_FUNC_INFO << "invalid axis type"; return atLeft; break;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPAxis::selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged)
 | |
| {
 | |
|   Q_UNUSED(event)
 | |
|   SelectablePart part = details.value<SelectablePart>();
 | |
|   if (mSelectableParts.testFlag(part))
 | |
|   {
 | |
|     SelectableParts selBefore = mSelectedParts;
 | |
|     setSelectedParts(additive ? mSelectedParts^part : part);
 | |
|     if (selectionStateChanged)
 | |
|       *selectionStateChanged = mSelectedParts != selBefore;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPAxis::deselectEvent(bool *selectionStateChanged)
 | |
| {
 | |
|   SelectableParts selBefore = mSelectedParts;
 | |
|   setSelectedParts(mSelectedParts & ~mSelectableParts);
 | |
|   if (selectionStateChanged)
 | |
|     *selectionStateChanged = mSelectedParts != selBefore;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   A convenience function to easily set the QPainter::Antialiased hint on the provided \a painter
 | |
|   before drawing axis lines.
 | |
| 
 | |
|   This is the antialiasing state the painter passed to the \ref draw method is in by default.
 | |
|   
 | |
|   This function takes into account the local setting of the antialiasing flag as well as the
 | |
|   overrides set with \ref QCustomPlot::setAntialiasedElements and \ref
 | |
|   QCustomPlot::setNotAntialiasedElements.
 | |
|   
 | |
|   \seebaseclassmethod
 | |
|   
 | |
|   \see setAntialiased
 | |
| */
 | |
| void QCPAxis::applyDefaultAntialiasingHint(QCPPainter *painter) const
 | |
| {
 | |
|   applyAntialiasingHint(painter, mAntialiased, QCP::aeAxes);
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Draws the axis with the specified \a painter, using the internal QCPAxisPainterPrivate instance.
 | |
| 
 | |
|   \seebaseclassmethod
 | |
| */
 | |
| void QCPAxis::draw(QCPPainter *painter)
 | |
| {
 | |
|   QVector<double> subTickPositions; // the final coordToPixel transformed vector passed to QCPAxisPainter
 | |
|   QVector<double> tickPositions; // the final coordToPixel transformed vector passed to QCPAxisPainter
 | |
|   QVector<QString> tickLabels; // the final vector passed to QCPAxisPainter
 | |
|   tickPositions.reserve(mTickVector.size());
 | |
|   tickLabels.reserve(mTickVector.size());
 | |
|   subTickPositions.reserve(mSubTickVector.size());
 | |
|   
 | |
|   if (mTicks)
 | |
|   {
 | |
|     for (int i=0; i<mTickVector.size(); ++i)
 | |
|     {
 | |
|       tickPositions.append(coordToPixel(mTickVector.at(i)));
 | |
|       if (mTickLabels)
 | |
|         tickLabels.append(mTickVectorLabels.at(i));
 | |
|     }
 | |
| 
 | |
|     if (mSubTicks)
 | |
|     {
 | |
|       const int subTickCount = mSubTickVector.size();
 | |
|       for (int i=0; i<subTickCount; ++i)
 | |
|         subTickPositions.append(coordToPixel(mSubTickVector.at(i)));
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   // transfer all properties of this axis to QCPAxisPainterPrivate which it needs to draw the axis.
 | |
|   // Note that some axis painter properties are already set by direct feed-through with QCPAxis setters
 | |
|   mAxisPainter->type = mAxisType;
 | |
|   mAxisPainter->basePen = getBasePen();
 | |
|   mAxisPainter->labelFont = getLabelFont();
 | |
|   mAxisPainter->labelColor = getLabelColor();
 | |
|   mAxisPainter->label = mLabel;
 | |
|   mAxisPainter->substituteExponent = mNumberBeautifulPowers;
 | |
|   mAxisPainter->tickPen = getTickPen();
 | |
|   mAxisPainter->subTickPen = getSubTickPen();
 | |
|   mAxisPainter->tickLabelFont = getTickLabelFont();
 | |
|   mAxisPainter->tickLabelColor = getTickLabelColor();
 | |
|   mAxisPainter->axisRect = mAxisRect->rect();
 | |
|   mAxisPainter->viewportRect = mParentPlot->viewport();
 | |
|   mAxisPainter->abbreviateDecimalPowers = mScaleType == stLogarithmic;
 | |
|   mAxisPainter->reversedEndings = mRangeReversed;
 | |
|   mAxisPainter->tickPositions = tickPositions;
 | |
|   mAxisPainter->tickLabels = tickLabels;
 | |
|   mAxisPainter->subTickPositions = subTickPositions;
 | |
|   mAxisPainter->draw(painter);
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Prepares the internal tick vector, sub tick vector and tick label vector. This is done by calling
 | |
|   QCPAxisTicker::generate on the currently installed ticker.
 | |
|   
 | |
|   If a change in the label text/count is detected, the cached axis margin is invalidated to make
 | |
|   sure the next margin calculation recalculates the label sizes and returns an up-to-date value.
 | |
| */
 | |
| void QCPAxis::setupTickVectors()
 | |
| {
 | |
|   if (!mParentPlot) return;
 | |
|   if ((!mTicks && !mTickLabels && !mGrid->visible()) || mRange.size() <= 0) return;
 | |
|   
 | |
|   QVector<QString> oldLabels = mTickVectorLabels;
 | |
|   mTicker->generate(mRange, mParentPlot->locale(), mNumberFormatChar, mNumberPrecision, mTickVector, mSubTicks ? &mSubTickVector : 0, mTickLabels ? &mTickVectorLabels : 0);
 | |
|   mCachedMarginValid &= mTickVectorLabels == oldLabels; // if labels have changed, margin might have changed, too
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns the pen that is used to draw the axis base line. Depending on the selection state, this
 | |
|   is either mSelectedBasePen or mBasePen.
 | |
| */
 | |
| QPen QCPAxis::getBasePen() const
 | |
| {
 | |
|   return mSelectedParts.testFlag(spAxis) ? mSelectedBasePen : mBasePen;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns the pen that is used to draw the (major) ticks. Depending on the selection state, this
 | |
|   is either mSelectedTickPen or mTickPen.
 | |
| */
 | |
| QPen QCPAxis::getTickPen() const
 | |
| {
 | |
|   return mSelectedParts.testFlag(spAxis) ? mSelectedTickPen : mTickPen;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns the pen that is used to draw the subticks. Depending on the selection state, this
 | |
|   is either mSelectedSubTickPen or mSubTickPen.
 | |
| */
 | |
| QPen QCPAxis::getSubTickPen() const
 | |
| {
 | |
|   return mSelectedParts.testFlag(spAxis) ? mSelectedSubTickPen : mSubTickPen;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns the font that is used to draw the tick labels. Depending on the selection state, this
 | |
|   is either mSelectedTickLabelFont or mTickLabelFont.
 | |
| */
 | |
| QFont QCPAxis::getTickLabelFont() const
 | |
| {
 | |
|   return mSelectedParts.testFlag(spTickLabels) ? mSelectedTickLabelFont : mTickLabelFont;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns the font that is used to draw the axis label. Depending on the selection state, this
 | |
|   is either mSelectedLabelFont or mLabelFont.
 | |
| */
 | |
| QFont QCPAxis::getLabelFont() const
 | |
| {
 | |
|   return mSelectedParts.testFlag(spAxisLabel) ? mSelectedLabelFont : mLabelFont;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns the color that is used to draw the tick labels. Depending on the selection state, this
 | |
|   is either mSelectedTickLabelColor or mTickLabelColor.
 | |
| */
 | |
| QColor QCPAxis::getTickLabelColor() const
 | |
| {
 | |
|   return mSelectedParts.testFlag(spTickLabels) ? mSelectedTickLabelColor : mTickLabelColor;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns the color that is used to draw the axis label. Depending on the selection state, this
 | |
|   is either mSelectedLabelColor or mLabelColor.
 | |
| */
 | |
| QColor QCPAxis::getLabelColor() const
 | |
| {
 | |
|   return mSelectedParts.testFlag(spAxisLabel) ? mSelectedLabelColor : mLabelColor;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns the appropriate outward margin for this axis. It is needed if \ref
 | |
|   QCPAxisRect::setAutoMargins is set to true on the parent axis rect. An axis with axis type \ref
 | |
|   atLeft will return an appropriate left margin, \ref atBottom will return an appropriate bottom
 | |
|   margin and so forth. For the calculation, this function goes through similar steps as \ref draw,
 | |
|   so changing one function likely requires the modification of the other one as well.
 | |
|   
 | |
|   The margin consists of the outward tick length, tick label padding, tick label size, label
 | |
|   padding, label size, and padding.
 | |
|   
 | |
|   The margin is cached internally, so repeated calls while leaving the axis range, fonts, etc.
 | |
|   unchanged are very fast.
 | |
| */
 | |
| int QCPAxis::calculateMargin()
 | |
| {
 | |
|   if (!mVisible) // if not visible, directly return 0, don't cache 0 because we can't react to setVisible in QCPAxis
 | |
|     return 0;
 | |
|   
 | |
|   if (mCachedMarginValid)
 | |
|     return mCachedMargin;
 | |
|   
 | |
|   // run through similar steps as QCPAxis::draw, and calculate margin needed to fit axis and its labels
 | |
|   int margin = 0;
 | |
|   
 | |
|   QVector<double> tickPositions; // the final coordToPixel transformed vector passed to QCPAxisPainter
 | |
|   QVector<QString> tickLabels; // the final vector passed to QCPAxisPainter
 | |
|   tickPositions.reserve(mTickVector.size());
 | |
|   tickLabels.reserve(mTickVector.size());
 | |
|   
 | |
|   if (mTicks)
 | |
|   {
 | |
|     for (int i=0; i<mTickVector.size(); ++i)
 | |
|     {
 | |
|       tickPositions.append(coordToPixel(mTickVector.at(i)));
 | |
|       if (mTickLabels)
 | |
|         tickLabels.append(mTickVectorLabels.at(i));
 | |
|     }
 | |
|   }
 | |
|   // transfer all properties of this axis to QCPAxisPainterPrivate which it needs to calculate the size.
 | |
|   // Note that some axis painter properties are already set by direct feed-through with QCPAxis setters
 | |
|   mAxisPainter->type = mAxisType;
 | |
|   mAxisPainter->labelFont = getLabelFont();
 | |
|   mAxisPainter->label = mLabel;
 | |
|   mAxisPainter->tickLabelFont = mTickLabelFont;
 | |
|   mAxisPainter->axisRect = mAxisRect->rect();
 | |
|   mAxisPainter->viewportRect = mParentPlot->viewport();
 | |
|   mAxisPainter->tickPositions = tickPositions;
 | |
|   mAxisPainter->tickLabels = tickLabels;
 | |
|   margin += mAxisPainter->size();
 | |
|   margin += mPadding;
 | |
| 
 | |
|   mCachedMargin = margin;
 | |
|   mCachedMarginValid = true;
 | |
|   return margin;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| QCP::Interaction QCPAxis::selectionCategory() const
 | |
| {
 | |
|   return QCP::iSelectAxes;
 | |
| }
 | |
| 
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPAxisPainterPrivate
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPAxisPainterPrivate
 | |
| 
 | |
|   \internal
 | |
|   \brief (Private)
 | |
|   
 | |
|   This is a private class and not part of the public QCustomPlot interface.
 | |
|   
 | |
|   It is used by QCPAxis to do the low-level drawing of axis backbone, tick marks, tick labels and
 | |
|   axis label. It also buffers the labels to reduce replot times. The parameters are configured by
 | |
|   directly accessing the public member variables.
 | |
| */
 | |
| 
 | |
| /*!
 | |
|   Constructs a QCPAxisPainterPrivate instance. Make sure to not create a new instance on every
 | |
|   redraw, to utilize the caching mechanisms.
 | |
| */
 | |
| QCPAxisPainterPrivate::QCPAxisPainterPrivate(QCustomPlot *parentPlot) :
 | |
|   type(QCPAxis::atLeft),
 | |
|   basePen(QPen(Qt::black, 0, Qt::SolidLine, Qt::SquareCap)),
 | |
|   lowerEnding(QCPLineEnding::esNone),
 | |
|   upperEnding(QCPLineEnding::esNone),
 | |
|   labelPadding(0),
 | |
|   tickLabelPadding(0),
 | |
|   tickLabelRotation(0),
 | |
|   tickLabelSide(QCPAxis::lsOutside),
 | |
|   substituteExponent(true),
 | |
|   numberMultiplyCross(false),
 | |
|   tickLengthIn(5),
 | |
|   tickLengthOut(0),
 | |
|   subTickLengthIn(2),
 | |
|   subTickLengthOut(0),
 | |
|   tickPen(QPen(Qt::black, 0, Qt::SolidLine, Qt::SquareCap)),
 | |
|   subTickPen(QPen(Qt::black, 0, Qt::SolidLine, Qt::SquareCap)),
 | |
|   offset(0),
 | |
|   abbreviateDecimalPowers(false),
 | |
|   reversedEndings(false),
 | |
|   mParentPlot(parentPlot),
 | |
|   mLabelCache(16) // cache at most 16 (tick) labels
 | |
| {
 | |
| }
 | |
| 
 | |
| QCPAxisPainterPrivate::~QCPAxisPainterPrivate()
 | |
| {
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Draws the axis with the specified \a painter.
 | |
|   
 | |
|   The selection boxes (mAxisSelectionBox, mTickLabelsSelectionBox, mLabelSelectionBox) are set
 | |
|   here, too.
 | |
| */
 | |
| void QCPAxisPainterPrivate::draw(QCPPainter *painter)
 | |
| {
 | |
|   QByteArray newHash = generateLabelParameterHash();
 | |
|   if (newHash != mLabelParameterHash)
 | |
|   {
 | |
|     mLabelCache.clear();
 | |
|     mLabelParameterHash = newHash;
 | |
|   }
 | |
|   
 | |
|   QPoint origin;
 | |
|   switch (type)
 | |
|   {
 | |
|     case QCPAxis::atLeft:   origin = axisRect.bottomLeft() +QPoint(-offset, 0); break;
 | |
|     case QCPAxis::atRight:  origin = axisRect.bottomRight()+QPoint(+offset, 0); break;
 | |
|     case QCPAxis::atTop:    origin = axisRect.topLeft()    +QPoint(0, -offset); break;
 | |
|     case QCPAxis::atBottom: origin = axisRect.bottomLeft() +QPoint(0, +offset); break;
 | |
|   }
 | |
| 
 | |
|   double xCor = 0, yCor = 0; // paint system correction, for pixel exact matches (affects baselines and ticks of top/right axes)
 | |
|   switch (type)
 | |
|   {
 | |
|     case QCPAxis::atTop: yCor = -1; break;
 | |
|     case QCPAxis::atRight: xCor = 1; break;
 | |
|     default: break;
 | |
|   }
 | |
|   int margin = 0;
 | |
|   // draw baseline:
 | |
|   QLineF baseLine;
 | |
|   painter->setPen(basePen);
 | |
|   if (QCPAxis::orientation(type) == Qt::Horizontal)
 | |
|     baseLine.setPoints(origin+QPointF(xCor, yCor), origin+QPointF(axisRect.width()+xCor, yCor));
 | |
|   else
 | |
|     baseLine.setPoints(origin+QPointF(xCor, yCor), origin+QPointF(xCor, -axisRect.height()+yCor));
 | |
|   if (reversedEndings)
 | |
|     baseLine = QLineF(baseLine.p2(), baseLine.p1()); // won't make a difference for line itself, but for line endings later
 | |
|   painter->drawLine(baseLine);
 | |
|   
 | |
|   // draw ticks:
 | |
|   if (!tickPositions.isEmpty())
 | |
|   {
 | |
|     painter->setPen(tickPen);
 | |
|     int tickDir = (type == QCPAxis::atBottom || type == QCPAxis::atRight) ? -1 : 1; // direction of ticks ("inward" is right for left axis and left for right axis)
 | |
|     if (QCPAxis::orientation(type) == Qt::Horizontal)
 | |
|     {
 | |
|       for (int i=0; i<tickPositions.size(); ++i)
 | |
|         painter->drawLine(QLineF(tickPositions.at(i)+xCor, origin.y()-tickLengthOut*tickDir+yCor, tickPositions.at(i)+xCor, origin.y()+tickLengthIn*tickDir+yCor));
 | |
|     } else
 | |
|     {
 | |
|       for (int i=0; i<tickPositions.size(); ++i)
 | |
|         painter->drawLine(QLineF(origin.x()-tickLengthOut*tickDir+xCor, tickPositions.at(i)+yCor, origin.x()+tickLengthIn*tickDir+xCor, tickPositions.at(i)+yCor));
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   // draw subticks:
 | |
|   if (!subTickPositions.isEmpty())
 | |
|   {
 | |
|     painter->setPen(subTickPen);
 | |
|     // direction of ticks ("inward" is right for left axis and left for right axis)
 | |
|     int tickDir = (type == QCPAxis::atBottom || type == QCPAxis::atRight) ? -1 : 1;
 | |
|     if (QCPAxis::orientation(type) == Qt::Horizontal)
 | |
|     {
 | |
|       for (int i=0; i<subTickPositions.size(); ++i)
 | |
|         painter->drawLine(QLineF(subTickPositions.at(i)+xCor, origin.y()-subTickLengthOut*tickDir+yCor, subTickPositions.at(i)+xCor, origin.y()+subTickLengthIn*tickDir+yCor));
 | |
|     } else
 | |
|     {
 | |
|       for (int i=0; i<subTickPositions.size(); ++i)
 | |
|         painter->drawLine(QLineF(origin.x()-subTickLengthOut*tickDir+xCor, subTickPositions.at(i)+yCor, origin.x()+subTickLengthIn*tickDir+xCor, subTickPositions.at(i)+yCor));
 | |
|     }
 | |
|   }
 | |
|   margin += qMax(0, qMax(tickLengthOut, subTickLengthOut));
 | |
|   
 | |
|   // draw axis base endings:
 | |
|   bool antialiasingBackup = painter->antialiasing();
 | |
|   painter->setAntialiasing(true); // always want endings to be antialiased, even if base and ticks themselves aren't
 | |
|   painter->setBrush(QBrush(basePen.color()));
 | |
|   QCPVector2D baseLineVector(baseLine.dx(), baseLine.dy());
 | |
|   if (lowerEnding.style() != QCPLineEnding::esNone)
 | |
|     lowerEnding.draw(painter, QCPVector2D(baseLine.p1())-baseLineVector.normalized()*lowerEnding.realLength()*(lowerEnding.inverted()?-1:1), -baseLineVector);
 | |
|   if (upperEnding.style() != QCPLineEnding::esNone)
 | |
|     upperEnding.draw(painter, QCPVector2D(baseLine.p2())+baseLineVector.normalized()*upperEnding.realLength()*(upperEnding.inverted()?-1:1), baseLineVector);
 | |
|   painter->setAntialiasing(antialiasingBackup);
 | |
|   
 | |
|   // tick labels:
 | |
|   QRect oldClipRect;
 | |
|   if (tickLabelSide == QCPAxis::lsInside) // if using inside labels, clip them to the axis rect
 | |
|   {
 | |
|     oldClipRect = painter->clipRegion().boundingRect();
 | |
|     painter->setClipRect(axisRect);
 | |
|   }
 | |
|   QSize tickLabelsSize(0, 0); // size of largest tick label, for offset calculation of axis label
 | |
|   if (!tickLabels.isEmpty())
 | |
|   {
 | |
|     if (tickLabelSide == QCPAxis::lsOutside)
 | |
|       margin += tickLabelPadding;
 | |
|     painter->setFont(tickLabelFont);
 | |
|     painter->setPen(QPen(tickLabelColor));
 | |
|     const int maxLabelIndex = qMin(tickPositions.size(), tickLabels.size());
 | |
|     int distanceToAxis = margin;
 | |
|     if (tickLabelSide == QCPAxis::lsInside)
 | |
|       distanceToAxis = -(qMax(tickLengthIn, subTickLengthIn)+tickLabelPadding);
 | |
|     for (int i=0; i<maxLabelIndex; ++i)
 | |
|       placeTickLabel(painter, tickPositions.at(i), distanceToAxis, tickLabels.at(i), &tickLabelsSize);
 | |
|     if (tickLabelSide == QCPAxis::lsOutside)
 | |
|       margin += (QCPAxis::orientation(type) == Qt::Horizontal) ? tickLabelsSize.height() : tickLabelsSize.width();
 | |
|   }
 | |
|   if (tickLabelSide == QCPAxis::lsInside)
 | |
|     painter->setClipRect(oldClipRect);
 | |
|   
 | |
|   // axis label:
 | |
|   QRect labelBounds;
 | |
|   if (!label.isEmpty())
 | |
|   {
 | |
|     margin += labelPadding;
 | |
|     painter->setFont(labelFont);
 | |
|     painter->setPen(QPen(labelColor));
 | |
|     labelBounds = painter->fontMetrics().boundingRect(0, 0, 0, 0, Qt::TextDontClip, label);
 | |
|     if (type == QCPAxis::atLeft)
 | |
|     {
 | |
|       QTransform oldTransform = painter->transform();
 | |
|       painter->translate((origin.x()-margin-labelBounds.height()), origin.y());
 | |
|       painter->rotate(-90);
 | |
|       painter->drawText(0, 0, axisRect.height(), labelBounds.height(), Qt::TextDontClip | Qt::AlignCenter, label);
 | |
|       painter->setTransform(oldTransform);
 | |
|     }
 | |
|     else if (type == QCPAxis::atRight)
 | |
|     {
 | |
|       QTransform oldTransform = painter->transform();
 | |
|       painter->translate((origin.x()+margin+labelBounds.height()), origin.y()-axisRect.height());
 | |
|       painter->rotate(90);
 | |
|       painter->drawText(0, 0, axisRect.height(), labelBounds.height(), Qt::TextDontClip | Qt::AlignCenter, label);
 | |
|       painter->setTransform(oldTransform);
 | |
|     }
 | |
|     else if (type == QCPAxis::atTop)
 | |
|       painter->drawText(origin.x(), origin.y()-margin-labelBounds.height(), axisRect.width(), labelBounds.height(), Qt::TextDontClip | Qt::AlignCenter, label);
 | |
|     else if (type == QCPAxis::atBottom)
 | |
|       painter->drawText(origin.x(), origin.y()+margin, axisRect.width(), labelBounds.height(), Qt::TextDontClip | Qt::AlignCenter, label);
 | |
|   }
 | |
|   
 | |
|   // set selection boxes:
 | |
|   int selectionTolerance = 0;
 | |
|   if (mParentPlot)
 | |
|     selectionTolerance = mParentPlot->selectionTolerance();
 | |
|   else
 | |
|     qDebug() << Q_FUNC_INFO << "mParentPlot is null";
 | |
|   int selAxisOutSize = qMax(qMax(tickLengthOut, subTickLengthOut), selectionTolerance);
 | |
|   int selAxisInSize = selectionTolerance;
 | |
|   int selTickLabelSize;
 | |
|   int selTickLabelOffset;
 | |
|   if (tickLabelSide == QCPAxis::lsOutside)
 | |
|   {
 | |
|     selTickLabelSize = (QCPAxis::orientation(type) == Qt::Horizontal ? tickLabelsSize.height() : tickLabelsSize.width());
 | |
|     selTickLabelOffset = qMax(tickLengthOut, subTickLengthOut)+tickLabelPadding;
 | |
|   } else
 | |
|   {
 | |
|     selTickLabelSize = -(QCPAxis::orientation(type) == Qt::Horizontal ? tickLabelsSize.height() : tickLabelsSize.width());
 | |
|     selTickLabelOffset = -(qMax(tickLengthIn, subTickLengthIn)+tickLabelPadding);
 | |
|   }
 | |
|   int selLabelSize = labelBounds.height();
 | |
|   int selLabelOffset = qMax(tickLengthOut, subTickLengthOut)+(!tickLabels.isEmpty() && tickLabelSide == QCPAxis::lsOutside ? tickLabelPadding+selTickLabelSize : 0)+labelPadding;
 | |
|   if (type == QCPAxis::atLeft)
 | |
|   {
 | |
|     mAxisSelectionBox.setCoords(origin.x()-selAxisOutSize, axisRect.top(), origin.x()+selAxisInSize, axisRect.bottom());
 | |
|     mTickLabelsSelectionBox.setCoords(origin.x()-selTickLabelOffset-selTickLabelSize, axisRect.top(), origin.x()-selTickLabelOffset, axisRect.bottom());
 | |
|     mLabelSelectionBox.setCoords(origin.x()-selLabelOffset-selLabelSize, axisRect.top(), origin.x()-selLabelOffset, axisRect.bottom());
 | |
|   } else if (type == QCPAxis::atRight)
 | |
|   {
 | |
|     mAxisSelectionBox.setCoords(origin.x()-selAxisInSize, axisRect.top(), origin.x()+selAxisOutSize, axisRect.bottom());
 | |
|     mTickLabelsSelectionBox.setCoords(origin.x()+selTickLabelOffset+selTickLabelSize, axisRect.top(), origin.x()+selTickLabelOffset, axisRect.bottom());
 | |
|     mLabelSelectionBox.setCoords(origin.x()+selLabelOffset+selLabelSize, axisRect.top(), origin.x()+selLabelOffset, axisRect.bottom());
 | |
|   } else if (type == QCPAxis::atTop)
 | |
|   {
 | |
|     mAxisSelectionBox.setCoords(axisRect.left(), origin.y()-selAxisOutSize, axisRect.right(), origin.y()+selAxisInSize);
 | |
|     mTickLabelsSelectionBox.setCoords(axisRect.left(), origin.y()-selTickLabelOffset-selTickLabelSize, axisRect.right(), origin.y()-selTickLabelOffset);
 | |
|     mLabelSelectionBox.setCoords(axisRect.left(), origin.y()-selLabelOffset-selLabelSize, axisRect.right(), origin.y()-selLabelOffset);
 | |
|   } else if (type == QCPAxis::atBottom)
 | |
|   {
 | |
|     mAxisSelectionBox.setCoords(axisRect.left(), origin.y()-selAxisInSize, axisRect.right(), origin.y()+selAxisOutSize);
 | |
|     mTickLabelsSelectionBox.setCoords(axisRect.left(), origin.y()+selTickLabelOffset+selTickLabelSize, axisRect.right(), origin.y()+selTickLabelOffset);
 | |
|     mLabelSelectionBox.setCoords(axisRect.left(), origin.y()+selLabelOffset+selLabelSize, axisRect.right(), origin.y()+selLabelOffset);
 | |
|   }
 | |
|   mAxisSelectionBox = mAxisSelectionBox.normalized();
 | |
|   mTickLabelsSelectionBox = mTickLabelsSelectionBox.normalized();
 | |
|   mLabelSelectionBox = mLabelSelectionBox.normalized();
 | |
|   // draw hitboxes for debug purposes:
 | |
|   //painter->setBrush(Qt::NoBrush);
 | |
|   //painter->drawRects(QVector<QRect>() << mAxisSelectionBox << mTickLabelsSelectionBox << mLabelSelectionBox);
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns the size ("margin" in QCPAxisRect context, so measured perpendicular to the axis backbone
 | |
|   direction) needed to fit the axis.
 | |
| */
 | |
| int QCPAxisPainterPrivate::size() const
 | |
| {
 | |
|   int result = 0;
 | |
|   
 | |
|   // get length of tick marks pointing outwards:
 | |
|   if (!tickPositions.isEmpty())
 | |
|     result += qMax(0, qMax(tickLengthOut, subTickLengthOut));
 | |
|   
 | |
|   // calculate size of tick labels:
 | |
|   if (tickLabelSide == QCPAxis::lsOutside)
 | |
|   {
 | |
|     QSize tickLabelsSize(0, 0);
 | |
|     if (!tickLabels.isEmpty())
 | |
|     {
 | |
|       for (int i=0; i<tickLabels.size(); ++i)
 | |
|         getMaxTickLabelSize(tickLabelFont, tickLabels.at(i), &tickLabelsSize);
 | |
|       result += QCPAxis::orientation(type) == Qt::Horizontal ? tickLabelsSize.height() : tickLabelsSize.width();
 | |
|     result += tickLabelPadding;
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   // calculate size of axis label (only height needed, because left/right labels are rotated by 90 degrees):
 | |
|   if (!label.isEmpty())
 | |
|   {
 | |
|     QFontMetrics fontMetrics(labelFont);
 | |
|     QRect bounds;
 | |
|     bounds = fontMetrics.boundingRect(0, 0, 0, 0, Qt::TextDontClip | Qt::AlignHCenter | Qt::AlignVCenter, label);
 | |
|     result += bounds.height() + labelPadding;
 | |
|   }
 | |
|   
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Clears the internal label cache. Upon the next \ref draw, all labels will be created new. This
 | |
|   method is called automatically in \ref draw, if any parameters have changed that invalidate the
 | |
|   cached labels, such as font, color, etc.
 | |
| */
 | |
| void QCPAxisPainterPrivate::clearCache()
 | |
| {
 | |
|   mLabelCache.clear();
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns a hash that allows uniquely identifying whether the label parameters have changed such
 | |
|   that the cached labels must be refreshed (\ref clearCache). It is used in \ref draw. If the
 | |
|   return value of this method hasn't changed since the last redraw, the respective label parameters
 | |
|   haven't changed and cached labels may be used.
 | |
| */
 | |
| QByteArray QCPAxisPainterPrivate::generateLabelParameterHash() const
 | |
| {
 | |
|   QByteArray result;
 | |
|   result.append(QByteArray::number(mParentPlot->bufferDevicePixelRatio()));
 | |
|   result.append(QByteArray::number(tickLabelRotation));
 | |
|   result.append(QByteArray::number((int)tickLabelSide));
 | |
|   result.append(QByteArray::number((int)substituteExponent));
 | |
|   result.append(QByteArray::number((int)numberMultiplyCross));
 | |
|   result.append(tickLabelColor.name().toLatin1()+QByteArray::number(tickLabelColor.alpha(), 16));
 | |
|   result.append(tickLabelFont.toString().toLatin1());
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Draws a single tick label with the provided \a painter, utilizing the internal label cache to
 | |
|   significantly speed up drawing of labels that were drawn in previous calls. The tick label is
 | |
|   always bound to an axis, the distance to the axis is controllable via \a distanceToAxis in
 | |
|   pixels. The pixel position in the axis direction is passed in the \a position parameter. Hence
 | |
|   for the bottom axis, \a position would indicate the horizontal pixel position (not coordinate),
 | |
|   at which the label should be drawn.
 | |
|   
 | |
|   In order to later draw the axis label in a place that doesn't overlap with the tick labels, the
 | |
|   largest tick label size is needed. This is acquired by passing a \a tickLabelsSize to the \ref
 | |
|   drawTickLabel calls during the process of drawing all tick labels of one axis. In every call, \a
 | |
|   tickLabelsSize is expanded, if the drawn label exceeds the value \a tickLabelsSize currently
 | |
|   holds.
 | |
|   
 | |
|   The label is drawn with the font and pen that are currently set on the \a painter. To draw
 | |
|   superscripted powers, the font is temporarily made smaller by a fixed factor (see \ref
 | |
|   getTickLabelData).
 | |
| */
 | |
| void QCPAxisPainterPrivate::placeTickLabel(QCPPainter *painter, double position, int distanceToAxis, const QString &text, QSize *tickLabelsSize)
 | |
| {
 | |
|   // warning: if you change anything here, also adapt getMaxTickLabelSize() accordingly!
 | |
|   if (text.isEmpty()) return;
 | |
|   QSize finalSize;
 | |
|   QPointF labelAnchor;
 | |
|   switch (type)
 | |
|   {
 | |
|     case QCPAxis::atLeft:   labelAnchor = QPointF(axisRect.left()-distanceToAxis-offset, position); break;
 | |
|     case QCPAxis::atRight:  labelAnchor = QPointF(axisRect.right()+distanceToAxis+offset, position); break;
 | |
|     case QCPAxis::atTop:    labelAnchor = QPointF(position, axisRect.top()-distanceToAxis-offset); break;
 | |
|     case QCPAxis::atBottom: labelAnchor = QPointF(position, axisRect.bottom()+distanceToAxis+offset); break;
 | |
|   }
 | |
|   if (mParentPlot->plottingHints().testFlag(QCP::phCacheLabels) && !painter->modes().testFlag(QCPPainter::pmNoCaching)) // label caching enabled
 | |
|   {
 | |
|     CachedLabel *cachedLabel = mLabelCache.take(text); // attempt to get label from cache
 | |
|     if (!cachedLabel)  // no cached label existed, create it
 | |
|     {
 | |
|       cachedLabel = new CachedLabel;
 | |
|       TickLabelData labelData = getTickLabelData(painter->font(), text);
 | |
|       cachedLabel->offset = getTickLabelDrawOffset(labelData)+labelData.rotatedTotalBounds.topLeft();
 | |
|       if (!qFuzzyCompare(1.0, mParentPlot->bufferDevicePixelRatio()))
 | |
|       {
 | |
|         cachedLabel->pixmap = QPixmap(labelData.rotatedTotalBounds.size()*mParentPlot->bufferDevicePixelRatio());
 | |
| #ifdef QCP_DEVICEPIXELRATIO_SUPPORTED
 | |
|         cachedLabel->pixmap.setDevicePixelRatio(mParentPlot->devicePixelRatio());
 | |
| #endif
 | |
|       } else
 | |
|         cachedLabel->pixmap = QPixmap(labelData.rotatedTotalBounds.size());
 | |
|       cachedLabel->pixmap = QPixmap(labelData.rotatedTotalBounds.size());
 | |
|       cachedLabel->pixmap.fill(Qt::transparent);
 | |
|       QCPPainter cachePainter(&cachedLabel->pixmap);
 | |
|       cachePainter.setPen(painter->pen());
 | |
|       drawTickLabel(&cachePainter, -labelData.rotatedTotalBounds.topLeft().x(), -labelData.rotatedTotalBounds.topLeft().y(), labelData);
 | |
|     }
 | |
|     // if label would be partly clipped by widget border on sides, don't draw it (only for outside tick labels):
 | |
|     bool labelClippedByBorder = false;
 | |
|     if (tickLabelSide == QCPAxis::lsOutside)
 | |
|     {
 | |
|       if (QCPAxis::orientation(type) == Qt::Horizontal)
 | |
|         labelClippedByBorder = labelAnchor.x()+cachedLabel->offset.x()+cachedLabel->pixmap.width()/mParentPlot->bufferDevicePixelRatio() > viewportRect.right() || labelAnchor.x()+cachedLabel->offset.x() < viewportRect.left();
 | |
|       else
 | |
|         labelClippedByBorder = labelAnchor.y()+cachedLabel->offset.y()+cachedLabel->pixmap.height()/mParentPlot->bufferDevicePixelRatio() > viewportRect.bottom() || labelAnchor.y()+cachedLabel->offset.y() < viewportRect.top();
 | |
|     }
 | |
|     if (!labelClippedByBorder)
 | |
|     {
 | |
|       painter->drawPixmap(labelAnchor+cachedLabel->offset, cachedLabel->pixmap);
 | |
|       finalSize = cachedLabel->pixmap.size()/mParentPlot->bufferDevicePixelRatio();
 | |
|     }
 | |
|     mLabelCache.insert(text, cachedLabel); // return label to cache or insert for the first time if newly created
 | |
|   } else // label caching disabled, draw text directly on surface:
 | |
|   {
 | |
|     TickLabelData labelData = getTickLabelData(painter->font(), text);
 | |
|     QPointF finalPosition = labelAnchor + getTickLabelDrawOffset(labelData);
 | |
|     // if label would be partly clipped by widget border on sides, don't draw it (only for outside tick labels):
 | |
|      bool labelClippedByBorder = false;
 | |
|     if (tickLabelSide == QCPAxis::lsOutside)
 | |
|     {
 | |
|       if (QCPAxis::orientation(type) == Qt::Horizontal)
 | |
|         labelClippedByBorder = finalPosition.x()+(labelData.rotatedTotalBounds.width()+labelData.rotatedTotalBounds.left()) > viewportRect.right() || finalPosition.x()+labelData.rotatedTotalBounds.left() < viewportRect.left();
 | |
|       else
 | |
|         labelClippedByBorder = finalPosition.y()+(labelData.rotatedTotalBounds.height()+labelData.rotatedTotalBounds.top()) > viewportRect.bottom() || finalPosition.y()+labelData.rotatedTotalBounds.top() < viewportRect.top();
 | |
|     }
 | |
|     if (!labelClippedByBorder)
 | |
|     {
 | |
|       drawTickLabel(painter, finalPosition.x(), finalPosition.y(), labelData);
 | |
|       finalSize = labelData.rotatedTotalBounds.size();
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   // expand passed tickLabelsSize if current tick label is larger:
 | |
|   if (finalSize.width() > tickLabelsSize->width())
 | |
|     tickLabelsSize->setWidth(finalSize.width());
 | |
|   if (finalSize.height() > tickLabelsSize->height())
 | |
|     tickLabelsSize->setHeight(finalSize.height());
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   This is a \ref placeTickLabel helper function.
 | |
|   
 | |
|   Draws the tick label specified in \a labelData with \a painter at the pixel positions \a x and \a
 | |
|   y. This function is used by \ref placeTickLabel to create new tick labels for the cache, or to
 | |
|   directly draw the labels on the QCustomPlot surface when label caching is disabled, i.e. when
 | |
|   QCP::phCacheLabels plotting hint is not set.
 | |
| */
 | |
| void QCPAxisPainterPrivate::drawTickLabel(QCPPainter *painter, double x, double y, const TickLabelData &labelData) const
 | |
| {
 | |
|   // backup painter settings that we're about to change:
 | |
|   QTransform oldTransform = painter->transform();
 | |
|   QFont oldFont = painter->font();
 | |
|   
 | |
|   // transform painter to position/rotation:
 | |
|   painter->translate(x, y);
 | |
|   if (!qFuzzyIsNull(tickLabelRotation))
 | |
|     painter->rotate(tickLabelRotation);
 | |
|   
 | |
|   // draw text:
 | |
|   if (!labelData.expPart.isEmpty()) // indicator that beautiful powers must be used
 | |
|   {
 | |
|     painter->setFont(labelData.baseFont);
 | |
|     painter->drawText(0, 0, 0, 0, Qt::TextDontClip, labelData.basePart);
 | |
|     if (!labelData.suffixPart.isEmpty())
 | |
|       painter->drawText(labelData.baseBounds.width()+1+labelData.expBounds.width(), 0, 0, 0, Qt::TextDontClip, labelData.suffixPart);
 | |
|     painter->setFont(labelData.expFont);
 | |
|     painter->drawText(labelData.baseBounds.width()+1, 0, labelData.expBounds.width(), labelData.expBounds.height(), Qt::TextDontClip,  labelData.expPart);
 | |
|   } else
 | |
|   {
 | |
|     painter->setFont(labelData.baseFont);
 | |
|     painter->drawText(0, 0, labelData.totalBounds.width(), labelData.totalBounds.height(), Qt::TextDontClip | Qt::AlignHCenter, labelData.basePart);
 | |
|   }
 | |
|   
 | |
|   // reset painter settings to what it was before:
 | |
|   painter->setTransform(oldTransform);
 | |
|   painter->setFont(oldFont);
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   This is a \ref placeTickLabel helper function.
 | |
|   
 | |
|   Transforms the passed \a text and \a font to a tickLabelData structure that can then be further
 | |
|   processed by \ref getTickLabelDrawOffset and \ref drawTickLabel. It splits the text into base and
 | |
|   exponent if necessary (member substituteExponent) and calculates appropriate bounding boxes.
 | |
| */
 | |
| QCPAxisPainterPrivate::TickLabelData QCPAxisPainterPrivate::getTickLabelData(const QFont &font, const QString &text) const
 | |
| {
 | |
|   TickLabelData result;
 | |
|   
 | |
|   // determine whether beautiful decimal powers should be used
 | |
|   bool useBeautifulPowers = false;
 | |
|   int ePos = -1; // first index of exponent part, text before that will be basePart, text until eLast will be expPart
 | |
|   int eLast = -1; // last index of exponent part, rest of text after this will be suffixPart
 | |
|   if (substituteExponent)
 | |
|   {
 | |
|     ePos = text.indexOf(QLatin1Char('e'));
 | |
|     if (ePos > 0 && text.at(ePos-1).isDigit())
 | |
|     {
 | |
|       eLast = ePos;
 | |
|       while (eLast+1 < text.size() && (text.at(eLast+1) == QLatin1Char('+') || text.at(eLast+1) == QLatin1Char('-') || text.at(eLast+1).isDigit()))
 | |
|         ++eLast;
 | |
|       if (eLast > ePos) // only if also to right of 'e' is a digit/+/- interpret it as beautifiable power
 | |
|         useBeautifulPowers = true;
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   // calculate text bounding rects and do string preparation for beautiful decimal powers:
 | |
|   result.baseFont = font;
 | |
|   if (result.baseFont.pointSizeF() > 0) // might return -1 if specified with setPixelSize, in that case we can't do correction in next line
 | |
|     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
 | |
|   if (useBeautifulPowers)
 | |
|   {
 | |
|     // split text into parts of number/symbol that will be drawn normally and part that will be drawn as exponent:
 | |
|     result.basePart = text.left(ePos);
 | |
|     result.suffixPart = text.mid(eLast+1); // also drawn normally but after exponent
 | |
|     // in log scaling, we want to turn "1*10^n" into "10^n", else add multiplication sign and decimal base:
 | |
|     if (abbreviateDecimalPowers && result.basePart == QLatin1String("1"))
 | |
|       result.basePart = QLatin1String("10");
 | |
|     else
 | |
|       result.basePart += (numberMultiplyCross ? QString(QChar(215)) : QString(QChar(183))) + QLatin1String("10");
 | |
|     result.expPart = text.mid(ePos+1, eLast-ePos);
 | |
|     // clip "+" and leading zeros off expPart:
 | |
|     while (result.expPart.length() > 2 && result.expPart.at(1) == QLatin1Char('0')) // length > 2 so we leave one zero when numberFormatChar is 'e'
 | |
|       result.expPart.remove(1, 1);
 | |
|     if (!result.expPart.isEmpty() && result.expPart.at(0) == QLatin1Char('+'))
 | |
|       result.expPart.remove(0, 1);
 | |
|     // prepare smaller font for exponent:
 | |
|     result.expFont = font;
 | |
|     if (result.expFont.pointSize() > 0)
 | |
|       result.expFont.setPointSize(result.expFont.pointSize()*0.75);
 | |
|     else
 | |
|       result.expFont.setPixelSize(result.expFont.pixelSize()*0.75);
 | |
|     // calculate bounding rects of base part(s), exponent part and total one:
 | |
|     result.baseBounds = QFontMetrics(result.baseFont).boundingRect(0, 0, 0, 0, Qt::TextDontClip, result.basePart);
 | |
|     result.expBounds = QFontMetrics(result.expFont).boundingRect(0, 0, 0, 0, Qt::TextDontClip, result.expPart);
 | |
|     if (!result.suffixPart.isEmpty())
 | |
|       result.suffixBounds = QFontMetrics(result.baseFont).boundingRect(0, 0, 0, 0, Qt::TextDontClip, result.suffixPart);
 | |
|     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
 | |
|   } else // useBeautifulPowers == false
 | |
|   {
 | |
|     result.basePart = text;
 | |
|     result.totalBounds = QFontMetrics(result.baseFont).boundingRect(0, 0, 0, 0, Qt::TextDontClip | Qt::AlignHCenter, result.basePart);
 | |
|   }
 | |
|   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
 | |
|   
 | |
|   // calculate possibly different bounding rect after rotation:
 | |
|   result.rotatedTotalBounds = result.totalBounds;
 | |
|   if (!qFuzzyIsNull(tickLabelRotation))
 | |
|   {
 | |
|     QTransform transform;
 | |
|     transform.rotate(tickLabelRotation);
 | |
|     result.rotatedTotalBounds = transform.mapRect(result.rotatedTotalBounds);
 | |
|   }
 | |
|   
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   This is a \ref placeTickLabel helper function.
 | |
|   
 | |
|   Calculates the offset at which the top left corner of the specified tick label shall be drawn.
 | |
|   The offset is relative to a point right next to the tick the label belongs to.
 | |
|   
 | |
|   This function is thus responsible for e.g. centering tick labels under ticks and positioning them
 | |
|   appropriately when they are rotated.
 | |
| */
 | |
| QPointF QCPAxisPainterPrivate::getTickLabelDrawOffset(const TickLabelData &labelData) const
 | |
| {
 | |
|   /*
 | |
|     calculate label offset from base point at tick (non-trivial, for best visual appearance): short
 | |
|     explanation for bottom axis: The anchor, i.e. the point in the label that is placed
 | |
|     horizontally under the corresponding tick is always on the label side that is closer to the
 | |
|     axis (e.g. the left side of the text when we're rotating clockwise). On that side, the height
 | |
|     is halved and the resulting point is defined the anchor. This way, a 90 degree rotated text
 | |
|     will be centered under the tick (i.e. displaced horizontally by half its height). At the same
 | |
|     time, a 45 degree rotated text will "point toward" its tick, as is typical for rotated tick
 | |
|     labels.
 | |
|   */
 | |
|   bool doRotation = !qFuzzyIsNull(tickLabelRotation);
 | |
|   bool flip = qFuzzyCompare(qAbs(tickLabelRotation), 90.0); // perfect +/-90 degree flip. Indicates vertical label centering on vertical axes.
 | |
|   double radians = tickLabelRotation/180.0*M_PI;
 | |
|   int x=0, y=0;
 | |
|   if ((type == QCPAxis::atLeft && tickLabelSide == QCPAxis::lsOutside) || (type == QCPAxis::atRight && tickLabelSide == QCPAxis::lsInside)) // Anchor at right side of tick label
 | |
|   {
 | |
|     if (doRotation)
 | |
|     {
 | |
|       if (tickLabelRotation > 0)
 | |
|       {
 | |
|         x = -qCos(radians)*labelData.totalBounds.width();
 | |
|         y = flip ? -labelData.totalBounds.width()/2.0 : -qSin(radians)*labelData.totalBounds.width()-qCos(radians)*labelData.totalBounds.height()/2.0;
 | |
|       } else
 | |
|       {
 | |
|         x = -qCos(-radians)*labelData.totalBounds.width()-qSin(-radians)*labelData.totalBounds.height();
 | |
|         y = flip ? +labelData.totalBounds.width()/2.0 : +qSin(-radians)*labelData.totalBounds.width()-qCos(-radians)*labelData.totalBounds.height()/2.0;
 | |
|       }
 | |
|     } else
 | |
|     {
 | |
|       x = -labelData.totalBounds.width();
 | |
|       y = -labelData.totalBounds.height()/2.0;
 | |
|     }
 | |
|   } else if ((type == QCPAxis::atRight && tickLabelSide == QCPAxis::lsOutside) || (type == QCPAxis::atLeft && tickLabelSide == QCPAxis::lsInside)) // Anchor at left side of tick label
 | |
|   {
 | |
|     if (doRotation)
 | |
|     {
 | |
|       if (tickLabelRotation > 0)
 | |
|       {
 | |
|         x = +qSin(radians)*labelData.totalBounds.height();
 | |
|         y = flip ? -labelData.totalBounds.width()/2.0 : -qCos(radians)*labelData.totalBounds.height()/2.0;
 | |
|       } else
 | |
|       {
 | |
|         x = 0;
 | |
|         y = flip ? +labelData.totalBounds.width()/2.0 : -qCos(-radians)*labelData.totalBounds.height()/2.0;
 | |
|       }
 | |
|     } else
 | |
|     {
 | |
|       x = 0;
 | |
|       y = -labelData.totalBounds.height()/2.0;
 | |
|     }
 | |
|   } else if ((type == QCPAxis::atTop && tickLabelSide == QCPAxis::lsOutside) || (type == QCPAxis::atBottom && tickLabelSide == QCPAxis::lsInside)) // Anchor at bottom side of tick label
 | |
|   {
 | |
|     if (doRotation)
 | |
|     {
 | |
|       if (tickLabelRotation > 0)
 | |
|       {
 | |
|         x = -qCos(radians)*labelData.totalBounds.width()+qSin(radians)*labelData.totalBounds.height()/2.0;
 | |
|         y = -qSin(radians)*labelData.totalBounds.width()-qCos(radians)*labelData.totalBounds.height();
 | |
|       } else
 | |
|       {
 | |
|         x = -qSin(-radians)*labelData.totalBounds.height()/2.0;
 | |
|         y = -qCos(-radians)*labelData.totalBounds.height();
 | |
|       }
 | |
|     } else
 | |
|     {
 | |
|       x = -labelData.totalBounds.width()/2.0;
 | |
|       y = -labelData.totalBounds.height();
 | |
|     }
 | |
|   } else if ((type == QCPAxis::atBottom && tickLabelSide == QCPAxis::lsOutside) || (type == QCPAxis::atTop && tickLabelSide == QCPAxis::lsInside)) // Anchor at top side of tick label
 | |
|   {
 | |
|     if (doRotation)
 | |
|     {
 | |
|       if (tickLabelRotation > 0)
 | |
|       {
 | |
|         x = +qSin(radians)*labelData.totalBounds.height()/2.0;
 | |
|         y = 0;
 | |
|       } else
 | |
|       {
 | |
|         x = -qCos(-radians)*labelData.totalBounds.width()-qSin(-radians)*labelData.totalBounds.height()/2.0;
 | |
|         y = +qSin(-radians)*labelData.totalBounds.width();
 | |
|       }
 | |
|     } else
 | |
|     {
 | |
|       x = -labelData.totalBounds.width()/2.0;
 | |
|       y = 0;
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   return QPointF(x, y);
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Simulates the steps done by \ref placeTickLabel by calculating bounding boxes of the text label
 | |
|   to be drawn, depending on number format etc. Since only the largest tick label is wanted for the
 | |
|   margin calculation, the passed \a tickLabelsSize is only expanded, if it's currently set to a
 | |
|   smaller width/height.
 | |
| */
 | |
| void QCPAxisPainterPrivate::getMaxTickLabelSize(const QFont &font, const QString &text,  QSize *tickLabelsSize) const
 | |
| {
 | |
|   // note: this function must return the same tick label sizes as the placeTickLabel function.
 | |
|   QSize finalSize;
 | |
|   if (mParentPlot->plottingHints().testFlag(QCP::phCacheLabels) && mLabelCache.contains(text)) // label caching enabled and have cached label
 | |
|   {
 | |
|     const CachedLabel *cachedLabel = mLabelCache.object(text);
 | |
|     finalSize = cachedLabel->pixmap.size()/mParentPlot->bufferDevicePixelRatio();
 | |
|   } else // label caching disabled or no label with this text cached:
 | |
|   {
 | |
|     TickLabelData labelData = getTickLabelData(font, text);
 | |
|     finalSize = labelData.rotatedTotalBounds.size();
 | |
|   }
 | |
|   
 | |
|   // expand passed tickLabelsSize if current tick label is larger:
 | |
|   if (finalSize.width() > tickLabelsSize->width())
 | |
|     tickLabelsSize->setWidth(finalSize.width());
 | |
|   if (finalSize.height() > tickLabelsSize->height())
 | |
|     tickLabelsSize->setHeight(finalSize.height());
 | |
| }
 | |
| /* end of 'src/axis/axis.cpp' */
 | |
| 
 | |
| 
 | |
| /* including file 'src/scatterstyle.cpp', size 17420                         */
 | |
| /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPScatterStyle
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPScatterStyle
 | |
|   \brief Represents the visual appearance of scatter points
 | |
|   
 | |
|   This class holds information about shape, color and size of scatter points. In plottables like
 | |
|   QCPGraph it is used to store how scatter points shall be drawn. For example, \ref
 | |
|   QCPGraph::setScatterStyle takes a QCPScatterStyle instance.
 | |
|   
 | |
|   A scatter style consists of a shape (\ref setShape), a line color (\ref setPen) and possibly a
 | |
|   fill (\ref setBrush), if the shape provides a fillable area. Further, the size of the shape can
 | |
|   be controlled with \ref setSize.
 | |
| 
 | |
|   \section QCPScatterStyle-defining Specifying a scatter style
 | |
|   
 | |
|   You can set all these configurations either by calling the respective functions on an instance:
 | |
|   \snippet documentation/doc-code-snippets/mainwindow.cpp qcpscatterstyle-creation-1
 | |
|   
 | |
|   Or you can use one of the various constructors that take different parameter combinations, making
 | |
|   it easy to specify a scatter style in a single call, like so:
 | |
|   \snippet documentation/doc-code-snippets/mainwindow.cpp qcpscatterstyle-creation-2
 | |
|   
 | |
|   \section QCPScatterStyle-undefinedpen Leaving the color/pen up to the plottable
 | |
|   
 | |
|   There are two constructors which leave the pen undefined: \ref QCPScatterStyle() and \ref
 | |
|   QCPScatterStyle(ScatterShape shape, double size). If those constructors are used, a call to \ref
 | |
|   isPenDefined will return false. It leads to scatter points that inherit the pen from the
 | |
|   plottable that uses the scatter style. Thus, if such a scatter style is passed to QCPGraph, the line
 | |
|   color of the graph (\ref QCPGraph::setPen) will be used by the scatter points. This makes
 | |
|   it very convenient to set up typical scatter settings:
 | |
|   
 | |
|   \snippet documentation/doc-code-snippets/mainwindow.cpp qcpscatterstyle-shortcreation
 | |
| 
 | |
|   Notice that it wasn't even necessary to explicitly call a QCPScatterStyle constructor. This works
 | |
|   because QCPScatterStyle provides a constructor that can transform a \ref ScatterShape directly
 | |
|   into a QCPScatterStyle instance (that's the \ref QCPScatterStyle(ScatterShape shape, double size)
 | |
|   constructor with a default for \a size). In those cases, C++ allows directly supplying a \ref
 | |
|   ScatterShape, where actually a QCPScatterStyle is expected.
 | |
|   
 | |
|   \section QCPScatterStyle-custompath-and-pixmap Custom shapes and pixmaps
 | |
|   
 | |
|   QCPScatterStyle supports drawing custom shapes and arbitrary pixmaps as scatter points.
 | |
| 
 | |
|   For custom shapes, you can provide a QPainterPath with the desired shape to the \ref
 | |
|   setCustomPath function or call the constructor that takes a painter path. The scatter shape will
 | |
|   automatically be set to \ref ssCustom.
 | |
|   
 | |
|   For pixmaps, you call \ref setPixmap with the desired QPixmap. Alternatively you can use the
 | |
|   constructor that takes a QPixmap. The scatter shape will automatically be set to \ref ssPixmap.
 | |
|   Note that \ref setSize does not influence the appearance of the pixmap.
 | |
| */
 | |
| 
 | |
| /* start documentation of inline functions */
 | |
| 
 | |
| /*! \fn bool QCPScatterStyle::isNone() const
 | |
|   
 | |
|   Returns whether the scatter shape is \ref ssNone.
 | |
|   
 | |
|   \see setShape
 | |
| */
 | |
| 
 | |
| /*! \fn bool QCPScatterStyle::isPenDefined() const
 | |
|   
 | |
|   Returns whether a pen has been defined for this scatter style.
 | |
|   
 | |
|   The pen is undefined if a constructor is called that does not carry \a pen as parameter. Those
 | |
|   are \ref QCPScatterStyle() and \ref QCPScatterStyle(ScatterShape shape, double size). If the pen
 | |
|   is undefined, the pen of the respective plottable will be used for drawing scatters.
 | |
|   
 | |
|   If a pen was defined for this scatter style instance, and you now wish to undefine the pen, call
 | |
|   \ref undefinePen.
 | |
|   
 | |
|   \see setPen
 | |
| */
 | |
| 
 | |
| /* end documentation of inline functions */
 | |
| 
 | |
| /*!
 | |
|   Creates a new QCPScatterStyle instance with size set to 6. No shape, pen or brush is defined.
 | |
|   
 | |
|   Since the pen is undefined (\ref isPenDefined returns false), the scatter color will be inherited
 | |
|   from the plottable that uses this scatter style.
 | |
| */
 | |
| QCPScatterStyle::QCPScatterStyle() :
 | |
|   mSize(6),
 | |
|   mShape(ssNone),
 | |
|   mPen(Qt::NoPen),
 | |
|   mBrush(Qt::NoBrush),
 | |
|   mPenDefined(false)
 | |
| {
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Creates a new QCPScatterStyle instance with shape set to \a shape and size to \a size. No pen or
 | |
|   brush is defined.
 | |
|   
 | |
|   Since the pen is undefined (\ref isPenDefined returns false), the scatter color will be inherited
 | |
|   from the plottable that uses this scatter style.
 | |
| */
 | |
| QCPScatterStyle::QCPScatterStyle(ScatterShape shape, double size) :
 | |
|   mSize(size),
 | |
|   mShape(shape),
 | |
|   mPen(Qt::NoPen),
 | |
|   mBrush(Qt::NoBrush),
 | |
|   mPenDefined(false)
 | |
| {
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Creates a new QCPScatterStyle instance with shape set to \a shape, the pen color set to \a color,
 | |
|   and size to \a size. No brush is defined, i.e. the scatter point will not be filled.
 | |
| */
 | |
| QCPScatterStyle::QCPScatterStyle(ScatterShape shape, const QColor &color, double size) :
 | |
|   mSize(size),
 | |
|   mShape(shape),
 | |
|   mPen(QPen(color)),
 | |
|   mBrush(Qt::NoBrush),
 | |
|   mPenDefined(true)
 | |
| {
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Creates a new QCPScatterStyle instance with shape set to \a shape, the pen color set to \a color,
 | |
|   the brush color to \a fill (with a solid pattern), and size to \a size.
 | |
| */
 | |
| QCPScatterStyle::QCPScatterStyle(ScatterShape shape, const QColor &color, const QColor &fill, double size) :
 | |
|   mSize(size),
 | |
|   mShape(shape),
 | |
|   mPen(QPen(color)),
 | |
|   mBrush(QBrush(fill)),
 | |
|   mPenDefined(true)
 | |
| {
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Creates a new QCPScatterStyle instance with shape set to \a shape, the pen set to \a pen, the
 | |
|   brush to \a brush, and size to \a size.
 | |
|   
 | |
|   \warning In some cases it might be tempting to directly use a pen style like <tt>Qt::NoPen</tt> as \a pen
 | |
|   and a color like <tt>Qt::blue</tt> as \a brush. Notice however, that the corresponding call\n
 | |
|   <tt>QCPScatterStyle(QCPScatterShape::ssCircle, Qt::NoPen, Qt::blue, 5)</tt>\n
 | |
|   doesn't necessarily lead C++ to use this constructor in some cases, but might mistake
 | |
|   <tt>Qt::NoPen</tt> for a QColor and use the
 | |
|   \ref QCPScatterStyle(ScatterShape shape, const QColor &color, const QColor &fill, double size)
 | |
|   constructor instead (which will lead to an unexpected look of the scatter points). To prevent
 | |
|   this, be more explicit with the parameter types. For example, use <tt>QBrush(Qt::blue)</tt>
 | |
|   instead of just <tt>Qt::blue</tt>, to clearly point out to the compiler that this constructor is
 | |
|   wanted.
 | |
| */
 | |
| QCPScatterStyle::QCPScatterStyle(ScatterShape shape, const QPen &pen, const QBrush &brush, double size) :
 | |
|   mSize(size),
 | |
|   mShape(shape),
 | |
|   mPen(pen),
 | |
|   mBrush(brush),
 | |
|   mPenDefined(pen.style() != Qt::NoPen)
 | |
| {
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Creates a new QCPScatterStyle instance which will show the specified \a pixmap. The scatter shape
 | |
|   is set to \ref ssPixmap.
 | |
| */
 | |
| QCPScatterStyle::QCPScatterStyle(const QPixmap &pixmap) :
 | |
|   mSize(5),
 | |
|   mShape(ssPixmap),
 | |
|   mPen(Qt::NoPen),
 | |
|   mBrush(Qt::NoBrush),
 | |
|   mPixmap(pixmap),
 | |
|   mPenDefined(false)
 | |
| {
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Creates a new QCPScatterStyle instance with a custom shape that is defined via \a customPath. The
 | |
|   scatter shape is set to \ref ssCustom.
 | |
|   
 | |
|   The custom shape line will be drawn with \a pen and filled with \a brush. The size has a slightly
 | |
|   different meaning than for built-in scatter points: The custom path will be drawn scaled by a
 | |
|   factor of \a size/6.0. Since the default \a size is 6, the custom path will appear at a its
 | |
|   natural size by default. To double the size of the path for example, set \a size to 12.
 | |
| */
 | |
| QCPScatterStyle::QCPScatterStyle(const QPainterPath &customPath, const QPen &pen, const QBrush &brush, double size) :
 | |
|   mSize(size),
 | |
|   mShape(ssCustom),
 | |
|   mPen(pen),
 | |
|   mBrush(brush),
 | |
|   mCustomPath(customPath),
 | |
|   mPenDefined(pen.style() != Qt::NoPen)
 | |
| {
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Copies the specified \a properties from the \a other scatter style to this scatter style.
 | |
| */
 | |
| void QCPScatterStyle::setFromOther(const QCPScatterStyle &other, ScatterProperties properties)
 | |
| {
 | |
|   if (properties.testFlag(spPen))
 | |
|   {
 | |
|     setPen(other.pen());
 | |
|     if (!other.isPenDefined())
 | |
|       undefinePen();
 | |
|   }
 | |
|   if (properties.testFlag(spBrush))
 | |
|     setBrush(other.brush());
 | |
|   if (properties.testFlag(spSize))
 | |
|     setSize(other.size());
 | |
|   if (properties.testFlag(spShape))
 | |
|   {
 | |
|     setShape(other.shape());
 | |
|     if (other.shape() == ssPixmap)
 | |
|       setPixmap(other.pixmap());
 | |
|     else if (other.shape() == ssCustom)
 | |
|       setCustomPath(other.customPath());
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the size (pixel diameter) of the drawn scatter points to \a size.
 | |
|   
 | |
|   \see setShape
 | |
| */
 | |
| void QCPScatterStyle::setSize(double size)
 | |
| {
 | |
|   mSize = size;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the shape to \a shape.
 | |
|   
 | |
|   Note that the calls \ref setPixmap and \ref setCustomPath automatically set the shape to \ref
 | |
|   ssPixmap and \ref ssCustom, respectively.
 | |
|   
 | |
|   \see setSize
 | |
| */
 | |
| void QCPScatterStyle::setShape(QCPScatterStyle::ScatterShape shape)
 | |
| {
 | |
|   mShape = shape;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the pen that will be used to draw scatter points to \a pen.
 | |
|   
 | |
|   If the pen was previously undefined (see \ref isPenDefined), the pen is considered defined after
 | |
|   a call to this function, even if \a pen is <tt>Qt::NoPen</tt>. If you have defined a pen
 | |
|   previously by calling this function and now wish to undefine the pen, call \ref undefinePen.
 | |
|   
 | |
|   \see setBrush
 | |
| */
 | |
| void QCPScatterStyle::setPen(const QPen &pen)
 | |
| {
 | |
|   mPenDefined = true;
 | |
|   mPen = pen;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the brush that will be used to fill scatter points to \a brush. Note that not all scatter
 | |
|   shapes have fillable areas. For example, \ref ssPlus does not while \ref ssCircle does.
 | |
|   
 | |
|   \see setPen
 | |
| */
 | |
| void QCPScatterStyle::setBrush(const QBrush &brush)
 | |
| {
 | |
|   mBrush = brush;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the pixmap that will be drawn as scatter point to \a pixmap.
 | |
|   
 | |
|   Note that \ref setSize does not influence the appearance of the pixmap.
 | |
|   
 | |
|   The scatter shape is automatically set to \ref ssPixmap.
 | |
| */
 | |
| void QCPScatterStyle::setPixmap(const QPixmap &pixmap)
 | |
| {
 | |
|   setShape(ssPixmap);
 | |
|   mPixmap = pixmap;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the custom shape that will be drawn as scatter point to \a customPath.
 | |
|   
 | |
|   The scatter shape is automatically set to \ref ssCustom.
 | |
| */
 | |
| void QCPScatterStyle::setCustomPath(const QPainterPath &customPath)
 | |
| {
 | |
|   setShape(ssCustom);
 | |
|   mCustomPath = customPath;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets this scatter style to have an undefined pen (see \ref isPenDefined for what an undefined pen
 | |
|   implies).
 | |
| 
 | |
|   A call to \ref setPen will define a pen.
 | |
| */
 | |
| void QCPScatterStyle::undefinePen()
 | |
| {
 | |
|   mPenDefined = false;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Applies the pen and the brush of this scatter style to \a painter. If this scatter style has an
 | |
|   undefined pen (\ref isPenDefined), sets the pen of \a painter to \a defaultPen instead.
 | |
|   
 | |
|   This function is used by plottables (or any class that wants to draw scatters) just before a
 | |
|   number of scatters with this style shall be drawn with the \a painter.
 | |
|   
 | |
|   \see drawShape
 | |
| */
 | |
| void QCPScatterStyle::applyTo(QCPPainter *painter, const QPen &defaultPen) const
 | |
| {
 | |
|   painter->setPen(mPenDefined ? mPen : defaultPen);
 | |
|   painter->setBrush(mBrush);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Draws the scatter shape with \a painter at position \a pos.
 | |
|   
 | |
|   This function does not modify the pen or the brush on the painter, as \ref applyTo is meant to be
 | |
|   called before scatter points are drawn with \ref drawShape.
 | |
|   
 | |
|   \see applyTo
 | |
| */
 | |
| void QCPScatterStyle::drawShape(QCPPainter *painter, const QPointF &pos) const
 | |
| {
 | |
|   drawShape(painter, pos.x(), pos.y());
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   Draws the scatter shape with \a painter at position \a x and \a y.
 | |
| */
 | |
| void QCPScatterStyle::drawShape(QCPPainter *painter, double x, double y) const
 | |
| {
 | |
|   double w = mSize/2.0;
 | |
|   switch (mShape)
 | |
|   {
 | |
|     case ssNone: break;
 | |
|     case ssDot:
 | |
|     {
 | |
|       painter->drawLine(QPointF(x, y), QPointF(x+0.0001, y));
 | |
|       break;
 | |
|     }
 | |
|     case ssCross:
 | |
|     {
 | |
|       painter->drawLine(QLineF(x-w, y-w, x+w, y+w));
 | |
|       painter->drawLine(QLineF(x-w, y+w, x+w, y-w));
 | |
|       break;
 | |
|     }
 | |
|     case ssPlus:
 | |
|     {
 | |
|       painter->drawLine(QLineF(x-w,   y, x+w,   y));
 | |
|       painter->drawLine(QLineF(  x, y+w,   x, y-w));
 | |
|       break;
 | |
|     }
 | |
|     case ssCircle:
 | |
|     {
 | |
|       painter->drawEllipse(QPointF(x , y), w, w);
 | |
|       break;
 | |
|     }
 | |
|     case ssDisc:
 | |
|     {
 | |
|       QBrush b = painter->brush();
 | |
|       painter->setBrush(painter->pen().color());
 | |
|       painter->drawEllipse(QPointF(x , y), w, w);
 | |
|       painter->setBrush(b);
 | |
|       break;
 | |
|     }
 | |
|     case ssSquare:
 | |
|     {
 | |
|       painter->drawRect(QRectF(x-w, y-w, mSize, mSize));
 | |
|       break;
 | |
|     }
 | |
|     case ssDiamond:
 | |
|     {
 | |
|       painter->drawLine(QLineF(x-w,   y,   x, y-w));
 | |
|       painter->drawLine(QLineF(  x, y-w, x+w,   y));
 | |
|       painter->drawLine(QLineF(x+w,   y,   x, y+w));
 | |
|       painter->drawLine(QLineF(  x, y+w, x-w,   y));
 | |
|       break;
 | |
|     }
 | |
|     case ssStar:
 | |
|     {
 | |
|       painter->drawLine(QLineF(x-w,   y, x+w,   y));
 | |
|       painter->drawLine(QLineF(  x, y+w,   x, y-w));
 | |
|       painter->drawLine(QLineF(x-w*0.707, y-w*0.707, x+w*0.707, y+w*0.707));
 | |
|       painter->drawLine(QLineF(x-w*0.707, y+w*0.707, x+w*0.707, y-w*0.707));
 | |
|       break;
 | |
|     }
 | |
|     case ssTriangle:
 | |
|     {
 | |
|        painter->drawLine(QLineF(x-w, y+0.755*w, x+w, y+0.755*w));
 | |
|        painter->drawLine(QLineF(x+w, y+0.755*w,   x, y-0.977*w));
 | |
|        painter->drawLine(QLineF(  x, y-0.977*w, x-w, y+0.755*w));
 | |
|       break;
 | |
|     }
 | |
|     case ssTriangleInverted:
 | |
|     {
 | |
|        painter->drawLine(QLineF(x-w, y-0.755*w, x+w, y-0.755*w));
 | |
|        painter->drawLine(QLineF(x+w, y-0.755*w,   x, y+0.977*w));
 | |
|        painter->drawLine(QLineF(  x, y+0.977*w, x-w, y-0.755*w));
 | |
|       break;
 | |
|     }
 | |
|     case ssCrossSquare:
 | |
|     {
 | |
|        painter->drawLine(QLineF(x-w, y-w, x+w*0.95, y+w*0.95));
 | |
|        painter->drawLine(QLineF(x-w, y+w*0.95, x+w*0.95, y-w));
 | |
|        painter->drawRect(QRectF(x-w, y-w, mSize, mSize));
 | |
|       break;
 | |
|     }
 | |
|     case ssPlusSquare:
 | |
|     {
 | |
|        painter->drawLine(QLineF(x-w,   y, x+w*0.95,   y));
 | |
|        painter->drawLine(QLineF(  x, y+w,        x, y-w));
 | |
|        painter->drawRect(QRectF(x-w, y-w, mSize, mSize));
 | |
|       break;
 | |
|     }
 | |
|     case ssCrossCircle:
 | |
|     {
 | |
|        painter->drawLine(QLineF(x-w*0.707, y-w*0.707, x+w*0.670, y+w*0.670));
 | |
|        painter->drawLine(QLineF(x-w*0.707, y+w*0.670, x+w*0.670, y-w*0.707));
 | |
|        painter->drawEllipse(QPointF(x, y), w, w);
 | |
|       break;
 | |
|     }
 | |
|     case ssPlusCircle:
 | |
|     {
 | |
|        painter->drawLine(QLineF(x-w,   y, x+w,   y));
 | |
|        painter->drawLine(QLineF(  x, y+w,   x, y-w));
 | |
|        painter->drawEllipse(QPointF(x, y), w, w);
 | |
|       break;
 | |
|     }
 | |
|     case ssPeace:
 | |
|     {
 | |
|        painter->drawLine(QLineF(x, y-w,         x,       y+w));
 | |
|        painter->drawLine(QLineF(x,   y, x-w*0.707, y+w*0.707));
 | |
|        painter->drawLine(QLineF(x,   y, x+w*0.707, y+w*0.707));
 | |
|        painter->drawEllipse(QPointF(x, y), w, w);
 | |
|       break;
 | |
|     }
 | |
|     case ssPixmap:
 | |
|     {
 | |
|       const double widthHalf = mPixmap.width()*0.5;
 | |
|       const double heightHalf = mPixmap.height()*0.5;
 | |
| #if QT_VERSION < QT_VERSION_CHECK(4, 8, 0)
 | |
|       const QRectF clipRect = painter->clipRegion().boundingRect().adjusted(-widthHalf, -heightHalf, widthHalf, heightHalf);
 | |
| #else
 | |
|       const QRectF clipRect = painter->clipBoundingRect().adjusted(-widthHalf, -heightHalf, widthHalf, heightHalf);
 | |
| #endif
 | |
|       if (clipRect.contains(x, y))
 | |
|         painter->drawPixmap(x-widthHalf, y-heightHalf, mPixmap);
 | |
|       break;
 | |
|     }
 | |
|     case ssCustom:
 | |
|     {
 | |
|       QTransform oldTransform = painter->transform();
 | |
|       painter->translate(x, y);
 | |
|       painter->scale(mSize/6.0, mSize/6.0);
 | |
|       painter->drawPath(mCustomPath);
 | |
|       painter->setTransform(oldTransform);
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| /* end of 'src/scatterstyle.cpp' */
 | |
| 
 | |
| //amalgamation: add datacontainer.cpp
 | |
| 
 | |
| /* including file 'src/plottable.cpp', size 38861                            */
 | |
| /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPSelectionDecorator
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPSelectionDecorator
 | |
|   \brief Controls how a plottable's data selection is drawn
 | |
|   
 | |
|   Each \ref QCPAbstractPlottable instance has one \ref QCPSelectionDecorator (accessible via \ref
 | |
|   QCPAbstractPlottable::selectionDecorator) and uses it when drawing selected segments of its data.
 | |
|   
 | |
|   The selection decorator controls both pen (\ref setPen) and brush (\ref setBrush), as well as the
 | |
|   scatter style (\ref setScatterStyle) if the plottable draws scatters. Since a \ref
 | |
|   QCPScatterStyle is itself composed of different properties such as color shape and size, the
 | |
|   decorator allows specifying exactly which of those properties shall be used for the selected data
 | |
|   point, via \ref setUsedScatterProperties.
 | |
|   
 | |
|   A \ref QCPSelectionDecorator subclass instance can be passed to a plottable via \ref
 | |
|   QCPAbstractPlottable::setSelectionDecorator, allowing greater customizability of the appearance
 | |
|   of selected segments.
 | |
|   
 | |
|   Use \ref copyFrom to easily transfer the settings of one decorator to another one. This is
 | |
|   especially useful since plottables take ownership of the passed selection decorator, and thus the
 | |
|   same decorator instance can not be passed to multiple plottables.
 | |
|   
 | |
|   Selection decorators can also themselves perform drawing operations by reimplementing \ref
 | |
|   drawDecoration, which is called by the plottable's draw method. The base class \ref
 | |
|   QCPSelectionDecorator does not make use of this however. For example, \ref
 | |
|   QCPSelectionDecoratorBracket draws brackets around selected data segments.
 | |
| */
 | |
| 
 | |
| /*!
 | |
|   Creates a new QCPSelectionDecorator instance with default values
 | |
| */
 | |
| QCPSelectionDecorator::QCPSelectionDecorator() :
 | |
|   mPen(QColor(80, 80, 255), 2.5),
 | |
|   mBrush(Qt::NoBrush),
 | |
|   mScatterStyle(QCPScatterStyle::ssNone, QPen(Qt::blue, 2), Qt::NoBrush, 6.0),
 | |
|   mUsedScatterProperties(QCPScatterStyle::spPen),
 | |
|   mPlottable(0)
 | |
| {
 | |
| }
 | |
| 
 | |
| QCPSelectionDecorator::~QCPSelectionDecorator()
 | |
| {
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the pen that will be used by the parent plottable to draw selected data segments.
 | |
| */
 | |
| void QCPSelectionDecorator::setPen(const QPen &pen)
 | |
| {
 | |
|   mPen = pen;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the brush that will be used by the parent plottable to draw selected data segments.
 | |
| */
 | |
| void QCPSelectionDecorator::setBrush(const QBrush &brush)
 | |
| {
 | |
|   mBrush = brush;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the scatter style that will be used by the parent plottable to draw scatters in selected
 | |
|   data segments.
 | |
|   
 | |
|   \a usedProperties specifies which parts of the passed \a scatterStyle will be used by the
 | |
|   plottable. The used properties can also be changed via \ref setUsedScatterProperties.
 | |
| */
 | |
| void QCPSelectionDecorator::setScatterStyle(const QCPScatterStyle &scatterStyle, QCPScatterStyle::ScatterProperties usedProperties)
 | |
| {
 | |
|   mScatterStyle = scatterStyle;
 | |
|   setUsedScatterProperties(usedProperties);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Use this method to define which properties of the scatter style (set via \ref setScatterStyle)
 | |
|   will be used for selected data segments. All properties of the scatter style that are not
 | |
|   specified in \a properties will remain as specified in the plottable's original scatter style.
 | |
| */
 | |
| void QCPSelectionDecorator::setUsedScatterProperties(const QCPScatterStyle::ScatterProperties &properties)
 | |
| {
 | |
|   mUsedScatterProperties = properties;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the pen of \a painter to the pen of this selection decorator.
 | |
|   
 | |
|   \see applyBrush, getFinalScatterStyle
 | |
| */
 | |
| void QCPSelectionDecorator::applyPen(QCPPainter *painter) const
 | |
| {
 | |
|   painter->setPen(mPen);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the brush of \a painter to the brush of this selection decorator.
 | |
|   
 | |
|   \see applyPen, getFinalScatterStyle
 | |
| */
 | |
| void QCPSelectionDecorator::applyBrush(QCPPainter *painter) const
 | |
| {
 | |
|   painter->setBrush(mBrush);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the scatter style that the parent plottable shall use for selected scatter points. The
 | |
|   plottable's original (unselected) scatter style must be passed as \a unselectedStyle. Depending
 | |
|   on the setting of \ref setUsedScatterProperties, the returned scatter style is a mixture of this
 | |
|   selecion decorator's scatter style (\ref setScatterStyle), and \a unselectedStyle.
 | |
|   
 | |
|   \see applyPen, applyBrush, setScatterStyle
 | |
| */
 | |
| QCPScatterStyle QCPSelectionDecorator::getFinalScatterStyle(const QCPScatterStyle &unselectedStyle) const
 | |
| {
 | |
|   QCPScatterStyle result(unselectedStyle);
 | |
|   result.setFromOther(mScatterStyle, mUsedScatterProperties);
 | |
|   
 | |
|   // if style shall inherit pen from plottable (has no own pen defined), give it the selected
 | |
|   // plottable pen explicitly, so it doesn't use the unselected plottable pen when used in the
 | |
|   // plottable:
 | |
|   if (!result.isPenDefined())
 | |
|     result.setPen(mPen);
 | |
|   
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Copies all properties (e.g. color, fill, scatter style) of the \a other selection decorator to
 | |
|   this selection decorator.
 | |
| */
 | |
| void QCPSelectionDecorator::copyFrom(const QCPSelectionDecorator *other)
 | |
| {
 | |
|   setPen(other->pen());
 | |
|   setBrush(other->brush());
 | |
|   setScatterStyle(other->scatterStyle(), other->usedScatterProperties());
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   This method is called by all plottables' draw methods to allow custom selection decorations to be
 | |
|   drawn. Use the passed \a painter to perform the drawing operations. \a selection carries the data
 | |
|   selection for which the decoration shall be drawn.
 | |
|   
 | |
|   The default base class implementation of \ref QCPSelectionDecorator has no special decoration, so
 | |
|   this method does nothing.
 | |
| */
 | |
| void QCPSelectionDecorator::drawDecoration(QCPPainter *painter, QCPDataSelection selection)
 | |
| {
 | |
|   Q_UNUSED(painter)
 | |
|   Q_UNUSED(selection)
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   This method is called as soon as a selection decorator is associated with a plottable, by a call
 | |
|   to \ref QCPAbstractPlottable::setSelectionDecorator. This way the selection decorator can obtain a pointer to the plottable that uses it (e.g. to access
 | |
|   data points via the \ref QCPAbstractPlottable::interface1D interface).
 | |
|   
 | |
|   If the selection decorator was already added to a different plottable before, this method aborts
 | |
|   the registration and returns false.
 | |
| */
 | |
| bool QCPSelectionDecorator::registerWithPlottable(QCPAbstractPlottable *plottable)
 | |
| {
 | |
|   if (!mPlottable)
 | |
|   {
 | |
|     mPlottable = plottable;
 | |
|     return true;
 | |
|   } else
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "This selection decorator is already registered with plottable:" << reinterpret_cast<quintptr>(mPlottable);
 | |
|     return false;
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPAbstractPlottable
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPAbstractPlottable
 | |
|   \brief The abstract base class for all data representing objects in a plot.
 | |
| 
 | |
|   It defines a very basic interface like name, pen, brush, visibility etc. Since this class is
 | |
|   abstract, it can't be instantiated. Use one of the subclasses or create a subclass yourself to
 | |
|   create new ways of displaying data (see "Creating own plottables" below). Plottables that display
 | |
|   one-dimensional data (i.e. data points have a single key dimension and one or multiple values at
 | |
|   each key) are based off of the template subclass \ref QCPAbstractPlottable1D, see details
 | |
|   there.
 | |
|   
 | |
|   All further specifics are in the subclasses, for example:
 | |
|   \li A normal graph with possibly a line and/or scatter points \ref QCPGraph
 | |
|   (typically created with \ref QCustomPlot::addGraph)
 | |
|   \li A parametric curve: \ref QCPCurve
 | |
|   \li A bar chart: \ref QCPBars
 | |
|   \li A statistical box plot: \ref QCPStatisticalBox
 | |
|   \li A color encoded two-dimensional map: \ref QCPColorMap
 | |
|   \li An OHLC/Candlestick chart: \ref QCPFinancial
 | |
|   
 | |
|   \section plottables-subclassing Creating own plottables
 | |
|   
 | |
|   Subclassing directly from QCPAbstractPlottable is only recommended if you wish to display
 | |
|   two-dimensional data like \ref QCPColorMap, i.e. two logical key dimensions and one (or more)
 | |
|   data dimensions. If you want to display data with only one logical key dimension, you should
 | |
|   rather derive from \ref QCPAbstractPlottable1D.
 | |
|   
 | |
|   If subclassing QCPAbstractPlottable directly, these are the pure virtual functions you must
 | |
|   implement:
 | |
|   \li \ref selectTest
 | |
|   \li \ref draw
 | |
|   \li \ref drawLegendIcon
 | |
|   \li \ref getKeyRange
 | |
|   \li \ref getValueRange
 | |
|   
 | |
|   See the documentation of those functions for what they need to do.
 | |
|   
 | |
|   For drawing your plot, you can use the \ref coordsToPixels functions to translate a point in plot
 | |
|   coordinates to pixel coordinates. This function is quite convenient, because it takes the
 | |
|   orientation of the key and value axes into account for you (x and y are swapped when the key axis
 | |
|   is vertical and the value axis horizontal). If you are worried about performance (i.e. you need
 | |
|   to translate many points in a loop like QCPGraph), you can directly use \ref
 | |
|   QCPAxis::coordToPixel. However, you must then take care about the orientation of the axis
 | |
|   yourself.
 | |
|   
 | |
|   Here are some important members you inherit from QCPAbstractPlottable:
 | |
|   <table>
 | |
|   <tr>
 | |
|     <td>QCustomPlot *\b mParentPlot</td>
 | |
|     <td>A pointer to the parent QCustomPlot instance. The parent plot is inferred from the axes that are passed in the constructor.</td>
 | |
|   </tr><tr>
 | |
|     <td>QString \b mName</td>
 | |
|     <td>The name of the plottable.</td>
 | |
|   </tr><tr>
 | |
|     <td>QPen \b mPen</td>
 | |
|     <td>The generic pen of the plottable. You should use this pen for the most prominent data representing lines in the plottable
 | |
|         (e.g QCPGraph uses this pen for its graph lines and scatters)</td>
 | |
|   </tr><tr>
 | |
|     <td>QBrush \b mBrush</td>
 | |
|     <td>The generic brush of the plottable. You should use this brush for the most prominent fillable structures in the plottable
 | |
|         (e.g. QCPGraph uses this brush to control filling under the graph)</td>
 | |
|   </tr><tr>
 | |
|     <td>QPointer<\ref QCPAxis> \b mKeyAxis, \b mValueAxis</td>
 | |
|     <td>The key and value axes this plottable is attached to. Call their QCPAxis::coordToPixel functions to translate coordinates
 | |
|         to pixels in either the key or value dimension. Make sure to check whether the pointer is null before using it. If one of
 | |
|         the axes is null, don't draw the plottable.</td>
 | |
|   </tr><tr>
 | |
|     <td>\ref QCPSelectionDecorator \b mSelectionDecorator</td>
 | |
|     <td>The currently set selection decorator which specifies how selected data of the plottable shall be drawn and decorated.
 | |
|         When drawing your data, you must consult this decorator for the appropriate pen/brush before drawing unselected/selected data segments.
 | |
|         Finally, you should call its \ref QCPSelectionDecorator::drawDecoration method at the end of your \ref draw implementation.</td>
 | |
|   </tr><tr>
 | |
|     <td>\ref QCP::SelectionType \b mSelectable</td>
 | |
|     <td>In which composition, if at all, this plottable's data may be selected. Enforcing this setting on the data selection is done
 | |
|         by QCPAbstractPlottable automatically.</td>
 | |
|   </tr><tr>
 | |
|     <td>\ref QCPDataSelection \b mSelection</td>
 | |
|     <td>Holds the current selection state of the plottable's data, i.e. the selected data ranges (\ref QCPDataRange).</td>
 | |
|   </tr>
 | |
|   </table>
 | |
| */
 | |
| 
 | |
| /* start of documentation of inline functions */
 | |
| 
 | |
| /*! \fn QCPSelectionDecorator *QCPAbstractPlottable::selectionDecorator() const
 | |
|   
 | |
|   Provides access to the selection decorator of this plottable. The selection decorator controls
 | |
|   how selected data ranges are drawn (e.g. their pen color and fill), see \ref
 | |
|   QCPSelectionDecorator for details.
 | |
|   
 | |
|   If you wish to use an own \ref QCPSelectionDecorator subclass, pass an instance of it to \ref
 | |
|   setSelectionDecorator.
 | |
| */
 | |
| 
 | |
| /*! \fn bool QCPAbstractPlottable::selected() const
 | |
|   
 | |
|   Returns true if there are any data points of the plottable currently selected. Use \ref selection
 | |
|   to retrieve the current \ref QCPDataSelection.
 | |
| */
 | |
| 
 | |
| /*! \fn QCPDataSelection QCPAbstractPlottable::selection() const
 | |
|   
 | |
|   Returns a \ref QCPDataSelection encompassing all the data points that are currently selected on
 | |
|   this plottable.
 | |
|   
 | |
|   \see selected, setSelection, setSelectable
 | |
| */
 | |
| 
 | |
| /*! \fn virtual QCPPlottableInterface1D *QCPAbstractPlottable::interface1D()
 | |
|   
 | |
|   If this plottable is a one-dimensional plottable, i.e. it implements the \ref
 | |
|   QCPPlottableInterface1D, returns the \a this pointer with that type. Otherwise (e.g. in the case
 | |
|   of a \ref QCPColorMap) returns zero.
 | |
|   
 | |
|   You can use this method to gain read access to data coordinates while holding a pointer to the
 | |
|   abstract base class only.
 | |
| */
 | |
| 
 | |
| /* end of documentation of inline functions */
 | |
| /* start of documentation of pure virtual functions */
 | |
| 
 | |
| /*! \fn void QCPAbstractPlottable::drawLegendIcon(QCPPainter *painter, const QRect &rect) const = 0
 | |
|   \internal
 | |
|   
 | |
|   called by QCPLegend::draw (via QCPPlottableLegendItem::draw) to create a graphical representation
 | |
|   of this plottable inside \a rect, next to the plottable name.
 | |
|   
 | |
|   The passed \a painter has its cliprect set to \a rect, so painting outside of \a rect won't
 | |
|   appear outside the legend icon border.
 | |
| */
 | |
| 
 | |
| /*! \fn QCPRange QCPAbstractPlottable::getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain) const = 0
 | |
|   
 | |
|   Returns the coordinate range that all data in this plottable span in the key axis dimension. For
 | |
|   logarithmic plots, one can set \a inSignDomain to either \ref QCP::sdNegative or \ref
 | |
|   QCP::sdPositive in order to restrict the returned range to that sign domain. E.g. when only
 | |
|   negative range is wanted, set \a inSignDomain to \ref QCP::sdNegative and all positive points
 | |
|   will be ignored for range calculation. For no restriction, just set \a inSignDomain to \ref
 | |
|   QCP::sdBoth (default). \a foundRange is an output parameter that indicates whether a range could
 | |
|   be found or not. If this is false, you shouldn't use the returned range (e.g. no points in data).
 | |
| 
 | |
|   Note that \a foundRange is not the same as \ref QCPRange::validRange, since the range returned by
 | |
|   this function may have size zero (e.g. when there is only one data point). In this case \a
 | |
|   foundRange would return true, but the returned range is not a valid range in terms of \ref
 | |
|   QCPRange::validRange.
 | |
|   
 | |
|   \see rescaleAxes, getValueRange
 | |
| */
 | |
| 
 | |
| /*! \fn QCPRange QCPAbstractPlottable::getValueRange(bool &foundRange, QCP::SignDomain inSignDomain, const QCPRange &inKeyRange) const = 0
 | |
|   
 | |
|   Returns the coordinate range that the data points in the specified key range (\a inKeyRange) span
 | |
|   in the value axis dimension. For logarithmic plots, one can set \a inSignDomain to either \ref
 | |
|   QCP::sdNegative or \ref QCP::sdPositive in order to restrict the returned range to that sign
 | |
|   domain. E.g. when only negative range is wanted, set \a inSignDomain to \ref QCP::sdNegative and
 | |
|   all positive points will be ignored for range calculation. For no restriction, just set \a
 | |
|   inSignDomain to \ref QCP::sdBoth (default). \a foundRange is an output parameter that indicates
 | |
|   whether a range could be found or not. If this is false, you shouldn't use the returned range
 | |
|   (e.g. no points in data).
 | |
|   
 | |
|   If \a inKeyRange has both lower and upper bound set to zero (is equal to <tt>QCPRange()</tt>),
 | |
|   all data points are considered, without any restriction on the keys.
 | |
| 
 | |
|   Note that \a foundRange is not the same as \ref QCPRange::validRange, since the range returned by
 | |
|   this function may have size zero (e.g. when there is only one data point). In this case \a
 | |
|   foundRange would return true, but the returned range is not a valid range in terms of \ref
 | |
|   QCPRange::validRange.
 | |
|   
 | |
|   \see rescaleAxes, getKeyRange
 | |
| */
 | |
| 
 | |
| /* end of documentation of pure virtual functions */
 | |
| /* start of documentation of signals */
 | |
| 
 | |
| /*! \fn void QCPAbstractPlottable::selectionChanged(bool selected)
 | |
|   
 | |
|   This signal is emitted when the selection state of this plottable has changed, either by user
 | |
|   interaction or by a direct call to \ref setSelection. The parameter \a selected indicates whether
 | |
|   there are any points selected or not.
 | |
|   
 | |
|   \see selectionChanged(const QCPDataSelection &selection)
 | |
| */
 | |
| 
 | |
| /*! \fn void QCPAbstractPlottable::selectionChanged(const QCPDataSelection &selection)
 | |
|   
 | |
|   This signal is emitted when the selection state of this plottable has changed, either by user
 | |
|   interaction or by a direct call to \ref setSelection. The parameter \a selection holds the
 | |
|   currently selected data ranges.
 | |
|   
 | |
|   \see selectionChanged(bool selected)
 | |
| */
 | |
| 
 | |
| /*! \fn void QCPAbstractPlottable::selectableChanged(QCP::SelectionType selectable);
 | |
|   
 | |
|   This signal is emitted when the selectability of this plottable has changed.
 | |
|   
 | |
|   \see setSelectable
 | |
| */
 | |
| 
 | |
| /* end of documentation of signals */
 | |
| 
 | |
| /*!
 | |
|   Constructs an abstract plottable which uses \a keyAxis as its key axis ("x") and \a valueAxis as
 | |
|   its value axis ("y"). \a keyAxis and \a valueAxis must reside in the same QCustomPlot instance
 | |
|   and have perpendicular orientations. If either of these restrictions is violated, a corresponding
 | |
|   message is printed to the debug output (qDebug), the construction is not aborted, though.
 | |
|   
 | |
|   Since QCPAbstractPlottable is an abstract class that defines the basic interface to plottables,
 | |
|   it can't be directly instantiated.
 | |
|   
 | |
|   You probably want one of the subclasses like \ref QCPGraph or \ref QCPCurve instead.
 | |
| */
 | |
| QCPAbstractPlottable::QCPAbstractPlottable(QCPAxis *keyAxis, QCPAxis *valueAxis) :
 | |
|   QCPLayerable(keyAxis->parentPlot(), QString(), keyAxis->axisRect()),
 | |
|   mName(),
 | |
|   mAntialiasedFill(true),
 | |
|   mAntialiasedScatters(true),
 | |
|   mPen(Qt::black),
 | |
|   mBrush(Qt::NoBrush),
 | |
|   mKeyAxis(keyAxis),
 | |
|   mValueAxis(valueAxis),
 | |
|   mSelectable(QCP::stWhole),
 | |
|   mSelectionDecorator(0)
 | |
| {
 | |
|   if (keyAxis->parentPlot() != valueAxis->parentPlot())
 | |
|     qDebug() << Q_FUNC_INFO << "Parent plot of keyAxis is not the same as that of valueAxis.";
 | |
|   if (keyAxis->orientation() == valueAxis->orientation())
 | |
|     qDebug() << Q_FUNC_INFO << "keyAxis and valueAxis must be orthogonal to each other.";
 | |
|   
 | |
|   mParentPlot->registerPlottable(this);
 | |
|   setSelectionDecorator(new QCPSelectionDecorator);
 | |
| }
 | |
| 
 | |
| QCPAbstractPlottable::~QCPAbstractPlottable()
 | |
| {
 | |
|   if (mSelectionDecorator)
 | |
|   {
 | |
|     delete mSelectionDecorator;
 | |
|     mSelectionDecorator = 0;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|    The name is the textual representation of this plottable as it is displayed in the legend
 | |
|    (\ref QCPLegend). It may contain any UTF-8 characters, including newlines.
 | |
| */
 | |
| void QCPAbstractPlottable::setName(const QString &name)
 | |
| {
 | |
|   mName = name;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets whether fills of this plottable are drawn antialiased or not.
 | |
|   
 | |
|   Note that this setting may be overridden by \ref QCustomPlot::setAntialiasedElements and \ref
 | |
|   QCustomPlot::setNotAntialiasedElements.
 | |
| */
 | |
| void QCPAbstractPlottable::setAntialiasedFill(bool enabled)
 | |
| {
 | |
|   mAntialiasedFill = enabled;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets whether the scatter symbols of this plottable are drawn antialiased or not.
 | |
|   
 | |
|   Note that this setting may be overridden by \ref QCustomPlot::setAntialiasedElements and \ref
 | |
|   QCustomPlot::setNotAntialiasedElements.
 | |
| */
 | |
| void QCPAbstractPlottable::setAntialiasedScatters(bool enabled)
 | |
| {
 | |
|   mAntialiasedScatters = enabled;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   The pen is used to draw basic lines that make up the plottable representation in the
 | |
|   plot.
 | |
|   
 | |
|   For example, the \ref QCPGraph subclass draws its graph lines with this pen.
 | |
| 
 | |
|   \see setBrush
 | |
| */
 | |
| void QCPAbstractPlottable::setPen(const QPen &pen)
 | |
| {
 | |
|   mPen = pen;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   The brush is used to draw basic fills of the plottable representation in the
 | |
|   plot. The Fill can be a color, gradient or texture, see the usage of QBrush.
 | |
|   
 | |
|   For example, the \ref QCPGraph subclass draws the fill under the graph with this brush, when
 | |
|   it's not set to Qt::NoBrush.
 | |
| 
 | |
|   \see setPen
 | |
| */
 | |
| void QCPAbstractPlottable::setBrush(const QBrush &brush)
 | |
| {
 | |
|   mBrush = brush;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   The key axis of a plottable can be set to any axis of a QCustomPlot, as long as it is orthogonal
 | |
|   to the plottable's value axis. This function performs no checks to make sure this is the case.
 | |
|   The typical mathematical choice is to use the x-axis (QCustomPlot::xAxis) as key axis and the
 | |
|   y-axis (QCustomPlot::yAxis) as value axis.
 | |
|   
 | |
|   Normally, the key and value axes are set in the constructor of the plottable (or \ref
 | |
|   QCustomPlot::addGraph when working with QCPGraphs through the dedicated graph interface).
 | |
| 
 | |
|   \see setValueAxis
 | |
| */
 | |
| void QCPAbstractPlottable::setKeyAxis(QCPAxis *axis)
 | |
| {
 | |
|   mKeyAxis = axis;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   The value axis of a plottable can be set to any axis of a QCustomPlot, as long as it is
 | |
|   orthogonal to the plottable's key axis. This function performs no checks to make sure this is the
 | |
|   case. The typical mathematical choice is to use the x-axis (QCustomPlot::xAxis) as key axis and
 | |
|   the y-axis (QCustomPlot::yAxis) as value axis.
 | |
| 
 | |
|   Normally, the key and value axes are set in the constructor of the plottable (or \ref
 | |
|   QCustomPlot::addGraph when working with QCPGraphs through the dedicated graph interface).
 | |
|   
 | |
|   \see setKeyAxis
 | |
| */
 | |
| void QCPAbstractPlottable::setValueAxis(QCPAxis *axis)
 | |
| {
 | |
|   mValueAxis = axis;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*!
 | |
|   Sets which data ranges of this plottable are selected. Selected data ranges are drawn differently
 | |
|   (e.g. color) in the plot. This can be controlled via the selection decorator (see \ref
 | |
|   selectionDecorator).
 | |
|   
 | |
|   The entire selection mechanism for plottables is handled automatically when \ref
 | |
|   QCustomPlot::setInteractions contains iSelectPlottables. You only need to call this function when
 | |
|   you wish to change the selection state programmatically.
 | |
|   
 | |
|   Using \ref setSelectable you can further specify for each plottable whether and to which
 | |
|   granularity it is selectable. If \a selection is not compatible with the current \ref
 | |
|   QCP::SelectionType set via \ref setSelectable, the resulting selection will be adjusted
 | |
|   accordingly (see \ref QCPDataSelection::enforceType).
 | |
|   
 | |
|   emits the \ref selectionChanged signal when \a selected is different from the previous selection state.
 | |
|   
 | |
|   \see setSelectable, selectTest
 | |
| */
 | |
| void QCPAbstractPlottable::setSelection(QCPDataSelection selection)
 | |
| {
 | |
|   selection.enforceType(mSelectable);
 | |
|   if (mSelection != selection)
 | |
|   {
 | |
|     mSelection = selection;
 | |
|     emit selectionChanged(selected());
 | |
|     emit selectionChanged(mSelection);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Use this method to set an own QCPSelectionDecorator (subclass) instance. This allows you to
 | |
|   customize the visual representation of selected data ranges further than by using the default
 | |
|   QCPSelectionDecorator.
 | |
|   
 | |
|   The plottable takes ownership of the \a decorator.
 | |
|   
 | |
|   The currently set decorator can be accessed via \ref selectionDecorator.
 | |
| */
 | |
| void QCPAbstractPlottable::setSelectionDecorator(QCPSelectionDecorator *decorator)
 | |
| {
 | |
|   if (decorator)
 | |
|   {
 | |
|     if (decorator->registerWithPlottable(this))
 | |
|     {
 | |
|       if (mSelectionDecorator) // delete old decorator if necessary
 | |
|         delete mSelectionDecorator;
 | |
|       mSelectionDecorator = decorator;
 | |
|     }
 | |
|   } else if (mSelectionDecorator) // just clear decorator
 | |
|   {
 | |
|     delete mSelectionDecorator;
 | |
|     mSelectionDecorator = 0;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets whether and to which granularity this plottable can be selected.
 | |
| 
 | |
|   A selection can happen by clicking on the QCustomPlot surface (When \ref
 | |
|   QCustomPlot::setInteractions contains \ref QCP::iSelectPlottables), by dragging a selection rect
 | |
|   (When \ref QCustomPlot::setSelectionRectMode is \ref QCP::srmSelect), or programmatically by
 | |
|   calling \ref setSelection.
 | |
|   
 | |
|   \see setSelection, QCP::SelectionType
 | |
| */
 | |
| void QCPAbstractPlottable::setSelectable(QCP::SelectionType selectable)
 | |
| {
 | |
|   if (mSelectable != selectable)
 | |
|   {
 | |
|     mSelectable = selectable;
 | |
|     QCPDataSelection oldSelection = mSelection;
 | |
|     mSelection.enforceType(mSelectable);
 | |
|     emit selectableChanged(mSelectable);
 | |
|     if (mSelection != oldSelection)
 | |
|     {
 | |
|       emit selectionChanged(selected());
 | |
|       emit selectionChanged(mSelection);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| /*!
 | |
|   Convenience function for transforming a key/value pair to pixels on the QCustomPlot surface,
 | |
|   taking the orientations of the axes associated with this plottable into account (e.g. whether key
 | |
|   represents x or y).
 | |
| 
 | |
|   \a key and \a value are transformed to the coodinates in pixels and are written to \a x and \a y.
 | |
| 
 | |
|   \see pixelsToCoords, QCPAxis::coordToPixel
 | |
| */
 | |
| void QCPAbstractPlottable::coordsToPixels(double key, double value, double &x, double &y) const
 | |
| {
 | |
|   QCPAxis *keyAxis = mKeyAxis.data();
 | |
|   QCPAxis *valueAxis = mValueAxis.data();
 | |
|   if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
 | |
|   
 | |
|   if (keyAxis->orientation() == Qt::Horizontal)
 | |
|   {
 | |
|     x = keyAxis->coordToPixel(key);
 | |
|     y = valueAxis->coordToPixel(value);
 | |
|   } else
 | |
|   {
 | |
|     y = keyAxis->coordToPixel(key);
 | |
|     x = valueAxis->coordToPixel(value);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
| 
 | |
|   Transforms the given \a key and \a value to pixel coordinates and returns them in a QPointF.
 | |
| */
 | |
| const QPointF QCPAbstractPlottable::coordsToPixels(double key, double value) const
 | |
| {
 | |
|   QCPAxis *keyAxis = mKeyAxis.data();
 | |
|   QCPAxis *valueAxis = mValueAxis.data();
 | |
|   if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return QPointF(); }
 | |
|   
 | |
|   if (keyAxis->orientation() == Qt::Horizontal)
 | |
|     return QPointF(keyAxis->coordToPixel(key), valueAxis->coordToPixel(value));
 | |
|   else
 | |
|     return QPointF(valueAxis->coordToPixel(value), keyAxis->coordToPixel(key));
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Convenience function for transforming a x/y pixel pair on the QCustomPlot surface to plot coordinates,
 | |
|   taking the orientations of the axes associated with this plottable into account (e.g. whether key
 | |
|   represents x or y).
 | |
| 
 | |
|   \a x and \a y are transformed to the plot coodinates and are written to \a key and \a value.
 | |
| 
 | |
|   \see coordsToPixels, QCPAxis::coordToPixel
 | |
| */
 | |
| void QCPAbstractPlottable::pixelsToCoords(double x, double y, double &key, double &value) const
 | |
| {
 | |
|   QCPAxis *keyAxis = mKeyAxis.data();
 | |
|   QCPAxis *valueAxis = mValueAxis.data();
 | |
|   if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
 | |
|   
 | |
|   if (keyAxis->orientation() == Qt::Horizontal)
 | |
|   {
 | |
|     key = keyAxis->pixelToCoord(x);
 | |
|     value = valueAxis->pixelToCoord(y);
 | |
|   } else
 | |
|   {
 | |
|     key = keyAxis->pixelToCoord(y);
 | |
|     value = valueAxis->pixelToCoord(x);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
| 
 | |
|   Returns the pixel input \a pixelPos as plot coordinates \a key and \a value.
 | |
| */
 | |
| void QCPAbstractPlottable::pixelsToCoords(const QPointF &pixelPos, double &key, double &value) const
 | |
| {
 | |
|   pixelsToCoords(pixelPos.x(), pixelPos.y(), key, value);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Rescales the key and value axes associated with this plottable to contain all displayed data, so
 | |
|   the whole plottable is visible. If the scaling of an axis is logarithmic, rescaleAxes will make
 | |
|   sure not to rescale to an illegal range i.e. a range containing different signs and/or zero.
 | |
|   Instead it will stay in the current sign domain and ignore all parts of the plottable that lie
 | |
|   outside of that domain.
 | |
|   
 | |
|   \a onlyEnlarge makes sure the ranges are only expanded, never reduced. So it's possible to show
 | |
|   multiple plottables in their entirety by multiple calls to rescaleAxes where the first call has
 | |
|   \a onlyEnlarge set to false (the default), and all subsequent set to true.
 | |
|   
 | |
|   \see rescaleKeyAxis, rescaleValueAxis, QCustomPlot::rescaleAxes, QCPAxis::rescale
 | |
| */
 | |
| void QCPAbstractPlottable::rescaleAxes(bool onlyEnlarge) const
 | |
| {
 | |
|   rescaleKeyAxis(onlyEnlarge);
 | |
|   rescaleValueAxis(onlyEnlarge);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Rescales the key axis of the plottable so the whole plottable is visible.
 | |
|   
 | |
|   See \ref rescaleAxes for detailed behaviour.
 | |
| */
 | |
| void QCPAbstractPlottable::rescaleKeyAxis(bool onlyEnlarge) const
 | |
| {
 | |
|   QCPAxis *keyAxis = mKeyAxis.data();
 | |
|   if (!keyAxis) { qDebug() << Q_FUNC_INFO << "invalid key axis"; return; }
 | |
|   
 | |
|   QCP::SignDomain signDomain = QCP::sdBoth;
 | |
|   if (keyAxis->scaleType() == QCPAxis::stLogarithmic)
 | |
|     signDomain = (keyAxis->range().upper < 0 ? QCP::sdNegative : QCP::sdPositive);
 | |
|   
 | |
|   bool foundRange;
 | |
|   QCPRange newRange = getKeyRange(foundRange, signDomain);
 | |
|   if (foundRange)
 | |
|   {
 | |
|     if (onlyEnlarge)
 | |
|       newRange.expand(keyAxis->range());
 | |
|     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
 | |
|     {
 | |
|       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
 | |
|       if (keyAxis->scaleType() == QCPAxis::stLinear)
 | |
|       {
 | |
|         newRange.lower = center-keyAxis->range().size()/2.0;
 | |
|         newRange.upper = center+keyAxis->range().size()/2.0;
 | |
|       } else // scaleType() == stLogarithmic
 | |
|       {
 | |
|         newRange.lower = center/qSqrt(keyAxis->range().upper/keyAxis->range().lower);
 | |
|         newRange.upper = center*qSqrt(keyAxis->range().upper/keyAxis->range().lower);
 | |
|       }
 | |
|     }
 | |
|     keyAxis->setRange(newRange);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Rescales the value axis of the plottable so the whole plottable is visible. If \a inKeyRange is
 | |
|   set to true, only the data points which are in the currently visible key axis range are
 | |
|   considered.
 | |
| 
 | |
|   Returns true if the axis was actually scaled. This might not be the case if this plottable has an
 | |
|   invalid range, e.g. because it has no data points.
 | |
| 
 | |
|   See \ref rescaleAxes for detailed behaviour.
 | |
| */
 | |
| void QCPAbstractPlottable::rescaleValueAxis(bool onlyEnlarge, bool inKeyRange) const
 | |
| {
 | |
|   QCPAxis *keyAxis = mKeyAxis.data();
 | |
|   QCPAxis *valueAxis = mValueAxis.data();
 | |
|   if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
 | |
|   
 | |
|   QCP::SignDomain signDomain = QCP::sdBoth;
 | |
|   if (valueAxis->scaleType() == QCPAxis::stLogarithmic)
 | |
|     signDomain = (valueAxis->range().upper < 0 ? QCP::sdNegative : QCP::sdPositive);
 | |
|   
 | |
|   bool foundRange;
 | |
|   QCPRange newRange = getValueRange(foundRange, signDomain, inKeyRange ? keyAxis->range() : QCPRange());
 | |
|   if (foundRange)
 | |
|   {
 | |
|     if (onlyEnlarge)
 | |
|       newRange.expand(valueAxis->range());
 | |
|     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
 | |
|     {
 | |
|       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
 | |
|       if (valueAxis->scaleType() == QCPAxis::stLinear)
 | |
|       {
 | |
|         newRange.lower = center-valueAxis->range().size()/2.0;
 | |
|         newRange.upper = center+valueAxis->range().size()/2.0;
 | |
|       } else // scaleType() == stLogarithmic
 | |
|       {
 | |
|         newRange.lower = center/qSqrt(valueAxis->range().upper/valueAxis->range().lower);
 | |
|         newRange.upper = center*qSqrt(valueAxis->range().upper/valueAxis->range().lower);
 | |
|       }
 | |
|     }
 | |
|     valueAxis->setRange(newRange);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
| 
 | |
|   Adds this plottable to the specified \a legend.
 | |
| 
 | |
|   Creates a QCPPlottableLegendItem which is inserted into the legend. Returns true on success, i.e.
 | |
|   when the legend exists and a legend item associated with this plottable isn't already in the
 | |
|   legend.
 | |
| 
 | |
|   If the plottable needs a more specialized representation in the legend, you can create a
 | |
|   corresponding subclass of \ref QCPPlottableLegendItem and add it to the legend manually instead
 | |
|   of calling this method.
 | |
| 
 | |
|   \see removeFromLegend, QCPLegend::addItem
 | |
| */
 | |
| bool QCPAbstractPlottable::addToLegend(QCPLegend *legend)
 | |
| {
 | |
|   if (!legend)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "passed legend is null";
 | |
|     return false;
 | |
|   }
 | |
|   if (legend->parentPlot() != mParentPlot)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "passed legend isn't in the same QCustomPlot as this plottable";
 | |
|     return false;
 | |
|   }
 | |
|   
 | |
|   if (!legend->hasItemWithPlottable(this))
 | |
|   {
 | |
|     legend->addItem(new QCPPlottableLegendItem(legend, this));
 | |
|     return true;
 | |
|   } else
 | |
|     return false;
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
| 
 | |
|   Adds this plottable to the legend of the parent QCustomPlot (\ref QCustomPlot::legend).
 | |
| 
 | |
|   \see removeFromLegend
 | |
| */
 | |
| bool QCPAbstractPlottable::addToLegend()
 | |
| {
 | |
|   if (!mParentPlot || !mParentPlot->legend)
 | |
|     return false;
 | |
|   else
 | |
|     return addToLegend(mParentPlot->legend);
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
| 
 | |
|   Removes the plottable from the specifed \a legend. This means the \ref QCPPlottableLegendItem
 | |
|   that is associated with this plottable is removed.
 | |
| 
 | |
|   Returns true on success, i.e. if the legend exists and a legend item associated with this
 | |
|   plottable was found and removed.
 | |
| 
 | |
|   \see addToLegend, QCPLegend::removeItem
 | |
| */
 | |
| bool QCPAbstractPlottable::removeFromLegend(QCPLegend *legend) const
 | |
| {
 | |
|   if (!legend)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "passed legend is null";
 | |
|     return false;
 | |
|   }
 | |
|   
 | |
|   if (QCPPlottableLegendItem *lip = legend->itemWithPlottable(this))
 | |
|     return legend->removeItem(lip);
 | |
|   else
 | |
|     return false;
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
| 
 | |
|   Removes the plottable from the legend of the parent QCustomPlot.
 | |
| 
 | |
|   \see addToLegend
 | |
| */
 | |
| bool QCPAbstractPlottable::removeFromLegend() const
 | |
| {
 | |
|   if (!mParentPlot || !mParentPlot->legend)
 | |
|     return false;
 | |
|   else
 | |
|     return removeFromLegend(mParentPlot->legend);
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| QRect QCPAbstractPlottable::clipRect() const
 | |
| {
 | |
|   if (mKeyAxis && mValueAxis)
 | |
|     return mKeyAxis.data()->axisRect()->rect() & mValueAxis.data()->axisRect()->rect();
 | |
|   else
 | |
|     return QRect();
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| QCP::Interaction QCPAbstractPlottable::selectionCategory() const
 | |
| {
 | |
|   return QCP::iSelectPlottables;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   A convenience function to easily set the QPainter::Antialiased hint on the provided \a painter
 | |
|   before drawing plottable lines.
 | |
| 
 | |
|   This is the antialiasing state the painter passed to the \ref draw method is in by default.
 | |
|   
 | |
|   This function takes into account the local setting of the antialiasing flag as well as the
 | |
|   overrides set with \ref QCustomPlot::setAntialiasedElements and \ref
 | |
|   QCustomPlot::setNotAntialiasedElements.
 | |
|   
 | |
|   \seebaseclassmethod
 | |
|   
 | |
|   \see setAntialiased, applyFillAntialiasingHint, applyScattersAntialiasingHint
 | |
| */
 | |
| void QCPAbstractPlottable::applyDefaultAntialiasingHint(QCPPainter *painter) const
 | |
| {
 | |
|   applyAntialiasingHint(painter, mAntialiased, QCP::aePlottables);
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   A convenience function to easily set the QPainter::Antialiased hint on the provided \a painter
 | |
|   before drawing plottable fills.
 | |
|   
 | |
|   This function takes into account the local setting of the antialiasing flag as well as the
 | |
|   overrides set with \ref QCustomPlot::setAntialiasedElements and \ref
 | |
|   QCustomPlot::setNotAntialiasedElements.
 | |
|   
 | |
|   \see setAntialiased, applyDefaultAntialiasingHint, applyScattersAntialiasingHint
 | |
| */
 | |
| void QCPAbstractPlottable::applyFillAntialiasingHint(QCPPainter *painter) const
 | |
| {
 | |
|   applyAntialiasingHint(painter, mAntialiasedFill, QCP::aeFills);
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   A convenience function to easily set the QPainter::Antialiased hint on the provided \a painter
 | |
|   before drawing plottable scatter points.
 | |
|   
 | |
|   This function takes into account the local setting of the antialiasing flag as well as the
 | |
|   overrides set with \ref QCustomPlot::setAntialiasedElements and \ref
 | |
|   QCustomPlot::setNotAntialiasedElements.
 | |
|   
 | |
|   \see setAntialiased, applyFillAntialiasingHint, applyDefaultAntialiasingHint
 | |
| */
 | |
| void QCPAbstractPlottable::applyScattersAntialiasingHint(QCPPainter *painter) const
 | |
| {
 | |
|   applyAntialiasingHint(painter, mAntialiasedScatters, QCP::aeScatters);
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPAbstractPlottable::selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged)
 | |
| {
 | |
|   Q_UNUSED(event)
 | |
|   
 | |
|   if (mSelectable != QCP::stNone)
 | |
|   {
 | |
|     QCPDataSelection newSelection = details.value<QCPDataSelection>();
 | |
|     QCPDataSelection selectionBefore = mSelection;
 | |
|     if (additive)
 | |
|     {
 | |
|       if (mSelectable == QCP::stWhole) // in whole selection mode, we toggle to no selection even if currently unselected point was hit
 | |
|       {
 | |
|         if (selected())
 | |
|           setSelection(QCPDataSelection());
 | |
|         else
 | |
|           setSelection(newSelection);
 | |
|       } else // in all other selection modes we toggle selections of homogeneously selected/unselected segments
 | |
|       {
 | |
|         if (mSelection.contains(newSelection)) // if entire newSelection is already selected, toggle selection
 | |
|           setSelection(mSelection-newSelection);
 | |
|         else
 | |
|           setSelection(mSelection+newSelection);
 | |
|       }
 | |
|     } else
 | |
|       setSelection(newSelection);
 | |
|     if (selectionStateChanged)
 | |
|       *selectionStateChanged = mSelection != selectionBefore;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPAbstractPlottable::deselectEvent(bool *selectionStateChanged)
 | |
| {
 | |
|   if (mSelectable != QCP::stNone)
 | |
|   {
 | |
|     QCPDataSelection selectionBefore = mSelection;
 | |
|     setSelection(QCPDataSelection());
 | |
|     if (selectionStateChanged)
 | |
|       *selectionStateChanged = mSelection != selectionBefore;
 | |
|   }
 | |
| }
 | |
| /* end of 'src/plottable.cpp' */
 | |
| 
 | |
| 
 | |
| /* including file 'src/item.cpp', size 49269                                 */
 | |
| /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPItemAnchor
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPItemAnchor
 | |
|   \brief An anchor of an item to which positions can be attached to.
 | |
|   
 | |
|   An item (QCPAbstractItem) may have one or more anchors. Unlike QCPItemPosition, an anchor doesn't
 | |
|   control anything on its item, but provides a way to tie other items via their positions to the
 | |
|   anchor.
 | |
| 
 | |
|   For example, a QCPItemRect is defined by its positions \a topLeft and \a bottomRight.
 | |
|   Additionally it has various anchors like \a top, \a topRight or \a bottomLeft etc. So you can
 | |
|   attach the \a start (which is a QCPItemPosition) of a QCPItemLine to one of the anchors by
 | |
|   calling QCPItemPosition::setParentAnchor on \a start, passing the wanted anchor of the
 | |
|   QCPItemRect. This way the start of the line will now always follow the respective anchor location
 | |
|   on the rect item.
 | |
|   
 | |
|   Note that QCPItemPosition derives from QCPItemAnchor, so every position can also serve as an
 | |
|   anchor to other positions.
 | |
|   
 | |
|   To learn how to provide anchors in your own item subclasses, see the subclassing section of the
 | |
|   QCPAbstractItem documentation.
 | |
| */
 | |
| 
 | |
| /* start documentation of inline functions */
 | |
| 
 | |
| /*! \fn virtual QCPItemPosition *QCPItemAnchor::toQCPItemPosition()
 | |
|   
 | |
|   Returns 0 if this instance is merely a QCPItemAnchor, and a valid pointer of type QCPItemPosition* if
 | |
|   it actually is a QCPItemPosition (which is a subclass of QCPItemAnchor).
 | |
|   
 | |
|   This safe downcast functionality could also be achieved with a dynamic_cast. However, QCustomPlot avoids
 | |
|   dynamic_cast to work with projects that don't have RTTI support enabled (e.g. -fno-rtti flag with
 | |
|   gcc compiler).
 | |
| */
 | |
| 
 | |
| /* end documentation of inline functions */
 | |
| 
 | |
| /*!
 | |
|   Creates a new QCPItemAnchor. You shouldn't create QCPItemAnchor instances directly, even if
 | |
|   you want to make a new item subclass. Use \ref QCPAbstractItem::createAnchor instead, as
 | |
|   explained in the subclassing section of the QCPAbstractItem documentation.
 | |
| */
 | |
| QCPItemAnchor::QCPItemAnchor(QCustomPlot *parentPlot, QCPAbstractItem *parentItem, const QString &name, int anchorId) :
 | |
|   mName(name),
 | |
|   mParentPlot(parentPlot),
 | |
|   mParentItem(parentItem),
 | |
|   mAnchorId(anchorId)
 | |
| {
 | |
| }
 | |
| 
 | |
| QCPItemAnchor::~QCPItemAnchor()
 | |
| {
 | |
|   // unregister as parent at children:
 | |
|   foreach (QCPItemPosition *child, mChildrenX.toList())
 | |
|   {
 | |
|     if (child->parentAnchorX() == this)
 | |
|       child->setParentAnchorX(0); // this acts back on this anchor and child removes itself from mChildrenX
 | |
|   }
 | |
|   foreach (QCPItemPosition *child, mChildrenY.toList())
 | |
|   {
 | |
|     if (child->parentAnchorY() == this)
 | |
|       child->setParentAnchorY(0); // this acts back on this anchor and child removes itself from mChildrenY
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the final absolute pixel position of the QCPItemAnchor on the QCustomPlot surface.
 | |
|   
 | |
|   The pixel information is internally retrieved via QCPAbstractItem::anchorPixelPosition of the
 | |
|   parent item, QCPItemAnchor is just an intermediary.
 | |
| */
 | |
| QPointF QCPItemAnchor::pixelPosition() const
 | |
| {
 | |
|   if (mParentItem)
 | |
|   {
 | |
|     if (mAnchorId > -1)
 | |
|     {
 | |
|       return mParentItem->anchorPixelPosition(mAnchorId);
 | |
|     } else
 | |
|     {
 | |
|       qDebug() << Q_FUNC_INFO << "no valid anchor id set:" << mAnchorId;
 | |
|       return QPointF();
 | |
|     }
 | |
|   } else
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "no parent item set";
 | |
|     return QPointF();
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Adds \a pos to the childX list of this anchor, which keeps track of which children use this
 | |
|   anchor as parent anchor for the respective coordinate. This is necessary to notify the children
 | |
|   prior to destruction of the anchor.
 | |
|   
 | |
|   Note that this function does not change the parent setting in \a pos.
 | |
| */
 | |
| void QCPItemAnchor::addChildX(QCPItemPosition *pos)
 | |
| {
 | |
|   if (!mChildrenX.contains(pos))
 | |
|     mChildrenX.insert(pos);
 | |
|   else
 | |
|     qDebug() << Q_FUNC_INFO << "provided pos is child already" << reinterpret_cast<quintptr>(pos);
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Removes \a pos from the childX list of this anchor.
 | |
|   
 | |
|   Note that this function does not change the parent setting in \a pos.
 | |
| */
 | |
| void QCPItemAnchor::removeChildX(QCPItemPosition *pos)
 | |
| {
 | |
|   if (!mChildrenX.remove(pos))
 | |
|     qDebug() << Q_FUNC_INFO << "provided pos isn't child" << reinterpret_cast<quintptr>(pos);
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Adds \a pos to the childY list of this anchor, which keeps track of which children use this
 | |
|   anchor as parent anchor for the respective coordinate. This is necessary to notify the children
 | |
|   prior to destruction of the anchor.
 | |
|   
 | |
|   Note that this function does not change the parent setting in \a pos.
 | |
| */
 | |
| void QCPItemAnchor::addChildY(QCPItemPosition *pos)
 | |
| {
 | |
|   if (!mChildrenY.contains(pos))
 | |
|     mChildrenY.insert(pos);
 | |
|   else
 | |
|     qDebug() << Q_FUNC_INFO << "provided pos is child already" << reinterpret_cast<quintptr>(pos);
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Removes \a pos from the childY list of this anchor.
 | |
|   
 | |
|   Note that this function does not change the parent setting in \a pos.
 | |
| */
 | |
| void QCPItemAnchor::removeChildY(QCPItemPosition *pos)
 | |
| {
 | |
|   if (!mChildrenY.remove(pos))
 | |
|     qDebug() << Q_FUNC_INFO << "provided pos isn't child" << reinterpret_cast<quintptr>(pos);
 | |
| }
 | |
| 
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPItemPosition
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPItemPosition
 | |
|   \brief Manages the position of an item.
 | |
|   
 | |
|   Every item has at least one public QCPItemPosition member pointer which provides ways to position the
 | |
|   item on the QCustomPlot surface. Some items have multiple positions, for example QCPItemRect has two:
 | |
|   \a topLeft and \a bottomRight.
 | |
| 
 | |
|   QCPItemPosition has a type (\ref PositionType) that can be set with \ref setType. This type
 | |
|   defines how coordinates passed to \ref setCoords are to be interpreted, e.g. as absolute pixel
 | |
|   coordinates, as plot coordinates of certain axes, etc. For more advanced plots it is also
 | |
|   possible to assign different types per X/Y coordinate of the position (see \ref setTypeX, \ref
 | |
|   setTypeY). This way an item could be positioned at a fixed pixel distance from the top in the Y
 | |
|   direction, while following a plot coordinate in the X direction.
 | |
| 
 | |
|   A QCPItemPosition may have a parent QCPItemAnchor, see \ref setParentAnchor. This way you can tie
 | |
|   multiple items together. If the QCPItemPosition has a parent, its coordinates (\ref setCoords)
 | |
|   are considered to be absolute pixels in the reference frame of the parent anchor, where (0, 0)
 | |
|   means directly ontop of the parent anchor. For example, You could attach the \a start position of
 | |
|   a QCPItemLine to the \a bottom anchor of a QCPItemText to make the starting point of the line
 | |
|   always be centered under the text label, no matter where the text is moved to. For more advanced
 | |
|   plots, it is possible to assign different parent anchors per X/Y coordinate of the position, see
 | |
|   \ref setParentAnchorX, \ref setParentAnchorY. This way an item could follow another item in the X
 | |
|   direction but stay at a fixed position in the Y direction. Or even follow item A in X, and item B
 | |
|   in Y.
 | |
| 
 | |
|   Note that every QCPItemPosition inherits from QCPItemAnchor and thus can itself be used as parent
 | |
|   anchor for other positions.
 | |
| 
 | |
|   To set the apparent pixel position on the QCustomPlot surface directly, use \ref setPixelPosition. This
 | |
|   works no matter what type this QCPItemPosition is or what parent-child situation it is in, as \ref
 | |
|   setPixelPosition transforms the coordinates appropriately, to make the position appear at the specified
 | |
|   pixel values.
 | |
| */
 | |
| 
 | |
| /* start documentation of inline functions */
 | |
| 
 | |
| /*! \fn QCPItemPosition::PositionType *QCPItemPosition::type() const
 | |
|   
 | |
|   Returns the current position type.
 | |
|   
 | |
|   If different types were set for X and Y (\ref setTypeX, \ref setTypeY), this method returns the
 | |
|   type of the X coordinate. In that case rather use \a typeX() and \a typeY().
 | |
|   
 | |
|   \see setType
 | |
| */
 | |
| 
 | |
| /*! \fn QCPItemAnchor *QCPItemPosition::parentAnchor() const
 | |
|   
 | |
|   Returns the current parent anchor.
 | |
|   
 | |
|   If different parent anchors were set for X and Y (\ref setParentAnchorX, \ref setParentAnchorY),
 | |
|   this method returns the parent anchor of the Y coordinate. In that case rather use \a
 | |
|   parentAnchorX() and \a parentAnchorY().
 | |
|   
 | |
|   \see setParentAnchor
 | |
| */
 | |
| 
 | |
| /* end documentation of inline functions */
 | |
| 
 | |
| /*!
 | |
|   Creates a new QCPItemPosition. You shouldn't create QCPItemPosition instances directly, even if
 | |
|   you want to make a new item subclass. Use \ref QCPAbstractItem::createPosition instead, as
 | |
|   explained in the subclassing section of the QCPAbstractItem documentation.
 | |
| */
 | |
| QCPItemPosition::QCPItemPosition(QCustomPlot *parentPlot, QCPAbstractItem *parentItem, const QString &name) :
 | |
|   QCPItemAnchor(parentPlot, parentItem, name),
 | |
|   mPositionTypeX(ptAbsolute),
 | |
|   mPositionTypeY(ptAbsolute),
 | |
|   mKey(0),
 | |
|   mValue(0),
 | |
|   mParentAnchorX(0),
 | |
|   mParentAnchorY(0)
 | |
| {
 | |
| }
 | |
| 
 | |
| QCPItemPosition::~QCPItemPosition()
 | |
| {
 | |
|   // unregister as parent at children:
 | |
|   // Note: this is done in ~QCPItemAnchor again, but it's important QCPItemPosition does it itself, because only then
 | |
|   //       the setParentAnchor(0) call the correct QCPItemPosition::pixelPosition function instead of QCPItemAnchor::pixelPosition
 | |
|   foreach (QCPItemPosition *child, mChildrenX.toList())
 | |
|   {
 | |
|     if (child->parentAnchorX() == this)
 | |
|       child->setParentAnchorX(0); // this acts back on this anchor and child removes itself from mChildrenX
 | |
|   }
 | |
|   foreach (QCPItemPosition *child, mChildrenY.toList())
 | |
|   {
 | |
|     if (child->parentAnchorY() == this)
 | |
|       child->setParentAnchorY(0); // this acts back on this anchor and child removes itself from mChildrenY
 | |
|   }
 | |
|   // unregister as child in parent:
 | |
|   if (mParentAnchorX)
 | |
|     mParentAnchorX->removeChildX(this);
 | |
|   if (mParentAnchorY)
 | |
|     mParentAnchorY->removeChildY(this);
 | |
| }
 | |
| 
 | |
| /* can't make this a header inline function, because QPointer breaks with forward declared types, see QTBUG-29588 */
 | |
| QCPAxisRect *QCPItemPosition::axisRect() const
 | |
| {
 | |
|   return mAxisRect.data();
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the type of the position. The type defines how the coordinates passed to \ref setCoords
 | |
|   should be handled and how the QCPItemPosition should behave in the plot.
 | |
|   
 | |
|   The possible values for \a type can be separated in two main categories:
 | |
| 
 | |
|   \li The position is regarded as a point in plot coordinates. This corresponds to \ref ptPlotCoords
 | |
|   and requires two axes that define the plot coordinate system. They can be specified with \ref setAxes.
 | |
|   By default, the QCustomPlot's x- and yAxis are used.
 | |
|   
 | |
|   \li The position is fixed on the QCustomPlot surface, i.e. independent of axis ranges. This
 | |
|   corresponds to all other types, i.e. \ref ptAbsolute, \ref ptViewportRatio and \ref
 | |
|   ptAxisRectRatio. They differ only in the way the absolute position is described, see the
 | |
|   documentation of \ref PositionType for details. For \ref ptAxisRectRatio, note that you can specify
 | |
|   the axis rect with \ref setAxisRect. By default this is set to the main axis rect.
 | |
|   
 | |
|   Note that the position type \ref ptPlotCoords is only available (and sensible) when the position
 | |
|   has no parent anchor (\ref setParentAnchor).
 | |
|   
 | |
|   If the type is changed, the apparent pixel position on the plot is preserved. This means
 | |
|   the coordinates as retrieved with coords() and set with \ref setCoords may change in the process.
 | |
|   
 | |
|   This method sets the type for both X and Y directions. It is also possible to set different types
 | |
|   for X and Y, see \ref setTypeX, \ref setTypeY.
 | |
| */
 | |
| void QCPItemPosition::setType(QCPItemPosition::PositionType type)
 | |
| {
 | |
|   setTypeX(type);
 | |
|   setTypeY(type);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   This method sets the position type of the X coordinate to \a type.
 | |
|   
 | |
|   For a detailed description of what a position type is, see the documentation of \ref setType.
 | |
|   
 | |
|   \see setType, setTypeY
 | |
| */
 | |
| void QCPItemPosition::setTypeX(QCPItemPosition::PositionType type)
 | |
| {
 | |
|   if (mPositionTypeX != type)
 | |
|   {
 | |
|     // if switching from or to coordinate type that isn't valid (e.g. because axes or axis rect
 | |
|     // were deleted), don't try to recover the pixelPosition() because it would output a qDebug warning.
 | |
|     bool retainPixelPosition = true;
 | |
|     if ((mPositionTypeX == ptPlotCoords || type == ptPlotCoords) && (!mKeyAxis || !mValueAxis))
 | |
|       retainPixelPosition = false;
 | |
|     if ((mPositionTypeX == ptAxisRectRatio || type == ptAxisRectRatio) && (!mAxisRect))
 | |
|       retainPixelPosition = false;
 | |
|     
 | |
|     QPointF pixel;
 | |
|     if (retainPixelPosition)
 | |
|       pixel = pixelPosition();
 | |
|     
 | |
|     mPositionTypeX = type;
 | |
|     
 | |
|     if (retainPixelPosition)
 | |
|       setPixelPosition(pixel);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   This method sets the position type of the Y coordinate to \a type.
 | |
|   
 | |
|   For a detailed description of what a position type is, see the documentation of \ref setType.
 | |
|   
 | |
|   \see setType, setTypeX
 | |
| */
 | |
| void QCPItemPosition::setTypeY(QCPItemPosition::PositionType type)
 | |
| {
 | |
|   if (mPositionTypeY != type)
 | |
|   {
 | |
|     // if switching from or to coordinate type that isn't valid (e.g. because axes or axis rect
 | |
|     // were deleted), don't try to recover the pixelPosition() because it would output a qDebug warning.
 | |
|     bool retainPixelPosition = true;
 | |
|     if ((mPositionTypeY == ptPlotCoords || type == ptPlotCoords) && (!mKeyAxis || !mValueAxis))
 | |
|       retainPixelPosition = false;
 | |
|     if ((mPositionTypeY == ptAxisRectRatio || type == ptAxisRectRatio) && (!mAxisRect))
 | |
|       retainPixelPosition = false;
 | |
|     
 | |
|     QPointF pixel;
 | |
|     if (retainPixelPosition)
 | |
|       pixel = pixelPosition();
 | |
|     
 | |
|     mPositionTypeY = type;
 | |
|     
 | |
|     if (retainPixelPosition)
 | |
|       setPixelPosition(pixel);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the parent of this QCPItemPosition to \a parentAnchor. This means the position will now
 | |
|   follow any position changes of the anchor. The local coordinate system of positions with a parent
 | |
|   anchor always is absolute pixels, with (0, 0) being exactly on top of the parent anchor. (Hence
 | |
|   the type shouldn't be set to \ref ptPlotCoords for positions with parent anchors.)
 | |
|   
 | |
|   if \a keepPixelPosition is true, the current pixel position of the QCPItemPosition is preserved
 | |
|   during reparenting. If it's set to false, the coordinates are set to (0, 0), i.e. the position
 | |
|   will be exactly on top of the parent anchor.
 | |
|   
 | |
|   To remove this QCPItemPosition from any parent anchor, set \a parentAnchor to 0.
 | |
|   
 | |
|   If the QCPItemPosition previously had no parent and the type is \ref ptPlotCoords, the type is
 | |
|   set to \ref ptAbsolute, to keep the position in a valid state.
 | |
|   
 | |
|   This method sets the parent anchor for both X and Y directions. It is also possible to set
 | |
|   different parents for X and Y, see \ref setParentAnchorX, \ref setParentAnchorY.
 | |
| */
 | |
| bool QCPItemPosition::setParentAnchor(QCPItemAnchor *parentAnchor, bool keepPixelPosition)
 | |
| {
 | |
|   bool successX = setParentAnchorX(parentAnchor, keepPixelPosition);
 | |
|   bool successY = setParentAnchorY(parentAnchor, keepPixelPosition);
 | |
|   return successX && successY;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   This method sets the parent anchor of the X coordinate to \a parentAnchor.
 | |
|   
 | |
|   For a detailed description of what a parent anchor is, see the documentation of \ref setParentAnchor.
 | |
|   
 | |
|   \see setParentAnchor, setParentAnchorY
 | |
| */
 | |
| bool QCPItemPosition::setParentAnchorX(QCPItemAnchor *parentAnchor, bool keepPixelPosition)
 | |
| {
 | |
|   // make sure self is not assigned as parent:
 | |
|   if (parentAnchor == this)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "can't set self as parent anchor" << reinterpret_cast<quintptr>(parentAnchor);
 | |
|     return false;
 | |
|   }
 | |
|   // make sure no recursive parent-child-relationships are created:
 | |
|   QCPItemAnchor *currentParent = parentAnchor;
 | |
|   while (currentParent)
 | |
|   {
 | |
|     if (QCPItemPosition *currentParentPos = currentParent->toQCPItemPosition())
 | |
|     {
 | |
|       // is a QCPItemPosition, might have further parent, so keep iterating
 | |
|       if (currentParentPos == this)
 | |
|       {
 | |
|         qDebug() << Q_FUNC_INFO << "can't create recursive parent-child-relationship" << reinterpret_cast<quintptr>(parentAnchor);
 | |
|         return false;
 | |
|       }
 | |
|       currentParent = currentParentPos->parentAnchorX();
 | |
|     } else
 | |
|     {
 | |
|       // is a QCPItemAnchor, can't have further parent. Now make sure the parent items aren't the
 | |
|       // same, to prevent a position being child of an anchor which itself depends on the position,
 | |
|       // because they're both on the same item:
 | |
|       if (currentParent->mParentItem == mParentItem)
 | |
|       {
 | |
|         qDebug() << Q_FUNC_INFO << "can't set parent to be an anchor which itself depends on this position" << reinterpret_cast<quintptr>(parentAnchor);
 | |
|         return false;
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   // if previously no parent set and PosType is still ptPlotCoords, set to ptAbsolute:
 | |
|   if (!mParentAnchorX && mPositionTypeX == ptPlotCoords)
 | |
|     setTypeX(ptAbsolute);
 | |
|   
 | |
|   // save pixel position:
 | |
|   QPointF pixelP;
 | |
|   if (keepPixelPosition)
 | |
|     pixelP = pixelPosition();
 | |
|   // unregister at current parent anchor:
 | |
|   if (mParentAnchorX)
 | |
|     mParentAnchorX->removeChildX(this);
 | |
|   // register at new parent anchor:
 | |
|   if (parentAnchor)
 | |
|     parentAnchor->addChildX(this);
 | |
|   mParentAnchorX = parentAnchor;
 | |
|   // restore pixel position under new parent:
 | |
|   if (keepPixelPosition)
 | |
|     setPixelPosition(pixelP);
 | |
|   else
 | |
|     setCoords(0, coords().y());
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   This method sets the parent anchor of the Y coordinate to \a parentAnchor.
 | |
|   
 | |
|   For a detailed description of what a parent anchor is, see the documentation of \ref setParentAnchor.
 | |
|   
 | |
|   \see setParentAnchor, setParentAnchorX
 | |
| */
 | |
| bool QCPItemPosition::setParentAnchorY(QCPItemAnchor *parentAnchor, bool keepPixelPosition)
 | |
| {
 | |
|   // make sure self is not assigned as parent:
 | |
|   if (parentAnchor == this)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "can't set self as parent anchor" << reinterpret_cast<quintptr>(parentAnchor);
 | |
|     return false;
 | |
|   }
 | |
|   // make sure no recursive parent-child-relationships are created:
 | |
|   QCPItemAnchor *currentParent = parentAnchor;
 | |
|   while (currentParent)
 | |
|   {
 | |
|     if (QCPItemPosition *currentParentPos = currentParent->toQCPItemPosition())
 | |
|     {
 | |
|       // is a QCPItemPosition, might have further parent, so keep iterating
 | |
|       if (currentParentPos == this)
 | |
|       {
 | |
|         qDebug() << Q_FUNC_INFO << "can't create recursive parent-child-relationship" << reinterpret_cast<quintptr>(parentAnchor);
 | |
|         return false;
 | |
|       }
 | |
|       currentParent = currentParentPos->parentAnchorY();
 | |
|     } else
 | |
|     {
 | |
|       // is a QCPItemAnchor, can't have further parent. Now make sure the parent items aren't the
 | |
|       // same, to prevent a position being child of an anchor which itself depends on the position,
 | |
|       // because they're both on the same item:
 | |
|       if (currentParent->mParentItem == mParentItem)
 | |
|       {
 | |
|         qDebug() << Q_FUNC_INFO << "can't set parent to be an anchor which itself depends on this position" << reinterpret_cast<quintptr>(parentAnchor);
 | |
|         return false;
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   // if previously no parent set and PosType is still ptPlotCoords, set to ptAbsolute:
 | |
|   if (!mParentAnchorY && mPositionTypeY == ptPlotCoords)
 | |
|     setTypeY(ptAbsolute);
 | |
|   
 | |
|   // save pixel position:
 | |
|   QPointF pixelP;
 | |
|   if (keepPixelPosition)
 | |
|     pixelP = pixelPosition();
 | |
|   // unregister at current parent anchor:
 | |
|   if (mParentAnchorY)
 | |
|     mParentAnchorY->removeChildY(this);
 | |
|   // register at new parent anchor:
 | |
|   if (parentAnchor)
 | |
|     parentAnchor->addChildY(this);
 | |
|   mParentAnchorY = parentAnchor;
 | |
|   // restore pixel position under new parent:
 | |
|   if (keepPixelPosition)
 | |
|     setPixelPosition(pixelP);
 | |
|   else
 | |
|     setCoords(coords().x(), 0);
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the coordinates of this QCPItemPosition. What the coordinates mean, is defined by the type
 | |
|   (\ref setType, \ref setTypeX, \ref setTypeY).
 | |
|   
 | |
|   For example, if the type is \ref ptAbsolute, \a key and \a value mean the x and y pixel position
 | |
|   on the QCustomPlot surface. In that case the origin (0, 0) is in the top left corner of the
 | |
|   QCustomPlot viewport. If the type is \ref ptPlotCoords, \a key and \a value mean a point in the
 | |
|   plot coordinate system defined by the axes set by \ref setAxes. By default those are the
 | |
|   QCustomPlot's xAxis and yAxis. See the documentation of \ref setType for other available
 | |
|   coordinate types and their meaning.
 | |
|   
 | |
|   If different types were configured for X and Y (\ref setTypeX, \ref setTypeY), \a key and \a
 | |
|   value must also be provided in the different coordinate systems. Here, the X type refers to \a
 | |
|   key, and the Y type refers to \a value.
 | |
| 
 | |
|   \see setPixelPosition
 | |
| */
 | |
| void QCPItemPosition::setCoords(double key, double value)
 | |
| {
 | |
|   mKey = key;
 | |
|   mValue = value;
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
| 
 | |
|   Sets the coordinates as a QPointF \a pos where pos.x has the meaning of \a key and pos.y the
 | |
|   meaning of \a value of the \ref setCoords(double key, double value) method.
 | |
| */
 | |
| void QCPItemPosition::setCoords(const QPointF &pos)
 | |
| {
 | |
|   setCoords(pos.x(), pos.y());
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the final absolute pixel position of the QCPItemPosition on the QCustomPlot surface. It
 | |
|   includes all effects of type (\ref setType) and possible parent anchors (\ref setParentAnchor).
 | |
| 
 | |
|   \see setPixelPosition
 | |
| */
 | |
| QPointF QCPItemPosition::pixelPosition() const
 | |
| {
 | |
|   QPointF result;
 | |
|   
 | |
|   // determine X:
 | |
|   switch (mPositionTypeX)
 | |
|   {
 | |
|     case ptAbsolute:
 | |
|     {
 | |
|       result.rx() = mKey;
 | |
|       if (mParentAnchorX)
 | |
|         result.rx() += mParentAnchorX->pixelPosition().x();
 | |
|       break;
 | |
|     }
 | |
|     case ptViewportRatio:
 | |
|     {
 | |
|       result.rx() = mKey*mParentPlot->viewport().width();
 | |
|       if (mParentAnchorX)
 | |
|         result.rx() += mParentAnchorX->pixelPosition().x();
 | |
|       else
 | |
|         result.rx() += mParentPlot->viewport().left();
 | |
|       break;
 | |
|     }
 | |
|     case ptAxisRectRatio:
 | |
|     {
 | |
|       if (mAxisRect)
 | |
|       {
 | |
|         result.rx() = mKey*mAxisRect.data()->width();
 | |
|         if (mParentAnchorX)
 | |
|           result.rx() += mParentAnchorX->pixelPosition().x();
 | |
|         else
 | |
|           result.rx() += mAxisRect.data()->left();
 | |
|       } else
 | |
|         qDebug() << Q_FUNC_INFO << "Item position type x is ptAxisRectRatio, but no axis rect was defined";
 | |
|       break;
 | |
|     }
 | |
|     case ptPlotCoords:
 | |
|     {
 | |
|       if (mKeyAxis && mKeyAxis.data()->orientation() == Qt::Horizontal)
 | |
|         result.rx() = mKeyAxis.data()->coordToPixel(mKey);
 | |
|       else if (mValueAxis && mValueAxis.data()->orientation() == Qt::Horizontal)
 | |
|         result.rx() = mValueAxis.data()->coordToPixel(mValue);
 | |
|       else
 | |
|         qDebug() << Q_FUNC_INFO << "Item position type x is ptPlotCoords, but no axes were defined";
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   // determine Y:
 | |
|   switch (mPositionTypeY)
 | |
|   {
 | |
|     case ptAbsolute:
 | |
|     {
 | |
|       result.ry() = mValue;
 | |
|       if (mParentAnchorY)
 | |
|         result.ry() += mParentAnchorY->pixelPosition().y();
 | |
|       break;
 | |
|     }
 | |
|     case ptViewportRatio:
 | |
|     {
 | |
|       result.ry() = mValue*mParentPlot->viewport().height();
 | |
|       if (mParentAnchorY)
 | |
|         result.ry() += mParentAnchorY->pixelPosition().y();
 | |
|       else
 | |
|         result.ry() += mParentPlot->viewport().top();
 | |
|       break;
 | |
|     }
 | |
|     case ptAxisRectRatio:
 | |
|     {
 | |
|       if (mAxisRect)
 | |
|       {
 | |
|         result.ry() = mValue*mAxisRect.data()->height();
 | |
|         if (mParentAnchorY)
 | |
|           result.ry() += mParentAnchorY->pixelPosition().y();
 | |
|         else
 | |
|           result.ry() += mAxisRect.data()->top();
 | |
|       } else
 | |
|         qDebug() << Q_FUNC_INFO << "Item position type y is ptAxisRectRatio, but no axis rect was defined";
 | |
|       break;
 | |
|     }
 | |
|     case ptPlotCoords:
 | |
|     {
 | |
|       if (mKeyAxis && mKeyAxis.data()->orientation() == Qt::Vertical)
 | |
|         result.ry() = mKeyAxis.data()->coordToPixel(mKey);
 | |
|       else if (mValueAxis && mValueAxis.data()->orientation() == Qt::Vertical)
 | |
|         result.ry() = mValueAxis.data()->coordToPixel(mValue);
 | |
|       else
 | |
|         qDebug() << Q_FUNC_INFO << "Item position type y is ptPlotCoords, but no axes were defined";
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   When \ref setType is \ref ptPlotCoords, this function may be used to specify the axes the
 | |
|   coordinates set with \ref setCoords relate to. By default they are set to the initial xAxis and
 | |
|   yAxis of the QCustomPlot.
 | |
| */
 | |
| void QCPItemPosition::setAxes(QCPAxis *keyAxis, QCPAxis *valueAxis)
 | |
| {
 | |
|   mKeyAxis = keyAxis;
 | |
|   mValueAxis = valueAxis;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   When \ref setType is \ref ptAxisRectRatio, this function may be used to specify the axis rect the
 | |
|   coordinates set with \ref setCoords relate to. By default this is set to the main axis rect of
 | |
|   the QCustomPlot.
 | |
| */
 | |
| void QCPItemPosition::setAxisRect(QCPAxisRect *axisRect)
 | |
| {
 | |
|   mAxisRect = axisRect;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the apparent pixel position. This works no matter what type (\ref setType) this
 | |
|   QCPItemPosition is or what parent-child situation it is in, as coordinates are transformed
 | |
|   appropriately, to make the position finally appear at the specified pixel values.
 | |
| 
 | |
|   Only if the type is \ref ptAbsolute and no parent anchor is set, this function's effect is
 | |
|   identical to that of \ref setCoords.
 | |
| 
 | |
|   \see pixelPosition, setCoords
 | |
| */
 | |
| void QCPItemPosition::setPixelPosition(const QPointF &pixelPosition)
 | |
| {
 | |
|   double x = pixelPosition.x();
 | |
|   double y = pixelPosition.y();
 | |
|   
 | |
|   switch (mPositionTypeX)
 | |
|   {
 | |
|     case ptAbsolute:
 | |
|     {
 | |
|       if (mParentAnchorX)
 | |
|         x -= mParentAnchorX->pixelPosition().x();
 | |
|       break;
 | |
|     }
 | |
|     case ptViewportRatio:
 | |
|     {
 | |
|       if (mParentAnchorX)
 | |
|         x -= mParentAnchorX->pixelPosition().x();
 | |
|       else
 | |
|         x -= mParentPlot->viewport().left();
 | |
|       x /= (double)mParentPlot->viewport().width();
 | |
|       break;
 | |
|     }
 | |
|     case ptAxisRectRatio:
 | |
|     {
 | |
|       if (mAxisRect)
 | |
|       {
 | |
|         if (mParentAnchorX)
 | |
|           x -= mParentAnchorX->pixelPosition().x();
 | |
|         else
 | |
|           x -= mAxisRect.data()->left();
 | |
|         x /= (double)mAxisRect.data()->width();
 | |
|       } else
 | |
|         qDebug() << Q_FUNC_INFO << "Item position type x is ptAxisRectRatio, but no axis rect was defined";
 | |
|       break;
 | |
|     }
 | |
|     case ptPlotCoords:
 | |
|     {
 | |
|       if (mKeyAxis && mKeyAxis.data()->orientation() == Qt::Horizontal)
 | |
|         x = mKeyAxis.data()->pixelToCoord(x);
 | |
|       else if (mValueAxis && mValueAxis.data()->orientation() == Qt::Horizontal)
 | |
|         y = mValueAxis.data()->pixelToCoord(x);
 | |
|       else
 | |
|         qDebug() << Q_FUNC_INFO << "Item position type x is ptPlotCoords, but no axes were defined";
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   switch (mPositionTypeY)
 | |
|   {
 | |
|     case ptAbsolute:
 | |
|     {
 | |
|       if (mParentAnchorY)
 | |
|         y -= mParentAnchorY->pixelPosition().y();
 | |
|       break;
 | |
|     }
 | |
|     case ptViewportRatio:
 | |
|     {
 | |
|       if (mParentAnchorY)
 | |
|         y -= mParentAnchorY->pixelPosition().y();
 | |
|       else
 | |
|         y -= mParentPlot->viewport().top();
 | |
|       y /= (double)mParentPlot->viewport().height();
 | |
|       break;
 | |
|     }
 | |
|     case ptAxisRectRatio:
 | |
|     {
 | |
|       if (mAxisRect)
 | |
|       {
 | |
|         if (mParentAnchorY)
 | |
|           y -= mParentAnchorY->pixelPosition().y();
 | |
|         else
 | |
|           y -= mAxisRect.data()->top();
 | |
|         y /= (double)mAxisRect.data()->height();
 | |
|       } else
 | |
|         qDebug() << Q_FUNC_INFO << "Item position type y is ptAxisRectRatio, but no axis rect was defined";
 | |
|       break;
 | |
|     }
 | |
|     case ptPlotCoords:
 | |
|     {
 | |
|       if (mKeyAxis && mKeyAxis.data()->orientation() == Qt::Vertical)
 | |
|         x = mKeyAxis.data()->pixelToCoord(y);
 | |
|       else if (mValueAxis && mValueAxis.data()->orientation() == Qt::Vertical)
 | |
|         y = mValueAxis.data()->pixelToCoord(y);
 | |
|       else
 | |
|         qDebug() << Q_FUNC_INFO << "Item position type y is ptPlotCoords, but no axes were defined";
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   setCoords(x, y);
 | |
| }
 | |
| 
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPAbstractItem
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPAbstractItem
 | |
|   \brief The abstract base class for all items in a plot.
 | |
|   
 | |
|   In QCustomPlot, items are supplemental graphical elements that are neither plottables
 | |
|   (QCPAbstractPlottable) nor axes (QCPAxis). While plottables are always tied to two axes and thus
 | |
|   plot coordinates, items can also be placed in absolute coordinates independent of any axes. Each
 | |
|   specific item has at least one QCPItemPosition member which controls the positioning. Some items
 | |
|   are defined by more than one coordinate and thus have two or more QCPItemPosition members (For
 | |
|   example, QCPItemRect has \a topLeft and \a bottomRight).
 | |
|   
 | |
|   This abstract base class defines a very basic interface like visibility and clipping. Since this
 | |
|   class is abstract, it can't be instantiated. Use one of the subclasses or create a subclass
 | |
|   yourself to create new items.
 | |
|   
 | |
|   The built-in items are:
 | |
|   <table>
 | |
|   <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>
 | |
|   <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>
 | |
|   <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>
 | |
|   <tr><td>QCPItemRect</td><td>A rectangle</td></tr>
 | |
|   <tr><td>QCPItemEllipse</td><td>An ellipse</td></tr>
 | |
|   <tr><td>QCPItemPixmap</td><td>An arbitrary pixmap</td></tr>
 | |
|   <tr><td>QCPItemText</td><td>A text label</td></tr>
 | |
|   <tr><td>QCPItemBracket</td><td>A bracket which may be used to reference/highlight certain parts in the plot.</td></tr>
 | |
|   <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>
 | |
|   </table>
 | |
|   
 | |
|   \section items-clipping Clipping
 | |
| 
 | |
|   Items are by default clipped to the main axis rect (they are only visible inside the axis rect).
 | |
|   To make an item visible outside that axis rect, disable clipping via \ref setClipToAxisRect
 | |
|   "setClipToAxisRect(false)".
 | |
| 
 | |
|   On the other hand if you want the item to be clipped to a different axis rect, specify it via
 | |
|   \ref setClipAxisRect. This clipAxisRect property of an item is only used for clipping behaviour, and
 | |
|   in principle is independent of the coordinate axes the item might be tied to via its position
 | |
|   members (\ref QCPItemPosition::setAxes). However, it is common that the axis rect for clipping
 | |
|   also contains the axes used for the item positions.
 | |
|   
 | |
|   \section items-using Using items
 | |
|   
 | |
|   First you instantiate the item you want to use and add it to the plot:
 | |
|   \snippet documentation/doc-code-snippets/mainwindow.cpp qcpitemline-creation-1
 | |
|   by default, the positions of the item are bound to the x- and y-Axis of the plot. So we can just
 | |
|   set the plot coordinates where the line should start/end:
 | |
|   \snippet documentation/doc-code-snippets/mainwindow.cpp qcpitemline-creation-2
 | |
|   If we don't want the line to be positioned in plot coordinates but a different coordinate system,
 | |
|   e.g. absolute pixel positions on the QCustomPlot surface, we need to change the position type like this:
 | |
|   \snippet documentation/doc-code-snippets/mainwindow.cpp qcpitemline-creation-3
 | |
|   Then we can set the coordinates, this time in pixels:
 | |
|   \snippet documentation/doc-code-snippets/mainwindow.cpp qcpitemline-creation-4
 | |
|   and make the line visible on the entire QCustomPlot, by disabling clipping to the axis rect:
 | |
|   \snippet documentation/doc-code-snippets/mainwindow.cpp qcpitemline-creation-5
 | |
|   
 | |
|   For more advanced plots, it is even possible to set different types and parent anchors per X/Y
 | |
|   coordinate of an item position, using for example \ref QCPItemPosition::setTypeX or \ref
 | |
|   QCPItemPosition::setParentAnchorX. For details, see the documentation of \ref QCPItemPosition.
 | |
|   
 | |
|   \section items-subclassing Creating own items
 | |
|   
 | |
|   To create an own item, you implement a subclass of QCPAbstractItem. These are the pure
 | |
|   virtual functions, you must implement:
 | |
|   \li \ref selectTest
 | |
|   \li \ref draw
 | |
|   
 | |
|   See the documentation of those functions for what they need to do.
 | |
|   
 | |
|   \subsection items-positioning Allowing the item to be positioned
 | |
|   
 | |
|   As mentioned, item positions are represented by QCPItemPosition members. Let's assume the new item shall
 | |
|   have only one point as its position (as opposed to two like a rect or multiple like a polygon). You then add
 | |
|   a public member of type QCPItemPosition like so:
 | |
|   
 | |
|   \code QCPItemPosition * const myPosition;\endcode
 | |
|   
 | |
|   the const makes sure the pointer itself can't be modified from the user of your new item (the QCPItemPosition
 | |
|   instance it points to, can be modified, of course).
 | |
|   The initialization of this pointer is made easy with the \ref createPosition function. Just assign
 | |
|   the return value of this function to each QCPItemPosition in the constructor of your item. \ref createPosition
 | |
|   takes a string which is the name of the position, typically this is identical to the variable name.
 | |
|   For example, the constructor of QCPItemExample could look like this:
 | |
|   
 | |
|   \code
 | |
|   QCPItemExample::QCPItemExample(QCustomPlot *parentPlot) :
 | |
|     QCPAbstractItem(parentPlot),
 | |
|     myPosition(createPosition("myPosition"))
 | |
|   {
 | |
|     // other constructor code
 | |
|   }
 | |
|   \endcode
 | |
|   
 | |
|   \subsection items-drawing The draw function
 | |
|   
 | |
|   To give your item a visual representation, reimplement the \ref draw function and use the passed
 | |
|   QCPPainter to draw the item. You can retrieve the item position in pixel coordinates from the
 | |
|   position member(s) via \ref QCPItemPosition::pixelPosition.
 | |
| 
 | |
|   To optimize performance you should calculate a bounding rect first (don't forget to take the pen
 | |
|   width into account), check whether it intersects the \ref clipRect, and only draw the item at all
 | |
|   if this is the case.
 | |
|   
 | |
|   \subsection items-selection The selectTest function
 | |
|   
 | |
|   Your implementation of the \ref selectTest function may use the helpers \ref
 | |
|   QCPVector2D::distanceSquaredToLine and \ref rectDistance. With these, the implementation of the
 | |
|   selection test becomes significantly simpler for most items. See the documentation of \ref
 | |
|   selectTest for what the function parameters mean and what the function should return.
 | |
|   
 | |
|   \subsection anchors Providing anchors
 | |
|   
 | |
|   Providing anchors (QCPItemAnchor) starts off like adding a position. First you create a public
 | |
|   member, e.g.
 | |
|   
 | |
|   \code QCPItemAnchor * const bottom;\endcode
 | |
| 
 | |
|   and create it in the constructor with the \ref createAnchor function, assigning it a name and an
 | |
|   anchor id (an integer enumerating all anchors on the item, you may create an own enum for this).
 | |
|   Since anchors can be placed anywhere, relative to the item's position(s), your item needs to
 | |
|   provide the position of every anchor with the reimplementation of the \ref anchorPixelPosition(int
 | |
|   anchorId) function.
 | |
|   
 | |
|   In essence the QCPItemAnchor is merely an intermediary that itself asks your item for the pixel
 | |
|   position when anything attached to the anchor needs to know the coordinates.
 | |
| */
 | |
| 
 | |
| /* start of documentation of inline functions */
 | |
| 
 | |
| /*! \fn QList<QCPItemPosition*> QCPAbstractItem::positions() const
 | |
|   
 | |
|   Returns all positions of the item in a list.
 | |
|   
 | |
|   \see anchors, position
 | |
| */
 | |
| 
 | |
| /*! \fn QList<QCPItemAnchor*> QCPAbstractItem::anchors() const
 | |
|   
 | |
|   Returns all anchors of the item in a list. Note that since a position (QCPItemPosition) is always
 | |
|   also an anchor, the list will also contain the positions of this item.
 | |
|   
 | |
|   \see positions, anchor
 | |
| */
 | |
| 
 | |
| /* end of documentation of inline functions */
 | |
| /* start documentation of pure virtual functions */
 | |
| 
 | |
| /*! \fn void QCPAbstractItem::draw(QCPPainter *painter) = 0
 | |
|   \internal
 | |
|   
 | |
|   Draws this item with the provided \a painter.
 | |
|   
 | |
|   The cliprect of the provided painter is set to the rect returned by \ref clipRect before this
 | |
|   function is called. The clipRect depends on the clipping settings defined by \ref
 | |
|   setClipToAxisRect and \ref setClipAxisRect.
 | |
| */
 | |
| 
 | |
| /* end documentation of pure virtual functions */
 | |
| /* start documentation of signals */
 | |
| 
 | |
| /*! \fn void QCPAbstractItem::selectionChanged(bool selected)
 | |
|   This signal is emitted when the selection state of this item has changed, either by user interaction
 | |
|   or by a direct call to \ref setSelected.
 | |
| */
 | |
| 
 | |
| /* end documentation of signals */
 | |
| 
 | |
| /*!
 | |
|   Base class constructor which initializes base class members.
 | |
| */
 | |
| QCPAbstractItem::QCPAbstractItem(QCustomPlot *parentPlot) :
 | |
|   QCPLayerable(parentPlot),
 | |
|   mClipToAxisRect(false),
 | |
|   mSelectable(true),
 | |
|   mSelected(false)
 | |
| {
 | |
|   parentPlot->registerItem(this);
 | |
|   
 | |
|   QList<QCPAxisRect*> rects = parentPlot->axisRects();
 | |
|   if (rects.size() > 0)
 | |
|   {
 | |
|     setClipToAxisRect(true);
 | |
|     setClipAxisRect(rects.first());
 | |
|   }
 | |
| }
 | |
| 
 | |
| QCPAbstractItem::~QCPAbstractItem()
 | |
| {
 | |
|   // don't delete mPositions because every position is also an anchor and thus in mAnchors
 | |
|   qDeleteAll(mAnchors);
 | |
| }
 | |
| 
 | |
| /* can't make this a header inline function, because QPointer breaks with forward declared types, see QTBUG-29588 */
 | |
| QCPAxisRect *QCPAbstractItem::clipAxisRect() const
 | |
| {
 | |
|   return mClipAxisRect.data();
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets whether the item shall be clipped to an axis rect or whether it shall be visible on the
 | |
|   entire QCustomPlot. The axis rect can be set with \ref setClipAxisRect.
 | |
|   
 | |
|   \see setClipAxisRect
 | |
| */
 | |
| void QCPAbstractItem::setClipToAxisRect(bool clip)
 | |
| {
 | |
|   mClipToAxisRect = clip;
 | |
|   if (mClipToAxisRect)
 | |
|     setParentLayerable(mClipAxisRect.data());
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the clip axis rect. It defines the rect that will be used to clip the item when \ref
 | |
|   setClipToAxisRect is set to true.
 | |
|   
 | |
|   \see setClipToAxisRect
 | |
| */
 | |
| void QCPAbstractItem::setClipAxisRect(QCPAxisRect *rect)
 | |
| {
 | |
|   mClipAxisRect = rect;
 | |
|   if (mClipToAxisRect)
 | |
|     setParentLayerable(mClipAxisRect.data());
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets whether the user can (de-)select this item by clicking on the QCustomPlot surface.
 | |
|   (When \ref QCustomPlot::setInteractions contains QCustomPlot::iSelectItems.)
 | |
|   
 | |
|   However, even when \a selectable was set to false, it is possible to set the selection manually,
 | |
|   by calling \ref setSelected.
 | |
|   
 | |
|   \see QCustomPlot::setInteractions, setSelected
 | |
| */
 | |
| void QCPAbstractItem::setSelectable(bool selectable)
 | |
| {
 | |
|   if (mSelectable != selectable)
 | |
|   {
 | |
|     mSelectable = selectable;
 | |
|     emit selectableChanged(mSelectable);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets whether this item is selected or not. When selected, it might use a different visual
 | |
|   appearance (e.g. pen and brush), this depends on the specific item though.
 | |
| 
 | |
|   The entire selection mechanism for items is handled automatically when \ref
 | |
|   QCustomPlot::setInteractions contains QCustomPlot::iSelectItems. You only need to call this
 | |
|   function when you wish to change the selection state manually.
 | |
|   
 | |
|   This function can change the selection state even when \ref setSelectable was set to false.
 | |
|   
 | |
|   emits the \ref selectionChanged signal when \a selected is different from the previous selection state.
 | |
|   
 | |
|   \see setSelectable, selectTest
 | |
| */
 | |
| void QCPAbstractItem::setSelected(bool selected)
 | |
| {
 | |
|   if (mSelected != selected)
 | |
|   {
 | |
|     mSelected = selected;
 | |
|     emit selectionChanged(mSelected);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the QCPItemPosition with the specified \a name. If this item doesn't have a position by
 | |
|   that name, returns 0.
 | |
|   
 | |
|   This function provides an alternative way to access item positions. Normally, you access
 | |
|   positions direcly by their member pointers (which typically have the same variable name as \a
 | |
|   name).
 | |
|   
 | |
|   \see positions, anchor
 | |
| */
 | |
| QCPItemPosition *QCPAbstractItem::position(const QString &name) const
 | |
| {
 | |
|   for (int i=0; i<mPositions.size(); ++i)
 | |
|   {
 | |
|     if (mPositions.at(i)->name() == name)
 | |
|       return mPositions.at(i);
 | |
|   }
 | |
|   qDebug() << Q_FUNC_INFO << "position with name not found:" << name;
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the QCPItemAnchor with the specified \a name. If this item doesn't have an anchor by
 | |
|   that name, returns 0.
 | |
|   
 | |
|   This function provides an alternative way to access item anchors. Normally, you access
 | |
|   anchors direcly by their member pointers (which typically have the same variable name as \a
 | |
|   name).
 | |
|   
 | |
|   \see anchors, position
 | |
| */
 | |
| QCPItemAnchor *QCPAbstractItem::anchor(const QString &name) const
 | |
| {
 | |
|   for (int i=0; i<mAnchors.size(); ++i)
 | |
|   {
 | |
|     if (mAnchors.at(i)->name() == name)
 | |
|       return mAnchors.at(i);
 | |
|   }
 | |
|   qDebug() << Q_FUNC_INFO << "anchor with name not found:" << name;
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns whether this item has an anchor with the specified \a name.
 | |
|   
 | |
|   Note that you can check for positions with this function, too. This is because every position is
 | |
|   also an anchor (QCPItemPosition inherits from QCPItemAnchor).
 | |
|   
 | |
|   \see anchor, position
 | |
| */
 | |
| bool QCPAbstractItem::hasAnchor(const QString &name) const
 | |
| {
 | |
|   for (int i=0; i<mAnchors.size(); ++i)
 | |
|   {
 | |
|     if (mAnchors.at(i)->name() == name)
 | |
|       return true;
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns the rect the visual representation of this item is clipped to. This depends on the
 | |
|   current setting of \ref setClipToAxisRect as well as the axis rect set with \ref setClipAxisRect.
 | |
|   
 | |
|   If the item is not clipped to an axis rect, QCustomPlot's viewport rect is returned.
 | |
|   
 | |
|   \see draw
 | |
| */
 | |
| QRect QCPAbstractItem::clipRect() const
 | |
| {
 | |
|   if (mClipToAxisRect && mClipAxisRect)
 | |
|     return mClipAxisRect.data()->rect();
 | |
|   else
 | |
|     return mParentPlot->viewport();
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   A convenience function to easily set the QPainter::Antialiased hint on the provided \a painter
 | |
|   before drawing item lines.
 | |
| 
 | |
|   This is the antialiasing state the painter passed to the \ref draw method is in by default.
 | |
|   
 | |
|   This function takes into account the local setting of the antialiasing flag as well as the
 | |
|   overrides set with \ref QCustomPlot::setAntialiasedElements and \ref
 | |
|   QCustomPlot::setNotAntialiasedElements.
 | |
|   
 | |
|   \see setAntialiased
 | |
| */
 | |
| void QCPAbstractItem::applyDefaultAntialiasingHint(QCPPainter *painter) const
 | |
| {
 | |
|   applyAntialiasingHint(painter, mAntialiased, QCP::aeItems);
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   A convenience function which returns the selectTest value for a specified \a rect and a specified
 | |
|   click position \a pos. \a filledRect defines whether a click inside the rect should also be
 | |
|   considered a hit or whether only the rect border is sensitive to hits.
 | |
|   
 | |
|   This function may be used to help with the implementation of the \ref selectTest function for
 | |
|   specific items.
 | |
|   
 | |
|   For example, if your item consists of four rects, call this function four times, once for each
 | |
|   rect, in your \ref selectTest reimplementation. Finally, return the minimum (non -1) of all four
 | |
|   returned values.
 | |
| */
 | |
| double QCPAbstractItem::rectDistance(const QRectF &rect, const QPointF &pos, bool filledRect) const
 | |
| {
 | |
|   double result = -1;
 | |
| 
 | |
|   // distance to border:
 | |
|   QList<QLineF> lines;
 | |
|   lines << QLineF(rect.topLeft(), rect.topRight()) << QLineF(rect.bottomLeft(), rect.bottomRight())
 | |
|         << QLineF(rect.topLeft(), rect.bottomLeft()) << QLineF(rect.topRight(), rect.bottomRight());
 | |
|   double minDistSqr = std::numeric_limits<double>::max();
 | |
|   for (int i=0; i<lines.size(); ++i)
 | |
|   {
 | |
|     double distSqr = QCPVector2D(pos).distanceSquaredToLine(lines.at(i).p1(), lines.at(i).p2());
 | |
|     if (distSqr < minDistSqr)
 | |
|       minDistSqr = distSqr;
 | |
|   }
 | |
|   result = qSqrt(minDistSqr);
 | |
|   
 | |
|   // filled rect, allow click inside to count as hit:
 | |
|   if (filledRect && result > mParentPlot->selectionTolerance()*0.99)
 | |
|   {
 | |
|     if (rect.contains(pos))
 | |
|       result = mParentPlot->selectionTolerance()*0.99;
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Returns the pixel position of the anchor with Id \a anchorId. This function must be reimplemented in
 | |
|   item subclasses if they want to provide anchors (QCPItemAnchor).
 | |
|   
 | |
|   For example, if the item has two anchors with id 0 and 1, this function takes one of these anchor
 | |
|   ids and returns the respective pixel points of the specified anchor.
 | |
|   
 | |
|   \see createAnchor
 | |
| */
 | |
| QPointF QCPAbstractItem::anchorPixelPosition(int anchorId) const
 | |
| {
 | |
|   qDebug() << Q_FUNC_INFO << "called on item which shouldn't have any anchors (this method not reimplemented). anchorId" << anchorId;
 | |
|   return QPointF();
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Creates a QCPItemPosition, registers it with this item and returns a pointer to it. The specified
 | |
|   \a name must be a unique string that is usually identical to the variable name of the position
 | |
|   member (This is needed to provide the name-based \ref position access to positions).
 | |
|   
 | |
|   Don't delete positions created by this function manually, as the item will take care of it.
 | |
|   
 | |
|   Use this function in the constructor (initialization list) of the specific item subclass to
 | |
|   create each position member. Don't create QCPItemPositions with \b new yourself, because they
 | |
|   won't be registered with the item properly.
 | |
|   
 | |
|   \see createAnchor
 | |
| */
 | |
| QCPItemPosition *QCPAbstractItem::createPosition(const QString &name)
 | |
| {
 | |
|   if (hasAnchor(name))
 | |
|     qDebug() << Q_FUNC_INFO << "anchor/position with name exists already:" << name;
 | |
|   QCPItemPosition *newPosition = new QCPItemPosition(mParentPlot, this, name);
 | |
|   mPositions.append(newPosition);
 | |
|   mAnchors.append(newPosition); // every position is also an anchor
 | |
|   newPosition->setAxes(mParentPlot->xAxis, mParentPlot->yAxis);
 | |
|   newPosition->setType(QCPItemPosition::ptPlotCoords);
 | |
|   if (mParentPlot->axisRect())
 | |
|     newPosition->setAxisRect(mParentPlot->axisRect());
 | |
|   newPosition->setCoords(0, 0);
 | |
|   return newPosition;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Creates a QCPItemAnchor, registers it with this item and returns a pointer to it. The specified
 | |
|   \a name must be a unique string that is usually identical to the variable name of the anchor
 | |
|   member (This is needed to provide the name based \ref anchor access to anchors).
 | |
|   
 | |
|   The \a anchorId must be a number identifying the created anchor. It is recommended to create an
 | |
|   enum (e.g. "AnchorIndex") for this on each item that uses anchors. This id is used by the anchor
 | |
|   to identify itself when it calls QCPAbstractItem::anchorPixelPosition. That function then returns
 | |
|   the correct pixel coordinates for the passed anchor id.
 | |
|   
 | |
|   Don't delete anchors created by this function manually, as the item will take care of it.
 | |
|   
 | |
|   Use this function in the constructor (initialization list) of the specific item subclass to
 | |
|   create each anchor member. Don't create QCPItemAnchors with \b new yourself, because then they
 | |
|   won't be registered with the item properly.
 | |
|   
 | |
|   \see createPosition
 | |
| */
 | |
| QCPItemAnchor *QCPAbstractItem::createAnchor(const QString &name, int anchorId)
 | |
| {
 | |
|   if (hasAnchor(name))
 | |
|     qDebug() << Q_FUNC_INFO << "anchor/position with name exists already:" << name;
 | |
|   QCPItemAnchor *newAnchor = new QCPItemAnchor(mParentPlot, this, name, anchorId);
 | |
|   mAnchors.append(newAnchor);
 | |
|   return newAnchor;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPAbstractItem::selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged)
 | |
| {
 | |
|   Q_UNUSED(event)
 | |
|   Q_UNUSED(details)
 | |
|   if (mSelectable)
 | |
|   {
 | |
|     bool selBefore = mSelected;
 | |
|     setSelected(additive ? !mSelected : true);
 | |
|     if (selectionStateChanged)
 | |
|       *selectionStateChanged = mSelected != selBefore;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPAbstractItem::deselectEvent(bool *selectionStateChanged)
 | |
| {
 | |
|   if (mSelectable)
 | |
|   {
 | |
|     bool selBefore = mSelected;
 | |
|     setSelected(false);
 | |
|     if (selectionStateChanged)
 | |
|       *selectionStateChanged = mSelected != selBefore;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| QCP::Interaction QCPAbstractItem::selectionCategory() const
 | |
| {
 | |
|   return QCP::iSelectItems;
 | |
| }
 | |
| /* end of 'src/item.cpp' */
 | |
| 
 | |
| 
 | |
| /* including file 'src/core.cpp', size 124243                                */
 | |
| /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCustomPlot
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCustomPlot
 | |
|   
 | |
|   \brief The central class of the library. This is the QWidget which displays the plot and
 | |
|   interacts with the user.
 | |
|   
 | |
|   For tutorials on how to use QCustomPlot, see the website\n
 | |
|   http://www.qcustomplot.com/
 | |
| */
 | |
| 
 | |
| /* start of documentation of inline functions */
 | |
| 
 | |
| /*! \fn QCPSelectionRect *QCustomPlot::selectionRect() const
 | |
|   
 | |
|   Allows access to the currently used QCPSelectionRect instance (or subclass thereof), that is used
 | |
|   to handle and draw selection rect interactions (see \ref setSelectionRectMode).
 | |
|   
 | |
|   \see setSelectionRect
 | |
| */
 | |
| 
 | |
| /*! \fn QCPLayoutGrid *QCustomPlot::plotLayout() const
 | |
|   
 | |
|   Returns the top level layout of this QCustomPlot instance. It is a \ref QCPLayoutGrid, initially containing just
 | |
|   one cell with the main QCPAxisRect inside.
 | |
| */
 | |
| 
 | |
| /* end of documentation of inline functions */
 | |
| /* start of documentation of signals */
 | |
| 
 | |
| /*! \fn void QCustomPlot::mouseDoubleClick(QMouseEvent *event)
 | |
| 
 | |
|   This signal is emitted when the QCustomPlot receives a mouse double click event.
 | |
| */
 | |
| 
 | |
| /*! \fn void QCustomPlot::mousePress(QMouseEvent *event)
 | |
| 
 | |
|   This signal is emitted when the QCustomPlot receives a mouse press event.
 | |
|   
 | |
|   It is emitted before QCustomPlot handles any other mechanism like range dragging. So a slot
 | |
|   connected to this signal can still influence the behaviour e.g. with \ref QCPAxisRect::setRangeDrag or \ref
 | |
|   QCPAxisRect::setRangeDragAxes.
 | |
| */
 | |
| 
 | |
| /*! \fn void QCustomPlot::mouseMove(QMouseEvent *event)
 | |
| 
 | |
|   This signal is emitted when the QCustomPlot receives a mouse move event.
 | |
|   
 | |
|   It is emitted before QCustomPlot handles any other mechanism like range dragging. So a slot
 | |
|   connected to this signal can still influence the behaviour e.g. with \ref QCPAxisRect::setRangeDrag or \ref
 | |
|   QCPAxisRect::setRangeDragAxes.
 | |
|   
 | |
|   \warning It is discouraged to change the drag-axes with \ref QCPAxisRect::setRangeDragAxes here,
 | |
|   because the dragging starting point was saved the moment the mouse was pressed. Thus it only has
 | |
|   a meaning for the range drag axes that were set at that moment. If you want to change the drag
 | |
|   axes, consider doing this in the \ref mousePress signal instead.
 | |
| */
 | |
| 
 | |
| /*! \fn void QCustomPlot::mouseRelease(QMouseEvent *event)
 | |
| 
 | |
|   This signal is emitted when the QCustomPlot receives a mouse release event.
 | |
|   
 | |
|   It is emitted before QCustomPlot handles any other mechanisms like object selection. So a
 | |
|   slot connected to this signal can still influence the behaviour e.g. with \ref setInteractions or
 | |
|   \ref QCPAbstractPlottable::setSelectable.
 | |
| */
 | |
| 
 | |
| /*! \fn void QCustomPlot::mouseWheel(QMouseEvent *event)
 | |
| 
 | |
|   This signal is emitted when the QCustomPlot receives a mouse wheel event.
 | |
|   
 | |
|   It is emitted before QCustomPlot handles any other mechanisms like range zooming. So a slot
 | |
|   connected to this signal can still influence the behaviour e.g. with \ref QCPAxisRect::setRangeZoom, \ref
 | |
|   QCPAxisRect::setRangeZoomAxes or \ref QCPAxisRect::setRangeZoomFactor.
 | |
| */
 | |
| 
 | |
| /*! \fn void QCustomPlot::plottableClick(QCPAbstractPlottable *plottable, int dataIndex, QMouseEvent *event)
 | |
| 
 | |
|   This signal is emitted when a plottable is clicked.
 | |
| 
 | |
|   \a event is the mouse event that caused the click and \a plottable is the plottable that received
 | |
|   the click. The parameter \a dataIndex indicates the data point that was closest to the click
 | |
|   position.
 | |
| 
 | |
|   \see plottableDoubleClick
 | |
| */
 | |
| 
 | |
| /*! \fn void QCustomPlot::plottableDoubleClick(QCPAbstractPlottable *plottable, int dataIndex, QMouseEvent *event)
 | |
| 
 | |
|   This signal is emitted when a plottable is double clicked.
 | |
| 
 | |
|   \a event is the mouse event that caused the click and \a plottable is the plottable that received
 | |
|   the click. The parameter \a dataIndex indicates the data point that was closest to the click
 | |
|   position.
 | |
| 
 | |
|   \see plottableClick
 | |
| */
 | |
| 
 | |
| /*! \fn void QCustomPlot::itemClick(QCPAbstractItem *item, QMouseEvent *event)
 | |
|   
 | |
|   This signal is emitted when an item is clicked.
 | |
| 
 | |
|   \a event is the mouse event that caused the click and \a item is the item that received the
 | |
|   click.
 | |
|   
 | |
|   \see itemDoubleClick
 | |
| */
 | |
| 
 | |
| /*! \fn void QCustomPlot::itemDoubleClick(QCPAbstractItem *item, QMouseEvent *event)
 | |
|   
 | |
|   This signal is emitted when an item is double clicked.
 | |
|   
 | |
|   \a event is the mouse event that caused the click and \a item is the item that received the
 | |
|   click.
 | |
|   
 | |
|   \see itemClick
 | |
| */
 | |
| 
 | |
| /*! \fn void QCustomPlot::axisClick(QCPAxis *axis, QCPAxis::SelectablePart part, QMouseEvent *event)
 | |
|   
 | |
|   This signal is emitted when an axis is clicked.
 | |
|   
 | |
|   \a event is the mouse event that caused the click, \a axis is the axis that received the click and
 | |
|   \a part indicates the part of the axis that was clicked.
 | |
|   
 | |
|   \see axisDoubleClick
 | |
| */
 | |
| 
 | |
| /*! \fn void QCustomPlot::axisDoubleClick(QCPAxis *axis, QCPAxis::SelectablePart part, QMouseEvent *event)
 | |
| 
 | |
|   This signal is emitted when an axis is double clicked.
 | |
|   
 | |
|   \a event is the mouse event that caused the click, \a axis is the axis that received the click and
 | |
|   \a part indicates the part of the axis that was clicked.
 | |
|   
 | |
|   \see axisClick
 | |
| */
 | |
| 
 | |
| /*! \fn void QCustomPlot::legendClick(QCPLegend *legend, QCPAbstractLegendItem *item, QMouseEvent *event)
 | |
| 
 | |
|   This signal is emitted when a legend (item) is clicked.
 | |
|   
 | |
|   \a event is the mouse event that caused the click, \a legend is the legend that received the
 | |
|   click and \a item is the legend item that received the click. If only the legend and no item is
 | |
|   clicked, \a item is 0. This happens for a click inside the legend padding or the space between
 | |
|   two items.
 | |
|   
 | |
|   \see legendDoubleClick
 | |
| */
 | |
| 
 | |
| /*! \fn void QCustomPlot::legendDoubleClick(QCPLegend *legend,  QCPAbstractLegendItem *item, QMouseEvent *event)
 | |
| 
 | |
|   This signal is emitted when a legend (item) is double clicked.
 | |
|   
 | |
|   \a event is the mouse event that caused the click, \a legend is the legend that received the
 | |
|   click and \a item is the legend item that received the click. If only the legend and no item is
 | |
|   clicked, \a item is 0. This happens for a click inside the legend padding or the space between
 | |
|   two items.
 | |
|   
 | |
|   \see legendClick
 | |
| */
 | |
| 
 | |
| /*! \fn void QCustomPlot::selectionChangedByUser()
 | |
|   
 | |
|   This signal is emitted after the user has changed the selection in the QCustomPlot, e.g. by
 | |
|   clicking. It is not emitted when the selection state of an object has changed programmatically by
 | |
|   a direct call to <tt>setSelected()</tt>/<tt>setSelection()</tt> on an object or by calling \ref
 | |
|   deselectAll.
 | |
|   
 | |
|   In addition to this signal, selectable objects also provide individual signals, for example \ref
 | |
|   QCPAxis::selectionChanged or \ref QCPAbstractPlottable::selectionChanged. Note that those signals
 | |
|   are emitted even if the selection state is changed programmatically.
 | |
|   
 | |
|   See the documentation of \ref setInteractions for details about the selection mechanism.
 | |
|   
 | |
|   \see selectedPlottables, selectedGraphs, selectedItems, selectedAxes, selectedLegends
 | |
| */
 | |
| 
 | |
| /*! \fn void QCustomPlot::beforeReplot()
 | |
|   
 | |
|   This signal is emitted immediately before a replot takes place (caused by a call to the slot \ref
 | |
|   replot).
 | |
|   
 | |
|   It is safe to mutually connect the replot slot with this signal on two QCustomPlots to make them
 | |
|   replot synchronously, it won't cause an infinite recursion.
 | |
|   
 | |
|   \see replot, afterReplot
 | |
| */
 | |
| 
 | |
| /*! \fn void QCustomPlot::afterReplot()
 | |
|   
 | |
|   This signal is emitted immediately after a replot has taken place (caused by a call to the slot \ref
 | |
|   replot).
 | |
|   
 | |
|   It is safe to mutually connect the replot slot with this signal on two QCustomPlots to make them
 | |
|   replot synchronously, it won't cause an infinite recursion.
 | |
|   
 | |
|   \see replot, beforeReplot
 | |
| */
 | |
| 
 | |
| /* end of documentation of signals */
 | |
| /* start of documentation of public members */
 | |
| 
 | |
| /*! \var QCPAxis *QCustomPlot::xAxis
 | |
| 
 | |
|   A pointer to the primary x Axis (bottom) of the main axis rect of the plot.
 | |
|   
 | |
|   QCustomPlot offers convenient pointers to the axes (\ref xAxis, \ref yAxis, \ref xAxis2, \ref
 | |
|   yAxis2) and the \ref legend. They make it very easy working with plots that only have a single
 | |
|   axis rect and at most one axis at each axis rect side. If you use \link thelayoutsystem the
 | |
|   layout system\endlink to add multiple axis rects or multiple axes to one side, use the \ref
 | |
|   QCPAxisRect::axis interface to access the new axes. If one of the four default axes or the
 | |
|   default legend is removed due to manipulation of the layout system (e.g. by removing the main
 | |
|   axis rect), the corresponding pointers become 0.
 | |
|   
 | |
|   If an axis convenience pointer is currently zero and a new axis rect or a corresponding axis is
 | |
|   added in the place of the main axis rect, QCustomPlot resets the convenience pointers to the
 | |
|   according new axes. Similarly the \ref legend convenience pointer will be reset if a legend is
 | |
|   added after the main legend was removed before.
 | |
| */
 | |
| 
 | |
| /*! \var QCPAxis *QCustomPlot::yAxis
 | |
| 
 | |
|   A pointer to the primary y Axis (left) of the main axis rect of the plot.
 | |
|   
 | |
|   QCustomPlot offers convenient pointers to the axes (\ref xAxis, \ref yAxis, \ref xAxis2, \ref
 | |
|   yAxis2) and the \ref legend. They make it very easy working with plots that only have a single
 | |
|   axis rect and at most one axis at each axis rect side. If you use \link thelayoutsystem the
 | |
|   layout system\endlink to add multiple axis rects or multiple axes to one side, use the \ref
 | |
|   QCPAxisRect::axis interface to access the new axes. If one of the four default axes or the
 | |
|   default legend is removed due to manipulation of the layout system (e.g. by removing the main
 | |
|   axis rect), the corresponding pointers become 0.
 | |
|   
 | |
|   If an axis convenience pointer is currently zero and a new axis rect or a corresponding axis is
 | |
|   added in the place of the main axis rect, QCustomPlot resets the convenience pointers to the
 | |
|   according new axes. Similarly the \ref legend convenience pointer will be reset if a legend is
 | |
|   added after the main legend was removed before.
 | |
| */
 | |
| 
 | |
| /*! \var QCPAxis *QCustomPlot::xAxis2
 | |
| 
 | |
|   A pointer to the secondary x Axis (top) of the main axis rect of the plot. Secondary axes are
 | |
|   invisible by default. Use QCPAxis::setVisible to change this (or use \ref
 | |
|   QCPAxisRect::setupFullAxesBox).
 | |
|   
 | |
|   QCustomPlot offers convenient pointers to the axes (\ref xAxis, \ref yAxis, \ref xAxis2, \ref
 | |
|   yAxis2) and the \ref legend. They make it very easy working with plots that only have a single
 | |
|   axis rect and at most one axis at each axis rect side. If you use \link thelayoutsystem the
 | |
|   layout system\endlink to add multiple axis rects or multiple axes to one side, use the \ref
 | |
|   QCPAxisRect::axis interface to access the new axes. If one of the four default axes or the
 | |
|   default legend is removed due to manipulation of the layout system (e.g. by removing the main
 | |
|   axis rect), the corresponding pointers become 0.
 | |
|   
 | |
|   If an axis convenience pointer is currently zero and a new axis rect or a corresponding axis is
 | |
|   added in the place of the main axis rect, QCustomPlot resets the convenience pointers to the
 | |
|   according new axes. Similarly the \ref legend convenience pointer will be reset if a legend is
 | |
|   added after the main legend was removed before.
 | |
| */
 | |
| 
 | |
| /*! \var QCPAxis *QCustomPlot::yAxis2
 | |
| 
 | |
|   A pointer to the secondary y Axis (right) of the main axis rect of the plot. Secondary axes are
 | |
|   invisible by default. Use QCPAxis::setVisible to change this (or use \ref
 | |
|   QCPAxisRect::setupFullAxesBox).
 | |
|   
 | |
|   QCustomPlot offers convenient pointers to the axes (\ref xAxis, \ref yAxis, \ref xAxis2, \ref
 | |
|   yAxis2) and the \ref legend. They make it very easy working with plots that only have a single
 | |
|   axis rect and at most one axis at each axis rect side. If you use \link thelayoutsystem the
 | |
|   layout system\endlink to add multiple axis rects or multiple axes to one side, use the \ref
 | |
|   QCPAxisRect::axis interface to access the new axes. If one of the four default axes or the
 | |
|   default legend is removed due to manipulation of the layout system (e.g. by removing the main
 | |
|   axis rect), the corresponding pointers become 0.
 | |
|   
 | |
|   If an axis convenience pointer is currently zero and a new axis rect or a corresponding axis is
 | |
|   added in the place of the main axis rect, QCustomPlot resets the convenience pointers to the
 | |
|   according new axes. Similarly the \ref legend convenience pointer will be reset if a legend is
 | |
|   added after the main legend was removed before.
 | |
| */
 | |
| 
 | |
| /*! \var QCPLegend *QCustomPlot::legend
 | |
| 
 | |
|   A pointer to the default legend of the main axis rect. The legend is invisible by default. Use
 | |
|   QCPLegend::setVisible to change this.
 | |
|   
 | |
|   QCustomPlot offers convenient pointers to the axes (\ref xAxis, \ref yAxis, \ref xAxis2, \ref
 | |
|   yAxis2) and the \ref legend. They make it very easy working with plots that only have a single
 | |
|   axis rect and at most one axis at each axis rect side. If you use \link thelayoutsystem the
 | |
|   layout system\endlink to add multiple legends to the plot, use the layout system interface to
 | |
|   access the new legend. For example, legends can be placed inside an axis rect's \ref
 | |
|   QCPAxisRect::insetLayout "inset layout", and must then also be accessed via the inset layout. If
 | |
|   the default legend is removed due to manipulation of the layout system (e.g. by removing the main
 | |
|   axis rect), the corresponding pointer becomes 0.
 | |
|   
 | |
|   If an axis convenience pointer is currently zero and a new axis rect or a corresponding axis is
 | |
|   added in the place of the main axis rect, QCustomPlot resets the convenience pointers to the
 | |
|   according new axes. Similarly the \ref legend convenience pointer will be reset if a legend is
 | |
|   added after the main legend was removed before.
 | |
| */
 | |
| 
 | |
| /* end of documentation of public members */
 | |
| 
 | |
| /*!
 | |
|   Constructs a QCustomPlot and sets reasonable default values.
 | |
| */
 | |
| QCustomPlot::QCustomPlot(QWidget *parent) :
 | |
|   QWidget(parent),
 | |
|   xAxis(0),
 | |
|   yAxis(0),
 | |
|   xAxis2(0),
 | |
|   yAxis2(0),
 | |
|   legend(0),
 | |
|   mBufferDevicePixelRatio(1.0), // will be adapted to primary screen below
 | |
|   mPlotLayout(0),
 | |
|   mAutoAddPlottableToLegend(true),
 | |
|   mAntialiasedElements(QCP::aeNone),
 | |
|   mNotAntialiasedElements(QCP::aeNone),
 | |
|   mInteractions(0),
 | |
|   mSelectionTolerance(8),
 | |
|   mNoAntialiasingOnDrag(false),
 | |
|   mBackgroundBrush(Qt::white, Qt::SolidPattern),
 | |
|   mBackgroundScaled(true),
 | |
|   mBackgroundScaledMode(Qt::KeepAspectRatioByExpanding),
 | |
|   mCurrentLayer(0),
 | |
|   mPlottingHints(QCP::phCacheLabels|QCP::phImmediateRefresh),
 | |
|   mMultiSelectModifier(Qt::ControlModifier),
 | |
|   mSelectionRectMode(QCP::srmNone),
 | |
|   mSelectionRect(0),
 | |
|   mOpenGl(false),
 | |
|   mMouseHasMoved(false),
 | |
|   mMouseEventLayerable(0),
 | |
|   mReplotting(false),
 | |
|   mReplotQueued(false),
 | |
|   mOpenGlMultisamples(16),
 | |
|   mOpenGlAntialiasedElementsBackup(QCP::aeNone),
 | |
|   mOpenGlCacheLabelsBackup(true)
 | |
| {
 | |
|   setAttribute(Qt::WA_NoMousePropagation);
 | |
|   setAttribute(Qt::WA_OpaquePaintEvent);
 | |
|   setFocusPolicy(Qt::ClickFocus);
 | |
|   setMouseTracking(true);
 | |
|   QLocale currentLocale = locale();
 | |
|   currentLocale.setNumberOptions(QLocale::OmitGroupSeparator);
 | |
|   setLocale(currentLocale);
 | |
| #ifdef QCP_DEVICEPIXELRATIO_SUPPORTED
 | |
|   setBufferDevicePixelRatio(QWidget::devicePixelRatio());
 | |
| #endif
 | |
|   
 | |
|   mOpenGlAntialiasedElementsBackup = mAntialiasedElements;
 | |
|   mOpenGlCacheLabelsBackup = mPlottingHints.testFlag(QCP::phCacheLabels);
 | |
|   // create initial layers:
 | |
|   mLayers.append(new QCPLayer(this, QLatin1String("background")));
 | |
|   mLayers.append(new QCPLayer(this, QLatin1String("grid")));
 | |
|   mLayers.append(new QCPLayer(this, QLatin1String("main")));
 | |
|   mLayers.append(new QCPLayer(this, QLatin1String("axes")));
 | |
|   mLayers.append(new QCPLayer(this, QLatin1String("legend")));
 | |
|   mLayers.append(new QCPLayer(this, QLatin1String("overlay")));
 | |
|   updateLayerIndices();
 | |
|   setCurrentLayer(QLatin1String("main"));
 | |
|   layer(QLatin1String("overlay"))->setMode(QCPLayer::lmBuffered);
 | |
|   
 | |
|   // create initial layout, axis rect and legend:
 | |
|   mPlotLayout = new QCPLayoutGrid;
 | |
|   mPlotLayout->initializeParentPlot(this);
 | |
|   mPlotLayout->setParent(this); // important because if parent is QWidget, QCPLayout::sizeConstraintsChanged will call QWidget::updateGeometry
 | |
|   mPlotLayout->setLayer(QLatin1String("main"));
 | |
|   QCPAxisRect *defaultAxisRect = new QCPAxisRect(this, true);
 | |
|   mPlotLayout->addElement(0, 0, defaultAxisRect);
 | |
|   xAxis = defaultAxisRect->axis(QCPAxis::atBottom);
 | |
|   yAxis = defaultAxisRect->axis(QCPAxis::atLeft);
 | |
|   xAxis2 = defaultAxisRect->axis(QCPAxis::atTop);
 | |
|   yAxis2 = defaultAxisRect->axis(QCPAxis::atRight);
 | |
|   legend = new QCPLegend;
 | |
|   legend->setVisible(false);
 | |
|   defaultAxisRect->insetLayout()->addElement(legend, Qt::AlignRight|Qt::AlignTop);
 | |
|   defaultAxisRect->insetLayout()->setMargins(QMargins(12, 12, 12, 12));
 | |
|   
 | |
|   defaultAxisRect->setLayer(QLatin1String("background"));
 | |
|   xAxis->setLayer(QLatin1String("axes"));
 | |
|   yAxis->setLayer(QLatin1String("axes"));
 | |
|   xAxis2->setLayer(QLatin1String("axes"));
 | |
|   yAxis2->setLayer(QLatin1String("axes"));
 | |
|   xAxis->grid()->setLayer(QLatin1String("grid"));
 | |
|   yAxis->grid()->setLayer(QLatin1String("grid"));
 | |
|   xAxis2->grid()->setLayer(QLatin1String("grid"));
 | |
|   yAxis2->grid()->setLayer(QLatin1String("grid"));
 | |
|   legend->setLayer(QLatin1String("legend"));
 | |
|   
 | |
|   // create selection rect instance:
 | |
|   mSelectionRect = new QCPSelectionRect(this);
 | |
|   mSelectionRect->setLayer(QLatin1String("overlay"));
 | |
|   
 | |
|   setViewport(rect()); // needs to be called after mPlotLayout has been created
 | |
|   
 | |
|   replot(rpQueuedReplot);
 | |
| }
 | |
| 
 | |
| QCustomPlot::~QCustomPlot()
 | |
| {
 | |
|   clearPlottables();
 | |
|   clearItems();
 | |
| 
 | |
|   if (mPlotLayout)
 | |
|   {
 | |
|     delete mPlotLayout;
 | |
|     mPlotLayout = 0;
 | |
|   }
 | |
|   
 | |
|   mCurrentLayer = 0;
 | |
|   qDeleteAll(mLayers); // don't use removeLayer, because it would prevent the last layer to be removed
 | |
|   mLayers.clear();
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets which elements are forcibly drawn antialiased as an \a or combination of QCP::AntialiasedElement.
 | |
|   
 | |
|   This overrides the antialiasing settings for whole element groups, normally controlled with the
 | |
|   \a setAntialiasing function on the individual elements. If an element is neither specified in
 | |
|   \ref setAntialiasedElements nor in \ref setNotAntialiasedElements, the antialiasing setting on
 | |
|   each individual element instance is used.
 | |
|   
 | |
|   For example, if \a antialiasedElements contains \ref QCP::aePlottables, all plottables will be
 | |
|   drawn antialiased, no matter what the specific QCPAbstractPlottable::setAntialiased value was set
 | |
|   to.
 | |
|   
 | |
|   if an element in \a antialiasedElements is already set in \ref setNotAntialiasedElements, it is
 | |
|   removed from there.
 | |
|   
 | |
|   \see setNotAntialiasedElements
 | |
| */
 | |
| void QCustomPlot::setAntialiasedElements(const QCP::AntialiasedElements &antialiasedElements)
 | |
| {
 | |
|   mAntialiasedElements = antialiasedElements;
 | |
|   
 | |
|   // make sure elements aren't in mNotAntialiasedElements and mAntialiasedElements simultaneously:
 | |
|   if ((mNotAntialiasedElements & mAntialiasedElements) != 0)
 | |
|     mNotAntialiasedElements |= ~mAntialiasedElements;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets whether the specified \a antialiasedElement is forcibly drawn antialiased.
 | |
|   
 | |
|   See \ref setAntialiasedElements for details.
 | |
|   
 | |
|   \see setNotAntialiasedElement
 | |
| */
 | |
| void QCustomPlot::setAntialiasedElement(QCP::AntialiasedElement antialiasedElement, bool enabled)
 | |
| {
 | |
|   if (!enabled && mAntialiasedElements.testFlag(antialiasedElement))
 | |
|     mAntialiasedElements &= ~antialiasedElement;
 | |
|   else if (enabled && !mAntialiasedElements.testFlag(antialiasedElement))
 | |
|     mAntialiasedElements |= antialiasedElement;
 | |
|   
 | |
|   // make sure elements aren't in mNotAntialiasedElements and mAntialiasedElements simultaneously:
 | |
|   if ((mNotAntialiasedElements & mAntialiasedElements) != 0)
 | |
|     mNotAntialiasedElements |= ~mAntialiasedElements;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets which elements are forcibly drawn not antialiased as an \a or combination of
 | |
|   QCP::AntialiasedElement.
 | |
|   
 | |
|   This overrides the antialiasing settings for whole element groups, normally controlled with the
 | |
|   \a setAntialiasing function on the individual elements. If an element is neither specified in
 | |
|   \ref setAntialiasedElements nor in \ref setNotAntialiasedElements, the antialiasing setting on
 | |
|   each individual element instance is used.
 | |
|   
 | |
|   For example, if \a notAntialiasedElements contains \ref QCP::aePlottables, no plottables will be
 | |
|   drawn antialiased, no matter what the specific QCPAbstractPlottable::setAntialiased value was set
 | |
|   to.
 | |
|   
 | |
|   if an element in \a notAntialiasedElements is already set in \ref setAntialiasedElements, it is
 | |
|   removed from there.
 | |
|   
 | |
|   \see setAntialiasedElements
 | |
| */
 | |
| void QCustomPlot::setNotAntialiasedElements(const QCP::AntialiasedElements ¬AntialiasedElements)
 | |
| {
 | |
|   mNotAntialiasedElements = notAntialiasedElements;
 | |
|   
 | |
|   // make sure elements aren't in mNotAntialiasedElements and mAntialiasedElements simultaneously:
 | |
|   if ((mNotAntialiasedElements & mAntialiasedElements) != 0)
 | |
|     mAntialiasedElements |= ~mNotAntialiasedElements;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets whether the specified \a notAntialiasedElement is forcibly drawn not antialiased.
 | |
|   
 | |
|   See \ref setNotAntialiasedElements for details.
 | |
|   
 | |
|   \see setAntialiasedElement
 | |
| */
 | |
| void QCustomPlot::setNotAntialiasedElement(QCP::AntialiasedElement notAntialiasedElement, bool enabled)
 | |
| {
 | |
|   if (!enabled && mNotAntialiasedElements.testFlag(notAntialiasedElement))
 | |
|     mNotAntialiasedElements &= ~notAntialiasedElement;
 | |
|   else if (enabled && !mNotAntialiasedElements.testFlag(notAntialiasedElement))
 | |
|     mNotAntialiasedElements |= notAntialiasedElement;
 | |
|   
 | |
|   // make sure elements aren't in mNotAntialiasedElements and mAntialiasedElements simultaneously:
 | |
|   if ((mNotAntialiasedElements & mAntialiasedElements) != 0)
 | |
|     mAntialiasedElements |= ~mNotAntialiasedElements;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   If set to true, adding a plottable (e.g. a graph) to the QCustomPlot automatically also adds the
 | |
|   plottable to the legend (QCustomPlot::legend).
 | |
|   
 | |
|   \see addGraph, QCPLegend::addItem
 | |
| */
 | |
| void QCustomPlot::setAutoAddPlottableToLegend(bool on)
 | |
| {
 | |
|   mAutoAddPlottableToLegend = on;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the possible interactions of this QCustomPlot as an or-combination of \ref QCP::Interaction
 | |
|   enums. There are the following types of interactions:
 | |
|   
 | |
|   <b>Axis range manipulation</b> is controlled via \ref QCP::iRangeDrag and \ref QCP::iRangeZoom. When the
 | |
|   respective interaction is enabled, the user may drag axes ranges and zoom with the mouse wheel.
 | |
|   For details how to control which axes the user may drag/zoom and in what orientations, see \ref
 | |
|   QCPAxisRect::setRangeDrag, \ref QCPAxisRect::setRangeZoom, \ref QCPAxisRect::setRangeDragAxes,
 | |
|   \ref QCPAxisRect::setRangeZoomAxes.
 | |
|   
 | |
|   <b>Plottable data selection</b> is controlled by \ref QCP::iSelectPlottables. If \ref
 | |
|   QCP::iSelectPlottables is set, the user may select plottables (graphs, curves, bars,...) and
 | |
|   their data by clicking on them or in their vicinity (\ref setSelectionTolerance). Whether the
 | |
|   user can actually select a plottable and its data can further be restricted with the \ref
 | |
|   QCPAbstractPlottable::setSelectable method on the specific plottable. For details, see the
 | |
|   special page about the \ref dataselection "data selection mechanism". To retrieve a list of all
 | |
|   currently selected plottables, call \ref selectedPlottables. If you're only interested in
 | |
|   QCPGraphs, you may use the convenience function \ref selectedGraphs.
 | |
|   
 | |
|   <b>Item selection</b> is controlled by \ref QCP::iSelectItems. If \ref QCP::iSelectItems is set, the user
 | |
|   may select items (QCPItemLine, QCPItemText,...) by clicking on them or in their vicinity. To find
 | |
|   out whether a specific item is selected, call QCPAbstractItem::selected(). To retrieve a list of
 | |
|   all currently selected items, call \ref selectedItems.
 | |
|   
 | |
|   <b>Axis selection</b> is controlled with \ref QCP::iSelectAxes. If \ref QCP::iSelectAxes is set, the user
 | |
|   may select parts of the axes by clicking on them. What parts exactly (e.g. Axis base line, tick
 | |
|   labels, axis label) are selectable can be controlled via \ref QCPAxis::setSelectableParts for
 | |
|   each axis. To retrieve a list of all axes that currently contain selected parts, call \ref
 | |
|   selectedAxes. Which parts of an axis are selected, can be retrieved with QCPAxis::selectedParts().
 | |
|   
 | |
|   <b>Legend selection</b> is controlled with \ref QCP::iSelectLegend. If this is set, the user may
 | |
|   select the legend itself or individual items by clicking on them. What parts exactly are
 | |
|   selectable can be controlled via \ref QCPLegend::setSelectableParts. To find out whether the
 | |
|   legend or any of its child items are selected, check the value of QCPLegend::selectedParts. To
 | |
|   find out which child items are selected, call \ref QCPLegend::selectedItems.
 | |
|   
 | |
|   <b>All other selectable elements</b> The selection of all other selectable objects (e.g.
 | |
|   QCPTextElement, or your own layerable subclasses) is controlled with \ref QCP::iSelectOther. If set, the
 | |
|   user may select those objects by clicking on them. To find out which are currently selected, you
 | |
|   need to check their selected state explicitly.
 | |
|   
 | |
|   If the selection state has changed by user interaction, the \ref selectionChangedByUser signal is
 | |
|   emitted. Each selectable object additionally emits an individual selectionChanged signal whenever
 | |
|   their selection state has changed, i.e. not only by user interaction.
 | |
|   
 | |
|   To allow multiple objects to be selected by holding the selection modifier (\ref
 | |
|   setMultiSelectModifier), set the flag \ref QCP::iMultiSelect.
 | |
|   
 | |
|   \note In addition to the selection mechanism presented here, QCustomPlot always emits
 | |
|   corresponding signals, when an object is clicked or double clicked. see \ref plottableClick and
 | |
|   \ref plottableDoubleClick for example.
 | |
|   
 | |
|   \see setInteraction, setSelectionTolerance
 | |
| */
 | |
| void QCustomPlot::setInteractions(const QCP::Interactions &interactions)
 | |
| {
 | |
|   mInteractions = interactions;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the single \a interaction of this QCustomPlot to \a enabled.
 | |
|   
 | |
|   For details about the interaction system, see \ref setInteractions.
 | |
|   
 | |
|   \see setInteractions
 | |
| */
 | |
| void QCustomPlot::setInteraction(const QCP::Interaction &interaction, bool enabled)
 | |
| {
 | |
|   if (!enabled && mInteractions.testFlag(interaction))
 | |
|     mInteractions &= ~interaction;
 | |
|   else if (enabled && !mInteractions.testFlag(interaction))
 | |
|     mInteractions |= interaction;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the tolerance that is used to decide whether a click selects an object (e.g. a plottable) or
 | |
|   not.
 | |
|   
 | |
|   If the user clicks in the vicinity of the line of e.g. a QCPGraph, it's only regarded as a
 | |
|   potential selection when the minimum distance between the click position and the graph line is
 | |
|   smaller than \a pixels. Objects that are defined by an area (e.g. QCPBars) only react to clicks
 | |
|   directly inside the area and ignore this selection tolerance. In other words, it only has meaning
 | |
|   for parts of objects that are too thin to exactly hit with a click and thus need such a
 | |
|   tolerance.
 | |
|   
 | |
|   \see setInteractions, QCPLayerable::selectTest
 | |
| */
 | |
| void QCustomPlot::setSelectionTolerance(int pixels)
 | |
| {
 | |
|   mSelectionTolerance = pixels;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets whether antialiasing is disabled for this QCustomPlot while the user is dragging axes
 | |
|   ranges. If many objects, especially plottables, are drawn antialiased, this greatly improves
 | |
|   performance during dragging. Thus it creates a more responsive user experience. As soon as the
 | |
|   user stops dragging, the last replot is done with normal antialiasing, to restore high image
 | |
|   quality.
 | |
|   
 | |
|   \see setAntialiasedElements, setNotAntialiasedElements
 | |
| */
 | |
| void QCustomPlot::setNoAntialiasingOnDrag(bool enabled)
 | |
| {
 | |
|   mNoAntialiasingOnDrag = enabled;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the plotting hints for this QCustomPlot instance as an \a or combination of QCP::PlottingHint.
 | |
|   
 | |
|   \see setPlottingHint
 | |
| */
 | |
| void QCustomPlot::setPlottingHints(const QCP::PlottingHints &hints)
 | |
| {
 | |
|   mPlottingHints = hints;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the specified plotting \a hint to \a enabled.
 | |
|   
 | |
|   \see setPlottingHints
 | |
| */
 | |
| void QCustomPlot::setPlottingHint(QCP::PlottingHint hint, bool enabled)
 | |
| {
 | |
|   QCP::PlottingHints newHints = mPlottingHints;
 | |
|   if (!enabled)
 | |
|     newHints &= ~hint;
 | |
|   else
 | |
|     newHints |= hint;
 | |
|   
 | |
|   if (newHints != mPlottingHints)
 | |
|     setPlottingHints(newHints);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the keyboard modifier that will be recognized as multi-select-modifier.
 | |
|   
 | |
|   If \ref QCP::iMultiSelect is specified in \ref setInteractions, the user may select multiple
 | |
|   objects (or data points) by clicking on them one after the other while holding down \a modifier.
 | |
|   
 | |
|   By default the multi-select-modifier is set to Qt::ControlModifier.
 | |
|   
 | |
|   \see setInteractions
 | |
| */
 | |
| void QCustomPlot::setMultiSelectModifier(Qt::KeyboardModifier modifier)
 | |
| {
 | |
|   mMultiSelectModifier = modifier;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets how QCustomPlot processes mouse click-and-drag interactions by the user.
 | |
| 
 | |
|   If \a mode is \ref QCP::srmNone, the mouse drag is forwarded to the underlying objects. For
 | |
|   example, QCPAxisRect may process a mouse drag by dragging axis ranges, see \ref
 | |
|   QCPAxisRect::setRangeDrag. If \a mode is not \ref QCP::srmNone, the current selection rect (\ref
 | |
|   selectionRect) becomes activated and allows e.g. rect zooming and data point selection.
 | |
|   
 | |
|   If you wish to provide your user both with axis range dragging and data selection/range zooming,
 | |
|   use this method to switch between the modes just before the interaction is processed, e.g. in
 | |
|   reaction to the \ref mousePress or \ref mouseMove signals. For example you could check whether
 | |
|   the user is holding a certain keyboard modifier, and then decide which \a mode shall be set.
 | |
|   
 | |
|   If a selection rect interaction is currently active, and \a mode is set to \ref QCP::srmNone, the
 | |
|   interaction is canceled (\ref QCPSelectionRect::cancel). Switching between any of the other modes
 | |
|   will keep the selection rect active. Upon completion of the interaction, the behaviour is as
 | |
|   defined by the currently set \a mode, not the mode that was set when the interaction started.
 | |
|   
 | |
|   \see setInteractions, setSelectionRect, QCPSelectionRect
 | |
| */
 | |
| void QCustomPlot::setSelectionRectMode(QCP::SelectionRectMode mode)
 | |
| {
 | |
|   if (mSelectionRect)
 | |
|   {
 | |
|     if (mode == QCP::srmNone)
 | |
|       mSelectionRect->cancel(); // when switching to none, we immediately want to abort a potentially active selection rect
 | |
|     
 | |
|     // disconnect old connections:
 | |
|     if (mSelectionRectMode == QCP::srmSelect)
 | |
|       disconnect(mSelectionRect, SIGNAL(accepted(QRect,QMouseEvent*)), this, SLOT(processRectSelection(QRect,QMouseEvent*)));
 | |
|     else if (mSelectionRectMode == QCP::srmZoom)
 | |
|       disconnect(mSelectionRect, SIGNAL(accepted(QRect,QMouseEvent*)), this, SLOT(processRectZoom(QRect,QMouseEvent*)));
 | |
|     
 | |
|     // establish new ones:
 | |
|     if (mode == QCP::srmSelect)
 | |
|       connect(mSelectionRect, SIGNAL(accepted(QRect,QMouseEvent*)), this, SLOT(processRectSelection(QRect,QMouseEvent*)));
 | |
|     else if (mode == QCP::srmZoom)
 | |
|       connect(mSelectionRect, SIGNAL(accepted(QRect,QMouseEvent*)), this, SLOT(processRectZoom(QRect,QMouseEvent*)));
 | |
|   }
 | |
|   
 | |
|   mSelectionRectMode = mode;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the \ref QCPSelectionRect instance that QCustomPlot will use if \a mode is not \ref
 | |
|   QCP::srmNone and the user performs a click-and-drag interaction. QCustomPlot takes ownership of
 | |
|   the passed \a selectionRect. It can be accessed later via \ref selectionRect.
 | |
|   
 | |
|   This method is useful if you wish to replace the default QCPSelectionRect instance with an
 | |
|   instance of a QCPSelectionRect subclass, to introduce custom behaviour of the selection rect.
 | |
|   
 | |
|   \see setSelectionRectMode
 | |
| */
 | |
| void QCustomPlot::setSelectionRect(QCPSelectionRect *selectionRect)
 | |
| {
 | |
|   if (mSelectionRect)
 | |
|     delete mSelectionRect;
 | |
|   
 | |
|   mSelectionRect = selectionRect;
 | |
|   
 | |
|   if (mSelectionRect)
 | |
|   {
 | |
|     // establish connections with new selection rect:
 | |
|     if (mSelectionRectMode == QCP::srmSelect)
 | |
|       connect(mSelectionRect, SIGNAL(accepted(QRect,QMouseEvent*)), this, SLOT(processRectSelection(QRect,QMouseEvent*)));
 | |
|     else if (mSelectionRectMode == QCP::srmZoom)
 | |
|       connect(mSelectionRect, SIGNAL(accepted(QRect,QMouseEvent*)), this, SLOT(processRectZoom(QRect,QMouseEvent*)));
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   This method allows to enable OpenGL plot rendering, for increased plotting performance of
 | |
|   graphically demanding plots (thick lines, translucent fills, etc.).
 | |
| 
 | |
|   If \a enabled is set to true, QCustomPlot will try to initialize OpenGL and, if successful,
 | |
|   continue plotting with hardware acceleration. The parameter \a multisampling controls how many
 | |
|   samples will be used per pixel, it essentially controls the antialiasing quality. If \a
 | |
|   multisampling is set too high for the current graphics hardware, the maximum allowed value will
 | |
|   be used.
 | |
| 
 | |
|   You can test whether switching to OpenGL rendering was successful by checking whether the
 | |
|   according getter \a QCustomPlot::openGl() returns true. If the OpenGL initialization fails,
 | |
|   rendering continues with the regular software rasterizer, and an according qDebug output is
 | |
|   generated.
 | |
| 
 | |
|   If switching to OpenGL was successful, this method disables label caching (\ref setPlottingHint
 | |
|   "setPlottingHint(QCP::phCacheLabels, false)") and turns on QCustomPlot's antialiasing override
 | |
|   for all elements (\ref setAntialiasedElements "setAntialiasedElements(QCP::aeAll)"), leading to a
 | |
|   higher quality output. The antialiasing override allows for pixel-grid aligned drawing in the
 | |
|   OpenGL paint device. As stated before, in OpenGL rendering the actual antialiasing of the plot is
 | |
|   controlled with \a multisampling. If \a enabled is set to false, the antialiasing/label caching
 | |
|   settings are restored to what they were before OpenGL was enabled, if they weren't altered in the
 | |
|   meantime.
 | |
| 
 | |
|   \note OpenGL support is only enabled if QCustomPlot is compiled with the macro \c QCUSTOMPLOT_USE_OPENGL
 | |
|   defined. This define must be set before including the QCustomPlot header both during compilation
 | |
|   of the QCustomPlot library as well as when compiling your application. It is best to just include
 | |
|   the line <tt>DEFINES += QCUSTOMPLOT_USE_OPENGL</tt> in the respective qmake project files.
 | |
|   \note If you are using a Qt version before 5.0, you must also add the module "opengl" to your \c
 | |
|   QT variable in the qmake project files. For Qt versions 5.0 and higher, QCustomPlot switches to a
 | |
|   newer OpenGL interface which is already in the "gui" module.
 | |
| */
 | |
| void QCustomPlot::setOpenGl(bool enabled, int multisampling)
 | |
| {
 | |
|   mOpenGlMultisamples = qMax(0, multisampling);
 | |
| #ifdef QCUSTOMPLOT_USE_OPENGL
 | |
|   mOpenGl = enabled;
 | |
|   if (mOpenGl)
 | |
|   {
 | |
|     if (setupOpenGl())
 | |
|     {
 | |
|       // backup antialiasing override and labelcaching setting so we can restore upon disabling OpenGL
 | |
|       mOpenGlAntialiasedElementsBackup = mAntialiasedElements;
 | |
|       mOpenGlCacheLabelsBackup = mPlottingHints.testFlag(QCP::phCacheLabels);
 | |
|       // set antialiasing override to antialias all (aligns gl pixel grid properly), and disable label caching (would use software rasterizer for pixmap caches):
 | |
|       setAntialiasedElements(QCP::aeAll);
 | |
|       setPlottingHint(QCP::phCacheLabels, false);
 | |
|     } else
 | |
|     {
 | |
|       qDebug() << Q_FUNC_INFO << "Failed to enable OpenGL, continuing plotting without hardware acceleration.";
 | |
|       mOpenGl = false;
 | |
|     }
 | |
|   } else
 | |
|   {
 | |
|     // restore antialiasing override and labelcaching to what it was before enabling OpenGL, if nobody changed it in the meantime:
 | |
|     if (mAntialiasedElements == QCP::aeAll)
 | |
|       setAntialiasedElements(mOpenGlAntialiasedElementsBackup);
 | |
|     if (!mPlottingHints.testFlag(QCP::phCacheLabels))
 | |
|       setPlottingHint(QCP::phCacheLabels, mOpenGlCacheLabelsBackup);
 | |
|     freeOpenGl();
 | |
|   }
 | |
|   // recreate all paint buffers:
 | |
|   mPaintBuffers.clear();
 | |
|   setupPaintBuffers();
 | |
| #else
 | |
|   Q_UNUSED(enabled)
 | |
|   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)";
 | |
| #endif
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the viewport of this QCustomPlot. Usually users of QCustomPlot don't need to change the
 | |
|   viewport manually.
 | |
| 
 | |
|   The viewport is the area in which the plot is drawn. All mechanisms, e.g. margin caluclation take
 | |
|   the viewport to be the outer border of the plot. The viewport normally is the rect() of the
 | |
|   QCustomPlot widget, i.e. a rect with top left (0, 0) and size of the QCustomPlot widget.
 | |
| 
 | |
|   Don't confuse the viewport with the axis rect (QCustomPlot::axisRect). An axis rect is typically
 | |
|   an area enclosed by four axes, where the graphs/plottables are drawn in. The viewport is larger
 | |
|   and contains also the axes themselves, their tick numbers, their labels, or even additional axis
 | |
|   rects, color scales and other layout elements.
 | |
| 
 | |
|   This function is used to allow arbitrary size exports with \ref toPixmap, \ref savePng, \ref
 | |
|   savePdf, etc. by temporarily changing the viewport size.
 | |
| */
 | |
| void QCustomPlot::setViewport(const QRect &rect)
 | |
| {
 | |
|   mViewport = rect;
 | |
|   if (mPlotLayout)
 | |
|     mPlotLayout->setOuterRect(mViewport);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the device pixel ratio used by the paint buffers of this QCustomPlot instance.
 | |
| 
 | |
|   Normally, this doesn't need to be set manually, because it is initialized with the regular \a
 | |
|   QWidget::devicePixelRatio which is configured by Qt to fit the display device (e.g. 1 for normal
 | |
|   displays, 2 for High-DPI displays).
 | |
| 
 | |
|   Device pixel ratios are supported by Qt only for Qt versions since 5.4. If this method is called
 | |
|   when QCustomPlot is being used with older Qt versions, outputs an according qDebug message and
 | |
|   leaves the internal buffer device pixel ratio at 1.0.
 | |
| */
 | |
| void QCustomPlot::setBufferDevicePixelRatio(double ratio)
 | |
| {
 | |
|   if (!qFuzzyCompare(ratio, mBufferDevicePixelRatio))
 | |
|   {
 | |
| #ifdef QCP_DEVICEPIXELRATIO_SUPPORTED
 | |
|     mBufferDevicePixelRatio = ratio;
 | |
|     for (int i=0; i<mPaintBuffers.size(); ++i)
 | |
|       mPaintBuffers.at(i)->setDevicePixelRatio(mBufferDevicePixelRatio);
 | |
|     // Note: axis label cache has devicePixelRatio as part of cache hash, so no need to manually clear cache here
 | |
| #else
 | |
|     qDebug() << Q_FUNC_INFO << "Device pixel ratios not supported for Qt versions before 5.4";
 | |
|     mBufferDevicePixelRatio = 1.0;
 | |
| #endif
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets \a pm as the viewport background pixmap (see \ref setViewport). The pixmap is always drawn
 | |
|   below all other objects in the plot.
 | |
| 
 | |
|   For cases where the provided pixmap doesn't have the same size as the viewport, scaling can be
 | |
|   enabled with \ref setBackgroundScaled and the scaling mode (whether and how the aspect ratio is
 | |
|   preserved) can be set with \ref setBackgroundScaledMode. To set all these options in one call,
 | |
|   consider using the overloaded version of this function.
 | |
|   
 | |
|   If a background brush was set with \ref setBackground(const QBrush &brush), the viewport will
 | |
|   first be filled with that brush, before drawing the background pixmap. This can be useful for
 | |
|   background pixmaps with translucent areas.
 | |
| 
 | |
|   \see setBackgroundScaled, setBackgroundScaledMode
 | |
| */
 | |
| void QCustomPlot::setBackground(const QPixmap &pm)
 | |
| {
 | |
|   mBackgroundPixmap = pm;
 | |
|   mScaledBackgroundPixmap = QPixmap();
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the background brush of the viewport (see \ref setViewport).
 | |
| 
 | |
|   Before drawing everything else, the background is filled with \a brush. If a background pixmap
 | |
|   was set with \ref setBackground(const QPixmap &pm), this brush will be used to fill the viewport
 | |
|   before the background pixmap is drawn. This can be useful for background pixmaps with translucent
 | |
|   areas.
 | |
|   
 | |
|   Set \a brush to Qt::NoBrush or Qt::Transparent to leave background transparent. This can be
 | |
|   useful for exporting to image formats which support transparency, e.g. \ref savePng.
 | |
| 
 | |
|   \see setBackgroundScaled, setBackgroundScaledMode
 | |
| */
 | |
| void QCustomPlot::setBackground(const QBrush &brush)
 | |
| {
 | |
|   mBackgroundBrush = brush;
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Allows setting the background pixmap of the viewport, whether it shall be scaled and how it
 | |
|   shall be scaled in one call.
 | |
| 
 | |
|   \see setBackground(const QPixmap &pm), setBackgroundScaled, setBackgroundScaledMode
 | |
| */
 | |
| void QCustomPlot::setBackground(const QPixmap &pm, bool scaled, Qt::AspectRatioMode mode)
 | |
| {
 | |
|   mBackgroundPixmap = pm;
 | |
|   mScaledBackgroundPixmap = QPixmap();
 | |
|   mBackgroundScaled = scaled;
 | |
|   mBackgroundScaledMode = mode;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets whether the viewport background pixmap shall be scaled to fit the viewport. If \a scaled is
 | |
|   set to true, control whether and how the aspect ratio of the original pixmap is preserved with
 | |
|   \ref setBackgroundScaledMode.
 | |
|   
 | |
|   Note that the scaled version of the original pixmap is buffered, so there is no performance
 | |
|   penalty on replots. (Except when the viewport dimensions are changed continuously.)
 | |
|   
 | |
|   \see setBackground, setBackgroundScaledMode
 | |
| */
 | |
| void QCustomPlot::setBackgroundScaled(bool scaled)
 | |
| {
 | |
|   mBackgroundScaled = scaled;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   If scaling of the viewport background pixmap is enabled (\ref setBackgroundScaled), use this
 | |
|   function to define whether and how the aspect ratio of the original pixmap is preserved.
 | |
|   
 | |
|   \see setBackground, setBackgroundScaled
 | |
| */
 | |
| void QCustomPlot::setBackgroundScaledMode(Qt::AspectRatioMode mode)
 | |
| {
 | |
|   mBackgroundScaledMode = mode;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the plottable with \a index. If the index is invalid, returns 0.
 | |
|   
 | |
|   There is an overloaded version of this function with no parameter which returns the last added
 | |
|   plottable, see QCustomPlot::plottable()
 | |
|   
 | |
|   \see plottableCount
 | |
| */
 | |
| QCPAbstractPlottable *QCustomPlot::plottable(int index)
 | |
| {
 | |
|   if (index >= 0 && index < mPlottables.size())
 | |
|   {
 | |
|     return mPlottables.at(index);
 | |
|   } else
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "index out of bounds:" << index;
 | |
|     return 0;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Returns the last plottable that was added to the plot. If there are no plottables in the plot,
 | |
|   returns 0.
 | |
|   
 | |
|   \see plottableCount
 | |
| */
 | |
| QCPAbstractPlottable *QCustomPlot::plottable()
 | |
| {
 | |
|   if (!mPlottables.isEmpty())
 | |
|   {
 | |
|     return mPlottables.last();
 | |
|   } else
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Removes the specified plottable from the plot and deletes it. If necessary, the corresponding
 | |
|   legend item is also removed from the default legend (QCustomPlot::legend).
 | |
|   
 | |
|   Returns true on success.
 | |
|   
 | |
|   \see clearPlottables
 | |
| */
 | |
| bool QCustomPlot::removePlottable(QCPAbstractPlottable *plottable)
 | |
| {
 | |
|   if (!mPlottables.contains(plottable))
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "plottable not in list:" << reinterpret_cast<quintptr>(plottable);
 | |
|     return false;
 | |
|   }
 | |
|   
 | |
|   // remove plottable from legend:
 | |
|   plottable->removeFromLegend();
 | |
|   // special handling for QCPGraphs to maintain the simple graph interface:
 | |
|   if (QCPGraph *graph = qobject_cast<QCPGraph*>(plottable))
 | |
|     mGraphs.removeOne(graph);
 | |
|   // remove plottable:
 | |
|   delete plottable;
 | |
|   mPlottables.removeOne(plottable);
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Removes and deletes the plottable by its \a index.
 | |
| */
 | |
| bool QCustomPlot::removePlottable(int index)
 | |
| {
 | |
|   if (index >= 0 && index < mPlottables.size())
 | |
|     return removePlottable(mPlottables[index]);
 | |
|   else
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "index out of bounds:" << index;
 | |
|     return false;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Removes all plottables from the plot and deletes them. Corresponding legend items are also
 | |
|   removed from the default legend (QCustomPlot::legend).
 | |
|   
 | |
|   Returns the number of plottables removed.
 | |
|   
 | |
|   \see removePlottable
 | |
| */
 | |
| int QCustomPlot::clearPlottables()
 | |
| {
 | |
|   int c = mPlottables.size();
 | |
|   for (int i=c-1; i >= 0; --i)
 | |
|     removePlottable(mPlottables[i]);
 | |
|   return c;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the number of currently existing plottables in the plot
 | |
|   
 | |
|   \see plottable
 | |
| */
 | |
| int QCustomPlot::plottableCount() const
 | |
| {
 | |
|   return mPlottables.size();
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns a list of the selected plottables. If no plottables are currently selected, the list is empty.
 | |
|   
 | |
|   There is a convenience function if you're only interested in selected graphs, see \ref selectedGraphs.
 | |
|   
 | |
|   \see setInteractions, QCPAbstractPlottable::setSelectable, QCPAbstractPlottable::setSelection
 | |
| */
 | |
| QList<QCPAbstractPlottable*> QCustomPlot::selectedPlottables() const
 | |
| {
 | |
|   QList<QCPAbstractPlottable*> result;
 | |
|   foreach (QCPAbstractPlottable *plottable, mPlottables)
 | |
|   {
 | |
|     if (plottable->selected())
 | |
|       result.append(plottable);
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the plottable at the pixel position \a pos. Plottables that only consist of single lines
 | |
|   (like graphs) have a tolerance band around them, see \ref setSelectionTolerance. If multiple
 | |
|   plottables come into consideration, the one closest to \a pos is returned.
 | |
|   
 | |
|   If \a onlySelectable is true, only plottables that are selectable
 | |
|   (QCPAbstractPlottable::setSelectable) are considered.
 | |
|   
 | |
|   If there is no plottable at \a pos, the return value is 0.
 | |
|   
 | |
|   \see itemAt, layoutElementAt
 | |
| */
 | |
| QCPAbstractPlottable *QCustomPlot::plottableAt(const QPointF &pos, bool onlySelectable) const
 | |
| {
 | |
|   QCPAbstractPlottable *resultPlottable = 0;
 | |
|   double resultDistance = mSelectionTolerance; // only regard clicks with distances smaller than mSelectionTolerance as selections, so initialize with that value
 | |
|   
 | |
|   foreach (QCPAbstractPlottable *plottable, mPlottables)
 | |
|   {
 | |
|     if (onlySelectable && !plottable->selectable()) // we could have also passed onlySelectable to the selectTest function, but checking here is faster, because we have access to QCPabstractPlottable::selectable
 | |
|       continue;
 | |
|     if ((plottable->keyAxis()->axisRect()->rect() & plottable->valueAxis()->axisRect()->rect()).contains(pos.toPoint())) // only consider clicks inside the rect that is spanned by the plottable's key/value axes
 | |
|     {
 | |
|       double currentDistance = plottable->selectTest(pos, false);
 | |
|       if (currentDistance >= 0 && currentDistance < resultDistance)
 | |
|       {
 | |
|         resultPlottable = plottable;
 | |
|         resultDistance = currentDistance;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   return resultPlottable;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns whether this QCustomPlot instance contains the \a plottable.
 | |
| */
 | |
| bool QCustomPlot::hasPlottable(QCPAbstractPlottable *plottable) const
 | |
| {
 | |
|   return mPlottables.contains(plottable);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the graph with \a index. If the index is invalid, returns 0.
 | |
|   
 | |
|   There is an overloaded version of this function with no parameter which returns the last created
 | |
|   graph, see QCustomPlot::graph()
 | |
|   
 | |
|   \see graphCount, addGraph
 | |
| */
 | |
| QCPGraph *QCustomPlot::graph(int index) const
 | |
| {
 | |
|   if (index >= 0 && index < mGraphs.size())
 | |
|   {
 | |
|     return mGraphs.at(index);
 | |
|   } else
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "index out of bounds:" << index;
 | |
|     return 0;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Returns the last graph, that was created with \ref addGraph. If there are no graphs in the plot,
 | |
|   returns 0.
 | |
|   
 | |
|   \see graphCount, addGraph
 | |
| */
 | |
| QCPGraph *QCustomPlot::graph() const
 | |
| {
 | |
|   if (!mGraphs.isEmpty())
 | |
|   {
 | |
|     return mGraphs.last();
 | |
|   } else
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Creates a new graph inside the plot. If \a keyAxis and \a valueAxis are left unspecified (0), the
 | |
|   bottom (xAxis) is used as key and the left (yAxis) is used as value axis. If specified, \a
 | |
|   keyAxis and \a valueAxis must reside in this QCustomPlot.
 | |
|   
 | |
|   \a keyAxis will be used as key axis (typically "x") and \a valueAxis as value axis (typically
 | |
|   "y") for the graph.
 | |
|   
 | |
|   Returns a pointer to the newly created graph, or 0 if adding the graph failed.
 | |
|   
 | |
|   \see graph, graphCount, removeGraph, clearGraphs
 | |
| */
 | |
| QCPGraph *QCustomPlot::addGraph(QCPAxis *keyAxis, QCPAxis *valueAxis)
 | |
| {
 | |
|   if (!keyAxis) keyAxis = xAxis;
 | |
|   if (!valueAxis) valueAxis = yAxis;
 | |
|   if (!keyAxis || !valueAxis)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "can't use default QCustomPlot xAxis or yAxis, because at least one is invalid (has been deleted)";
 | |
|     return 0;
 | |
|   }
 | |
|   if (keyAxis->parentPlot() != this || valueAxis->parentPlot() != this)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "passed keyAxis or valueAxis doesn't have this QCustomPlot as parent";
 | |
|     return 0;
 | |
|   }
 | |
|   
 | |
|   QCPGraph *newGraph = new QCPGraph(keyAxis, valueAxis);
 | |
|   newGraph->setName(QLatin1String("Graph ")+QString::number(mGraphs.size()));
 | |
|   return newGraph;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Removes the specified \a graph from the plot and deletes it. If necessary, the corresponding
 | |
|   legend item is also removed from the default legend (QCustomPlot::legend). If any other graphs in
 | |
|   the plot have a channel fill set towards the removed graph, the channel fill property of those
 | |
|   graphs is reset to zero (no channel fill).
 | |
|   
 | |
|   Returns true on success.
 | |
|   
 | |
|   \see clearGraphs
 | |
| */
 | |
| bool QCustomPlot::removeGraph(QCPGraph *graph)
 | |
| {
 | |
|   return removePlottable(graph);
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Removes and deletes the graph by its \a index.
 | |
| */
 | |
| bool QCustomPlot::removeGraph(int index)
 | |
| {
 | |
|   if (index >= 0 && index < mGraphs.size())
 | |
|     return removeGraph(mGraphs[index]);
 | |
|   else
 | |
|     return false;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Removes all graphs from the plot and deletes them. Corresponding legend items are also removed
 | |
|   from the default legend (QCustomPlot::legend).
 | |
| 
 | |
|   Returns the number of graphs removed.
 | |
|   
 | |
|   \see removeGraph
 | |
| */
 | |
| int QCustomPlot::clearGraphs()
 | |
| {
 | |
|   int c = mGraphs.size();
 | |
|   for (int i=c-1; i >= 0; --i)
 | |
|     removeGraph(mGraphs[i]);
 | |
|   return c;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the number of currently existing graphs in the plot
 | |
|   
 | |
|   \see graph, addGraph
 | |
| */
 | |
| int QCustomPlot::graphCount() const
 | |
| {
 | |
|   return mGraphs.size();
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns a list of the selected graphs. If no graphs are currently selected, the list is empty.
 | |
|   
 | |
|   If you are not only interested in selected graphs but other plottables like QCPCurve, QCPBars,
 | |
|   etc., use \ref selectedPlottables.
 | |
|   
 | |
|   \see setInteractions, selectedPlottables, QCPAbstractPlottable::setSelectable, QCPAbstractPlottable::setSelection
 | |
| */
 | |
| QList<QCPGraph*> QCustomPlot::selectedGraphs() const
 | |
| {
 | |
|   QList<QCPGraph*> result;
 | |
|   foreach (QCPGraph *graph, mGraphs)
 | |
|   {
 | |
|     if (graph->selected())
 | |
|       result.append(graph);
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the item with \a index. If the index is invalid, returns 0.
 | |
|   
 | |
|   There is an overloaded version of this function with no parameter which returns the last added
 | |
|   item, see QCustomPlot::item()
 | |
|   
 | |
|   \see itemCount
 | |
| */
 | |
| QCPAbstractItem *QCustomPlot::item(int index) const
 | |
| {
 | |
|   if (index >= 0 && index < mItems.size())
 | |
|   {
 | |
|     return mItems.at(index);
 | |
|   } else
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "index out of bounds:" << index;
 | |
|     return 0;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Returns the last item that was added to this plot. If there are no items in the plot,
 | |
|   returns 0.
 | |
|   
 | |
|   \see itemCount
 | |
| */
 | |
| QCPAbstractItem *QCustomPlot::item() const
 | |
| {
 | |
|   if (!mItems.isEmpty())
 | |
|   {
 | |
|     return mItems.last();
 | |
|   } else
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Removes the specified item from the plot and deletes it.
 | |
|   
 | |
|   Returns true on success.
 | |
|   
 | |
|   \see clearItems
 | |
| */
 | |
| bool QCustomPlot::removeItem(QCPAbstractItem *item)
 | |
| {
 | |
|   if (mItems.contains(item))
 | |
|   {
 | |
|     delete item;
 | |
|     mItems.removeOne(item);
 | |
|     return true;
 | |
|   } else
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "item not in list:" << reinterpret_cast<quintptr>(item);
 | |
|     return false;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Removes and deletes the item by its \a index.
 | |
| */
 | |
| bool QCustomPlot::removeItem(int index)
 | |
| {
 | |
|   if (index >= 0 && index < mItems.size())
 | |
|     return removeItem(mItems[index]);
 | |
|   else
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "index out of bounds:" << index;
 | |
|     return false;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Removes all items from the plot and deletes them.
 | |
|   
 | |
|   Returns the number of items removed.
 | |
|   
 | |
|   \see removeItem
 | |
| */
 | |
| int QCustomPlot::clearItems()
 | |
| {
 | |
|   int c = mItems.size();
 | |
|   for (int i=c-1; i >= 0; --i)
 | |
|     removeItem(mItems[i]);
 | |
|   return c;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the number of currently existing items in the plot
 | |
|   
 | |
|   \see item
 | |
| */
 | |
| int QCustomPlot::itemCount() const
 | |
| {
 | |
|   return mItems.size();
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns a list of the selected items. If no items are currently selected, the list is empty.
 | |
|   
 | |
|   \see setInteractions, QCPAbstractItem::setSelectable, QCPAbstractItem::setSelected
 | |
| */
 | |
| QList<QCPAbstractItem*> QCustomPlot::selectedItems() const
 | |
| {
 | |
|   QList<QCPAbstractItem*> result;
 | |
|   foreach (QCPAbstractItem *item, mItems)
 | |
|   {
 | |
|     if (item->selected())
 | |
|       result.append(item);
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the item at the pixel position \a pos. Items that only consist of single lines (e.g. \ref
 | |
|   QCPItemLine or \ref QCPItemCurve) have a tolerance band around them, see \ref
 | |
|   setSelectionTolerance. If multiple items come into consideration, the one closest to \a pos is
 | |
|   returned.
 | |
|   
 | |
|   If \a onlySelectable is true, only items that are selectable (QCPAbstractItem::setSelectable) are
 | |
|   considered.
 | |
|   
 | |
|   If there is no item at \a pos, the return value is 0.
 | |
|   
 | |
|   \see plottableAt, layoutElementAt
 | |
| */
 | |
| QCPAbstractItem *QCustomPlot::itemAt(const QPointF &pos, bool onlySelectable) const
 | |
| {
 | |
|   QCPAbstractItem *resultItem = 0;
 | |
|   double resultDistance = mSelectionTolerance; // only regard clicks with distances smaller than mSelectionTolerance as selections, so initialize with that value
 | |
|   
 | |
|   foreach (QCPAbstractItem *item, mItems)
 | |
|   {
 | |
|     if (onlySelectable && !item->selectable()) // we could have also passed onlySelectable to the selectTest function, but checking here is faster, because we have access to QCPAbstractItem::selectable
 | |
|       continue;
 | |
|     if (!item->clipToAxisRect() || item->clipRect().contains(pos.toPoint())) // only consider clicks inside axis cliprect of the item if actually clipped to it
 | |
|     {
 | |
|       double currentDistance = item->selectTest(pos, false);
 | |
|       if (currentDistance >= 0 && currentDistance < resultDistance)
 | |
|       {
 | |
|         resultItem = item;
 | |
|         resultDistance = currentDistance;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   return resultItem;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns whether this QCustomPlot contains the \a item.
 | |
|   
 | |
|   \see item
 | |
| */
 | |
| bool QCustomPlot::hasItem(QCPAbstractItem *item) const
 | |
| {
 | |
|   return mItems.contains(item);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the layer with the specified \a name. If there is no layer with the specified name, 0 is
 | |
|   returned.
 | |
|   
 | |
|   Layer names are case-sensitive.
 | |
|   
 | |
|   \see addLayer, moveLayer, removeLayer
 | |
| */
 | |
| QCPLayer *QCustomPlot::layer(const QString &name) const
 | |
| {
 | |
|   foreach (QCPLayer *layer, mLayers)
 | |
|   {
 | |
|     if (layer->name() == name)
 | |
|       return layer;
 | |
|   }
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Returns the layer by \a index. If the index is invalid, 0 is returned.
 | |
|   
 | |
|   \see addLayer, moveLayer, removeLayer
 | |
| */
 | |
| QCPLayer *QCustomPlot::layer(int index) const
 | |
| {
 | |
|   if (index >= 0 && index < mLayers.size())
 | |
|   {
 | |
|     return mLayers.at(index);
 | |
|   } else
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "index out of bounds:" << index;
 | |
|     return 0;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the layer that is set as current layer (see \ref setCurrentLayer).
 | |
| */
 | |
| QCPLayer *QCustomPlot::currentLayer() const
 | |
| {
 | |
|   return mCurrentLayer;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the layer with the specified \a name to be the current layer. All layerables (\ref
 | |
|   QCPLayerable), e.g. plottables and items, are created on the current layer.
 | |
|   
 | |
|   Returns true on success, i.e. if there is a layer with the specified \a name in the QCustomPlot.
 | |
|   
 | |
|   Layer names are case-sensitive.
 | |
|   
 | |
|   \see addLayer, moveLayer, removeLayer, QCPLayerable::setLayer
 | |
| */
 | |
| bool QCustomPlot::setCurrentLayer(const QString &name)
 | |
| {
 | |
|   if (QCPLayer *newCurrentLayer = layer(name))
 | |
|   {
 | |
|     return setCurrentLayer(newCurrentLayer);
 | |
|   } else
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "layer with name doesn't exist:" << name;
 | |
|     return false;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Sets the provided \a layer to be the current layer.
 | |
|   
 | |
|   Returns true on success, i.e. when \a layer is a valid layer in the QCustomPlot.
 | |
|   
 | |
|   \see addLayer, moveLayer, removeLayer
 | |
| */
 | |
| bool QCustomPlot::setCurrentLayer(QCPLayer *layer)
 | |
| {
 | |
|   if (!mLayers.contains(layer))
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "layer not a layer of this QCustomPlot:" << reinterpret_cast<quintptr>(layer);
 | |
|     return false;
 | |
|   }
 | |
|   
 | |
|   mCurrentLayer = layer;
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the number of currently existing layers in the plot
 | |
|   
 | |
|   \see layer, addLayer
 | |
| */
 | |
| int QCustomPlot::layerCount() const
 | |
| {
 | |
|   return mLayers.size();
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Adds a new layer to this QCustomPlot instance. The new layer will have the name \a name, which
 | |
|   must be unique. Depending on \a insertMode, it is positioned either below or above \a otherLayer.
 | |
|   
 | |
|   Returns true on success, i.e. if there is no other layer named \a name and \a otherLayer is a
 | |
|   valid layer inside this QCustomPlot.
 | |
|   
 | |
|   If \a otherLayer is 0, the highest layer in the QCustomPlot will be used.
 | |
|   
 | |
|   For an explanation of what layers are in QCustomPlot, see the documentation of \ref QCPLayer.
 | |
|   
 | |
|   \see layer, moveLayer, removeLayer
 | |
| */
 | |
| bool QCustomPlot::addLayer(const QString &name, QCPLayer *otherLayer, QCustomPlot::LayerInsertMode insertMode)
 | |
| {
 | |
|   if (!otherLayer)
 | |
|     otherLayer = mLayers.last();
 | |
|   if (!mLayers.contains(otherLayer))
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "otherLayer not a layer of this QCustomPlot:" << reinterpret_cast<quintptr>(otherLayer);
 | |
|     return false;
 | |
|   }
 | |
|   if (layer(name))
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "A layer exists already with the name" << name;
 | |
|     return false;
 | |
|   }
 | |
|     
 | |
|   QCPLayer *newLayer = new QCPLayer(this, name);
 | |
|   mLayers.insert(otherLayer->index() + (insertMode==limAbove ? 1:0), newLayer);
 | |
|   updateLayerIndices();
 | |
|   setupPaintBuffers(); // associates new layer with the appropriate paint buffer
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Removes the specified \a layer and returns true on success.
 | |
|   
 | |
|   All layerables (e.g. plottables and items) on the removed layer will be moved to the layer below
 | |
|   \a layer. If \a layer is the bottom layer, the layerables are moved to the layer above. In both
 | |
|   cases, the total rendering order of all layerables in the QCustomPlot is preserved.
 | |
|   
 | |
|   If \a layer is the current layer (\ref setCurrentLayer), the layer below (or above, if bottom
 | |
|   layer) becomes the new current layer.
 | |
|   
 | |
|   It is not possible to remove the last layer of the plot.
 | |
|   
 | |
|   \see layer, addLayer, moveLayer
 | |
| */
 | |
| bool QCustomPlot::removeLayer(QCPLayer *layer)
 | |
| {
 | |
|   if (!mLayers.contains(layer))
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "layer not a layer of this QCustomPlot:" << reinterpret_cast<quintptr>(layer);
 | |
|     return false;
 | |
|   }
 | |
|   if (mLayers.size() < 2)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "can't remove last layer";
 | |
|     return false;
 | |
|   }
 | |
|   
 | |
|   // append all children of this layer to layer below (if this is lowest layer, prepend to layer above)
 | |
|   int removedIndex = layer->index();
 | |
|   bool isFirstLayer = removedIndex==0;
 | |
|   QCPLayer *targetLayer = isFirstLayer ? mLayers.at(removedIndex+1) : mLayers.at(removedIndex-1);
 | |
|   QList<QCPLayerable*> children = layer->children();
 | |
|   if (isFirstLayer) // prepend in reverse order (so order relative to each other stays the same)
 | |
|   {
 | |
|     for (int i=children.size()-1; i>=0; --i)
 | |
|       children.at(i)->moveToLayer(targetLayer, true);
 | |
|   } else  // append normally
 | |
|   {
 | |
|     for (int i=0; i<children.size(); ++i)
 | |
|       children.at(i)->moveToLayer(targetLayer, false);
 | |
|   }
 | |
|   // if removed layer is current layer, change current layer to layer below/above:
 | |
|   if (layer == mCurrentLayer)
 | |
|     setCurrentLayer(targetLayer);
 | |
|   // invalidate the paint buffer that was responsible for this layer:
 | |
|   if (!layer->mPaintBuffer.isNull())
 | |
|     layer->mPaintBuffer.data()->setInvalidated();
 | |
|   // remove layer:
 | |
|   delete layer;
 | |
|   mLayers.removeOne(layer);
 | |
|   updateLayerIndices();
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Moves the specified \a layer either above or below \a otherLayer. Whether it's placed above or
 | |
|   below is controlled with \a insertMode.
 | |
|   
 | |
|   Returns true on success, i.e. when both \a layer and \a otherLayer are valid layers in the
 | |
|   QCustomPlot.
 | |
|   
 | |
|   \see layer, addLayer, moveLayer
 | |
| */
 | |
| bool QCustomPlot::moveLayer(QCPLayer *layer, QCPLayer *otherLayer, QCustomPlot::LayerInsertMode insertMode)
 | |
| {
 | |
|   if (!mLayers.contains(layer))
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "layer not a layer of this QCustomPlot:" << reinterpret_cast<quintptr>(layer);
 | |
|     return false;
 | |
|   }
 | |
|   if (!mLayers.contains(otherLayer))
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "otherLayer not a layer of this QCustomPlot:" << reinterpret_cast<quintptr>(otherLayer);
 | |
|     return false;
 | |
|   }
 | |
|   
 | |
|   if (layer->index() > otherLayer->index())
 | |
|     mLayers.move(layer->index(), otherLayer->index() + (insertMode==limAbove ? 1:0));
 | |
|   else if (layer->index() < otherLayer->index())
 | |
|     mLayers.move(layer->index(), otherLayer->index() + (insertMode==limAbove ? 0:-1));
 | |
|   
 | |
|   // invalidate the paint buffers that are responsible for the layers:
 | |
|   if (!layer->mPaintBuffer.isNull())
 | |
|     layer->mPaintBuffer.data()->setInvalidated();
 | |
|   if (!otherLayer->mPaintBuffer.isNull())
 | |
|     otherLayer->mPaintBuffer.data()->setInvalidated();
 | |
|   
 | |
|   updateLayerIndices();
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the number of axis rects in the plot.
 | |
|   
 | |
|   All axis rects can be accessed via QCustomPlot::axisRect().
 | |
|   
 | |
|   Initially, only one axis rect exists in the plot.
 | |
|   
 | |
|   \see axisRect, axisRects
 | |
| */
 | |
| int QCustomPlot::axisRectCount() const
 | |
| {
 | |
|   return axisRects().size();
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the axis rect with \a index.
 | |
|   
 | |
|   Initially, only one axis rect (with index 0) exists in the plot. If multiple axis rects were
 | |
|   added, all of them may be accessed with this function in a linear fashion (even when they are
 | |
|   nested in a layout hierarchy or inside other axis rects via QCPAxisRect::insetLayout).
 | |
|   
 | |
|   \see axisRectCount, axisRects
 | |
| */
 | |
| QCPAxisRect *QCustomPlot::axisRect(int index) const
 | |
| {
 | |
|   const QList<QCPAxisRect*> rectList = axisRects();
 | |
|   if (index >= 0 && index < rectList.size())
 | |
|   {
 | |
|     return rectList.at(index);
 | |
|   } else
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "invalid axis rect index" << index;
 | |
|     return 0;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns all axis rects in the plot.
 | |
|   
 | |
|   \see axisRectCount, axisRect
 | |
| */
 | |
| QList<QCPAxisRect*> QCustomPlot::axisRects() const
 | |
| {
 | |
|   QList<QCPAxisRect*> result;
 | |
|   QStack<QCPLayoutElement*> elementStack;
 | |
|   if (mPlotLayout)
 | |
|     elementStack.push(mPlotLayout);
 | |
|   
 | |
|   while (!elementStack.isEmpty())
 | |
|   {
 | |
|     foreach (QCPLayoutElement *element, elementStack.pop()->elements(false))
 | |
|     {
 | |
|       if (element)
 | |
|       {
 | |
|         elementStack.push(element);
 | |
|         if (QCPAxisRect *ar = qobject_cast<QCPAxisRect*>(element))
 | |
|           result.append(ar);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the layout element at pixel position \a pos. If there is no element at that position,
 | |
|   returns 0.
 | |
|   
 | |
|   Only visible elements are used. If \ref QCPLayoutElement::setVisible on the element itself or on
 | |
|   any of its parent elements is set to false, it will not be considered.
 | |
|   
 | |
|   \see itemAt, plottableAt
 | |
| */
 | |
| QCPLayoutElement *QCustomPlot::layoutElementAt(const QPointF &pos) const
 | |
| {
 | |
|   QCPLayoutElement *currentElement = mPlotLayout;
 | |
|   bool searchSubElements = true;
 | |
|   while (searchSubElements && currentElement)
 | |
|   {
 | |
|     searchSubElements = false;
 | |
|     foreach (QCPLayoutElement *subElement, currentElement->elements(false))
 | |
|     {
 | |
|       if (subElement && subElement->realVisibility() && subElement->selectTest(pos, false) >= 0)
 | |
|       {
 | |
|         currentElement = subElement;
 | |
|         searchSubElements = true;
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   return currentElement;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the layout element of type \ref QCPAxisRect at pixel position \a pos. This method ignores
 | |
|   other layout elements even if they are visually in front of the axis rect (e.g. a \ref
 | |
|   QCPLegend). If there is no axis rect at that position, returns 0.
 | |
| 
 | |
|   Only visible axis rects are used. If \ref QCPLayoutElement::setVisible on the axis rect itself or
 | |
|   on any of its parent elements is set to false, it will not be considered.
 | |
| 
 | |
|   \see layoutElementAt
 | |
| */
 | |
| QCPAxisRect *QCustomPlot::axisRectAt(const QPointF &pos) const
 | |
| {
 | |
|   QCPAxisRect *result = 0;
 | |
|   QCPLayoutElement *currentElement = mPlotLayout;
 | |
|   bool searchSubElements = true;
 | |
|   while (searchSubElements && currentElement)
 | |
|   {
 | |
|     searchSubElements = false;
 | |
|     foreach (QCPLayoutElement *subElement, currentElement->elements(false))
 | |
|     {
 | |
|       if (subElement && subElement->realVisibility() && subElement->selectTest(pos, false) >= 0)
 | |
|       {
 | |
|         currentElement = subElement;
 | |
|         searchSubElements = true;
 | |
|         if (QCPAxisRect *ar = qobject_cast<QCPAxisRect*>(currentElement))
 | |
|           result = ar;
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the axes that currently have selected parts, i.e. whose selection state is not \ref
 | |
|   QCPAxis::spNone.
 | |
|   
 | |
|   \see selectedPlottables, selectedLegends, setInteractions, QCPAxis::setSelectedParts,
 | |
|   QCPAxis::setSelectableParts
 | |
| */
 | |
| QList<QCPAxis*> QCustomPlot::selectedAxes() const
 | |
| {
 | |
|   QList<QCPAxis*> result, allAxes;
 | |
|   foreach (QCPAxisRect *rect, axisRects())
 | |
|     allAxes << rect->axes();
 | |
|   
 | |
|   foreach (QCPAxis *axis, allAxes)
 | |
|   {
 | |
|     if (axis->selectedParts() != QCPAxis::spNone)
 | |
|       result.append(axis);
 | |
|   }
 | |
|   
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the legends that currently have selected parts, i.e. whose selection state is not \ref
 | |
|   QCPLegend::spNone.
 | |
|   
 | |
|   \see selectedPlottables, selectedAxes, setInteractions, QCPLegend::setSelectedParts,
 | |
|   QCPLegend::setSelectableParts, QCPLegend::selectedItems
 | |
| */
 | |
| QList<QCPLegend*> QCustomPlot::selectedLegends() const
 | |
| {
 | |
|   QList<QCPLegend*> result;
 | |
|   
 | |
|   QStack<QCPLayoutElement*> elementStack;
 | |
|   if (mPlotLayout)
 | |
|     elementStack.push(mPlotLayout);
 | |
|   
 | |
|   while (!elementStack.isEmpty())
 | |
|   {
 | |
|     foreach (QCPLayoutElement *subElement, elementStack.pop()->elements(false))
 | |
|     {
 | |
|       if (subElement)
 | |
|       {
 | |
|         elementStack.push(subElement);
 | |
|         if (QCPLegend *leg = qobject_cast<QCPLegend*>(subElement))
 | |
|         {
 | |
|           if (leg->selectedParts() != QCPLegend::spNone)
 | |
|             result.append(leg);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Deselects all layerables (plottables, items, axes, legends,...) of the QCustomPlot.
 | |
|   
 | |
|   Since calling this function is not a user interaction, this does not emit the \ref
 | |
|   selectionChangedByUser signal. The individual selectionChanged signals are emitted though, if the
 | |
|   objects were previously selected.
 | |
|   
 | |
|   \see setInteractions, selectedPlottables, selectedItems, selectedAxes, selectedLegends
 | |
| */
 | |
| void QCustomPlot::deselectAll()
 | |
| {
 | |
|   foreach (QCPLayer *layer, mLayers)
 | |
|   {
 | |
|     foreach (QCPLayerable *layerable, layer->children())
 | |
|       layerable->deselectEvent(0);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Causes a complete replot into the internal paint buffer(s). Finally, the widget surface is
 | |
|   refreshed with the new buffer contents. This is the method that must be called to make changes to
 | |
|   the plot, e.g. on the axis ranges or data points of graphs, visible.
 | |
| 
 | |
|   The parameter \a refreshPriority can be used to fine-tune the timing of the replot. For example
 | |
|   if your application calls \ref replot very quickly in succession (e.g. multiple independent
 | |
|   functions change some aspects of the plot and each wants to make sure the change gets replotted),
 | |
|   it is advisable to set \a refreshPriority to \ref QCustomPlot::rpQueuedReplot. This way, the
 | |
|   actual replotting is deferred to the next event loop iteration. Multiple successive calls of \ref
 | |
|   replot with this priority will only cause a single replot, avoiding redundant replots and
 | |
|   improving performance.
 | |
| 
 | |
|   Under a few circumstances, QCustomPlot causes a replot by itself. Those are resize events of the
 | |
|   QCustomPlot widget and user interactions (object selection and range dragging/zooming).
 | |
| 
 | |
|   Before the replot happens, the signal \ref beforeReplot is emitted. After the replot, \ref
 | |
|   afterReplot is emitted. It is safe to mutually connect the replot slot with any of those two
 | |
|   signals on two QCustomPlots to make them replot synchronously, it won't cause an infinite
 | |
|   recursion.
 | |
| 
 | |
|   If a layer is in mode \ref QCPLayer::lmBuffered (\ref QCPLayer::setMode), it is also possible to
 | |
|   replot only that specific layer via \ref QCPLayer::replot. See the documentation there for
 | |
|   details.
 | |
| */
 | |
| void QCustomPlot::replot(QCustomPlot::RefreshPriority refreshPriority)
 | |
| {
 | |
|   if (refreshPriority == QCustomPlot::rpQueuedReplot)
 | |
|   {
 | |
|     if (!mReplotQueued)
 | |
|     {
 | |
|       mReplotQueued = true;
 | |
|       QTimer::singleShot(0, this, SLOT(replot()));
 | |
|     }
 | |
|     return;
 | |
|   }
 | |
|   
 | |
|   if (mReplotting) // incase signals loop back to replot slot
 | |
|     return;
 | |
|   mReplotting = true;
 | |
|   mReplotQueued = false;
 | |
|   emit beforeReplot();
 | |
|   
 | |
|   updateLayout();
 | |
|   // draw all layered objects (grid, axes, plottables, items, legend,...) into their buffers:
 | |
|   setupPaintBuffers();
 | |
|   foreach (QCPLayer *layer, mLayers)
 | |
|     layer->drawToPaintBuffer();
 | |
|   for (int i=0; i<mPaintBuffers.size(); ++i)
 | |
|     mPaintBuffers.at(i)->setInvalidated(false);
 | |
|   
 | |
|   if ((refreshPriority == rpRefreshHint && mPlottingHints.testFlag(QCP::phImmediateRefresh)) || refreshPriority==rpImmediateRefresh)
 | |
|     repaint();
 | |
|   else
 | |
|     update();
 | |
|   
 | |
|   emit afterReplot();
 | |
|   mReplotting = false;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Rescales the axes such that all plottables (like graphs) in the plot are fully visible.
 | |
|   
 | |
|   if \a onlyVisiblePlottables is set to true, only the plottables that have their visibility set to true
 | |
|   (QCPLayerable::setVisible), will be used to rescale the axes.
 | |
|   
 | |
|   \see QCPAbstractPlottable::rescaleAxes, QCPAxis::rescale
 | |
| */
 | |
| void QCustomPlot::rescaleAxes(bool onlyVisiblePlottables)
 | |
| {
 | |
|   QList<QCPAxis*> allAxes;
 | |
|   foreach (QCPAxisRect *rect, axisRects())
 | |
|     allAxes << rect->axes();
 | |
|   
 | |
|   foreach (QCPAxis *axis, allAxes)
 | |
|     axis->rescale(onlyVisiblePlottables);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Saves a PDF with the vectorized plot to the file \a fileName. The axis ratio as well as the scale
 | |
|   of texts and lines will be derived from the specified \a width and \a height. This means, the
 | |
|   output will look like the normal on-screen output of a QCustomPlot widget with the corresponding
 | |
|   pixel width and height. If either \a width or \a height is zero, the exported image will have the
 | |
|   same dimensions as the QCustomPlot widget currently has.
 | |
| 
 | |
|   Setting \a exportPen to \ref QCP::epNoCosmetic allows to disable the use of cosmetic pens when
 | |
|   drawing to the PDF file. Cosmetic pens are pens with numerical width 0, which are always drawn as
 | |
|   a one pixel wide line, no matter what zoom factor is set in the PDF-Viewer. For more information
 | |
|   about cosmetic pens, see the QPainter and QPen documentation.
 | |
| 
 | |
|   The objects of the plot will appear in the current selection state. If you don't want any
 | |
|   selected objects to be painted in their selected look, deselect everything with \ref deselectAll
 | |
|   before calling this function.
 | |
| 
 | |
|   Returns true on success.
 | |
| 
 | |
|   \warning
 | |
|   \li If you plan on editing the exported PDF file with a vector graphics editor like Inkscape, it
 | |
|   is advised to set \a exportPen to \ref QCP::epNoCosmetic to avoid losing those cosmetic lines
 | |
|   (which might be quite many, because cosmetic pens are the default for e.g. axes and tick marks).
 | |
|   \li If calling this function inside the constructor of the parent of the QCustomPlot widget
 | |
|   (i.e. the MainWindow constructor, if QCustomPlot is inside the MainWindow), always provide
 | |
|   explicit non-zero widths and heights. If you leave \a width or \a height as 0 (default), this
 | |
|   function uses the current width and height of the QCustomPlot widget. However, in Qt, these
 | |
|   aren't defined yet inside the constructor, so you would get an image that has strange
 | |
|   widths/heights.
 | |
| 
 | |
|   \a pdfCreator and \a pdfTitle may be used to set the according metadata fields in the resulting
 | |
|   PDF file.
 | |
| 
 | |
|   \note On Android systems, this method does nothing and issues an according qDebug warning
 | |
|   message. This is also the case if for other reasons the define flag \c QT_NO_PRINTER is set.
 | |
| 
 | |
|   \see savePng, saveBmp, saveJpg, saveRastered
 | |
| */
 | |
| bool QCustomPlot::savePdf(const QString &fileName, int width, int height, QCP::ExportPen exportPen, const QString &pdfCreator, const QString &pdfTitle)
 | |
| {
 | |
|   bool success = false;
 | |
| #ifdef QT_NO_PRINTER
 | |
|   Q_UNUSED(fileName)
 | |
|   Q_UNUSED(exportPen)
 | |
|   Q_UNUSED(width)
 | |
|   Q_UNUSED(height)
 | |
|   Q_UNUSED(pdfCreator)
 | |
|   Q_UNUSED(pdfTitle)
 | |
|   qDebug() << Q_FUNC_INFO << "Qt was built without printer support (QT_NO_PRINTER). PDF not created.";
 | |
| #else
 | |
|   int newWidth, newHeight;
 | |
|   if (width == 0 || height == 0)
 | |
|   {
 | |
|     newWidth = this->width();
 | |
|     newHeight = this->height();
 | |
|   } else
 | |
|   {
 | |
|     newWidth = width;
 | |
|     newHeight = height;
 | |
|   }
 | |
|   
 | |
|   QPrinter printer(QPrinter::ScreenResolution);
 | |
|   printer.setOutputFileName(fileName);
 | |
|   printer.setOutputFormat(QPrinter::PdfFormat);
 | |
|   printer.setColorMode(QPrinter::Color);
 | |
|   printer.printEngine()->setProperty(QPrintEngine::PPK_Creator, pdfCreator);
 | |
|   printer.printEngine()->setProperty(QPrintEngine::PPK_DocumentName, pdfTitle);
 | |
|   QRect oldViewport = viewport();
 | |
|   setViewport(QRect(0, 0, newWidth, newHeight));
 | |
| #if QT_VERSION < QT_VERSION_CHECK(5, 3, 0)
 | |
|   printer.setFullPage(true);
 | |
|   printer.setPaperSize(viewport().size(), QPrinter::DevicePixel);
 | |
| #else
 | |
|   QPageLayout pageLayout;
 | |
|   pageLayout.setMode(QPageLayout::FullPageMode);
 | |
|   pageLayout.setOrientation(QPageLayout::Portrait);
 | |
|   pageLayout.setMargins(QMarginsF(0, 0, 0, 0));
 | |
|   pageLayout.setPageSize(QPageSize(viewport().size(), QPageSize::Point, QString(), QPageSize::ExactMatch));
 | |
|   printer.setPageLayout(pageLayout);
 | |
| #endif
 | |
|   QCPPainter printpainter;
 | |
|   if (printpainter.begin(&printer))
 | |
|   {
 | |
|     printpainter.setMode(QCPPainter::pmVectorized);
 | |
|     printpainter.setMode(QCPPainter::pmNoCaching);
 | |
|     printpainter.setMode(QCPPainter::pmNonCosmetic, exportPen==QCP::epNoCosmetic);
 | |
|     printpainter.setWindow(mViewport);
 | |
|     if (mBackgroundBrush.style() != Qt::NoBrush &&
 | |
|         mBackgroundBrush.color() != Qt::white &&
 | |
|         mBackgroundBrush.color() != Qt::transparent &&
 | |
|         mBackgroundBrush.color().alpha() > 0) // draw pdf background color if not white/transparent
 | |
|       printpainter.fillRect(viewport(), mBackgroundBrush);
 | |
|     draw(&printpainter);
 | |
|     printpainter.end();
 | |
|     success = true;
 | |
|   }
 | |
|   setViewport(oldViewport);
 | |
| #endif // QT_NO_PRINTER
 | |
|   return success;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Saves a PNG image file to \a fileName on disc. The output plot will have the dimensions \a width
 | |
|   and \a height in pixels, multiplied by \a scale. If either \a width or \a height is zero, the
 | |
|   current width and height of the QCustomPlot widget is used instead. Line widths and texts etc.
 | |
|   are not scaled up when larger widths/heights are used. If you want that effect, use the \a scale
 | |
|   parameter.
 | |
| 
 | |
|   For example, if you set both \a width and \a height to 100 and \a scale to 2, you will end up with an
 | |
|   image file of size 200*200 in which all graphical elements are scaled up by factor 2 (line widths,
 | |
|   texts, etc.). This scaling is not done by stretching a 100*100 image, the result will have full
 | |
|   200*200 pixel resolution.
 | |
| 
 | |
|   If you use a high scaling factor, it is recommended to enable antialiasing for all elements by
 | |
|   temporarily setting \ref QCustomPlot::setAntialiasedElements to \ref QCP::aeAll as this allows
 | |
|   QCustomPlot to place objects with sub-pixel accuracy.
 | |
| 
 | |
|   image compression can be controlled with the \a quality parameter which must be between 0 and 100
 | |
|   or -1 to use the default setting.
 | |
| 
 | |
|   The \a resolution will be written to the image file header and has no direct consequence for the
 | |
|   quality or the pixel size. However, if opening the image with a tool which respects the metadata,
 | |
|   it will be able to scale the image to match either a given size in real units of length (inch,
 | |
|   centimeters, etc.), or the target display DPI. You can specify in which units \a resolution is
 | |
|   given, by setting \a resolutionUnit. The \a resolution is converted to the format's expected
 | |
|   resolution unit internally.
 | |
| 
 | |
|   Returns true on success. If this function fails, most likely the PNG format isn't supported by
 | |
|   the system, see Qt docs about QImageWriter::supportedImageFormats().
 | |
| 
 | |
|   The objects of the plot will appear in the current selection state. If you don't want any selected
 | |
|   objects to be painted in their selected look, deselect everything with \ref deselectAll before calling
 | |
|   this function.
 | |
| 
 | |
|   If you want the PNG to have a transparent background, call \ref setBackground(const QBrush &brush)
 | |
|   with no brush (Qt::NoBrush) or a transparent color (Qt::transparent), before saving.
 | |
| 
 | |
|   \warning If calling this function inside the constructor of the parent of the QCustomPlot widget
 | |
|   (i.e. the MainWindow constructor, if QCustomPlot is inside the MainWindow), always provide
 | |
|   explicit non-zero widths and heights. If you leave \a width or \a height as 0 (default), this
 | |
|   function uses the current width and height of the QCustomPlot widget. However, in Qt, these
 | |
|   aren't defined yet inside the constructor, so you would get an image that has strange
 | |
|   widths/heights.
 | |
| 
 | |
|   \see savePdf, saveBmp, saveJpg, saveRastered
 | |
| */
 | |
| bool QCustomPlot::savePng(const QString &fileName, int width, int height, double scale, int quality, int resolution, QCP::ResolutionUnit resolutionUnit)
 | |
| {
 | |
|   return saveRastered(fileName, width, height, scale, "PNG", quality, resolution, resolutionUnit);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Saves a JPEG image file to \a fileName on disc. The output plot will have the dimensions \a width
 | |
|   and \a height in pixels, multiplied by \a scale. If either \a width or \a height is zero, the
 | |
|   current width and height of the QCustomPlot widget is used instead. Line widths and texts etc.
 | |
|   are not scaled up when larger widths/heights are used. If you want that effect, use the \a scale
 | |
|   parameter.
 | |
| 
 | |
|   For example, if you set both \a width and \a height to 100 and \a scale to 2, you will end up with an
 | |
|   image file of size 200*200 in which all graphical elements are scaled up by factor 2 (line widths,
 | |
|   texts, etc.). This scaling is not done by stretching a 100*100 image, the result will have full
 | |
|   200*200 pixel resolution.
 | |
| 
 | |
|   If you use a high scaling factor, it is recommended to enable antialiasing for all elements by
 | |
|   temporarily setting \ref QCustomPlot::setAntialiasedElements to \ref QCP::aeAll as this allows
 | |
|   QCustomPlot to place objects with sub-pixel accuracy.
 | |
| 
 | |
|   image compression can be controlled with the \a quality parameter which must be between 0 and 100
 | |
|   or -1 to use the default setting.
 | |
| 
 | |
|   The \a resolution will be written to the image file header and has no direct consequence for the
 | |
|   quality or the pixel size. However, if opening the image with a tool which respects the metadata,
 | |
|   it will be able to scale the image to match either a given size in real units of length (inch,
 | |
|   centimeters, etc.), or the target display DPI. You can specify in which units \a resolution is
 | |
|   given, by setting \a resolutionUnit. The \a resolution is converted to the format's expected
 | |
|   resolution unit internally.
 | |
| 
 | |
|   Returns true on success. If this function fails, most likely the JPEG format isn't supported by
 | |
|   the system, see Qt docs about QImageWriter::supportedImageFormats().
 | |
| 
 | |
|   The objects of the plot will appear in the current selection state. If you don't want any selected
 | |
|   objects to be painted in their selected look, deselect everything with \ref deselectAll before calling
 | |
|   this function.
 | |
| 
 | |
|   \warning If calling this function inside the constructor of the parent of the QCustomPlot widget
 | |
|   (i.e. the MainWindow constructor, if QCustomPlot is inside the MainWindow), always provide
 | |
|   explicit non-zero widths and heights. If you leave \a width or \a height as 0 (default), this
 | |
|   function uses the current width and height of the QCustomPlot widget. However, in Qt, these
 | |
|   aren't defined yet inside the constructor, so you would get an image that has strange
 | |
|   widths/heights.
 | |
| 
 | |
|   \see savePdf, savePng, saveBmp, saveRastered
 | |
| */
 | |
| bool QCustomPlot::saveJpg(const QString &fileName, int width, int height, double scale, int quality, int resolution, QCP::ResolutionUnit resolutionUnit)
 | |
| {
 | |
|   return saveRastered(fileName, width, height, scale, "JPG", quality, resolution, resolutionUnit);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Saves a BMP image file to \a fileName on disc. The output plot will have the dimensions \a width
 | |
|   and \a height in pixels, multiplied by \a scale. If either \a width or \a height is zero, the
 | |
|   current width and height of the QCustomPlot widget is used instead. Line widths and texts etc.
 | |
|   are not scaled up when larger widths/heights are used. If you want that effect, use the \a scale
 | |
|   parameter.
 | |
| 
 | |
|   For example, if you set both \a width and \a height to 100 and \a scale to 2, you will end up with an
 | |
|   image file of size 200*200 in which all graphical elements are scaled up by factor 2 (line widths,
 | |
|   texts, etc.). This scaling is not done by stretching a 100*100 image, the result will have full
 | |
|   200*200 pixel resolution.
 | |
| 
 | |
|   If you use a high scaling factor, it is recommended to enable antialiasing for all elements by
 | |
|   temporarily setting \ref QCustomPlot::setAntialiasedElements to \ref QCP::aeAll as this allows
 | |
|   QCustomPlot to place objects with sub-pixel accuracy.
 | |
| 
 | |
|   The \a resolution will be written to the image file header and has no direct consequence for the
 | |
|   quality or the pixel size. However, if opening the image with a tool which respects the metadata,
 | |
|   it will be able to scale the image to match either a given size in real units of length (inch,
 | |
|   centimeters, etc.), or the target display DPI. You can specify in which units \a resolution is
 | |
|   given, by setting \a resolutionUnit. The \a resolution is converted to the format's expected
 | |
|   resolution unit internally.
 | |
| 
 | |
|   Returns true on success. If this function fails, most likely the BMP format isn't supported by
 | |
|   the system, see Qt docs about QImageWriter::supportedImageFormats().
 | |
| 
 | |
|   The objects of the plot will appear in the current selection state. If you don't want any selected
 | |
|   objects to be painted in their selected look, deselect everything with \ref deselectAll before calling
 | |
|   this function.
 | |
| 
 | |
|   \warning If calling this function inside the constructor of the parent of the QCustomPlot widget
 | |
|   (i.e. the MainWindow constructor, if QCustomPlot is inside the MainWindow), always provide
 | |
|   explicit non-zero widths and heights. If you leave \a width or \a height as 0 (default), this
 | |
|   function uses the current width and height of the QCustomPlot widget. However, in Qt, these
 | |
|   aren't defined yet inside the constructor, so you would get an image that has strange
 | |
|   widths/heights.
 | |
| 
 | |
|   \see savePdf, savePng, saveJpg, saveRastered
 | |
| */
 | |
| bool QCustomPlot::saveBmp(const QString &fileName, int width, int height, double scale, int resolution, QCP::ResolutionUnit resolutionUnit)
 | |
| {
 | |
|   return saveRastered(fileName, width, height, scale, "BMP", -1, resolution, resolutionUnit);
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns a minimum size hint that corresponds to the minimum size of the top level layout
 | |
|   (\ref plotLayout). To prevent QCustomPlot from being collapsed to size/width zero, set a minimum
 | |
|   size (setMinimumSize) either on the whole QCustomPlot or on any layout elements inside the plot.
 | |
|   This is especially important, when placed in a QLayout where other components try to take in as
 | |
|   much space as possible (e.g. QMdiArea).
 | |
| */
 | |
| QSize QCustomPlot::minimumSizeHint() const
 | |
| {
 | |
|   return mPlotLayout->minimumSizeHint();
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns a size hint that is the same as \ref minimumSizeHint.
 | |
|   
 | |
| */
 | |
| QSize QCustomPlot::sizeHint() const
 | |
| {
 | |
|   return mPlotLayout->minimumSizeHint();
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Event handler for when the QCustomPlot widget needs repainting. This does not cause a \ref replot, but
 | |
|   draws the internal buffer on the widget surface.
 | |
| */
 | |
| void QCustomPlot::paintEvent(QPaintEvent *event)
 | |
| {
 | |
|   Q_UNUSED(event);
 | |
|   QCPPainter painter(this);
 | |
|   if (painter.isActive())
 | |
|   {
 | |
|     painter.setRenderHint(QPainter::HighQualityAntialiasing); // to make Antialiasing look good if using the OpenGL graphicssystem
 | |
|     if (mBackgroundBrush.style() != Qt::NoBrush)
 | |
|       painter.fillRect(mViewport, mBackgroundBrush);
 | |
|     drawBackground(&painter);
 | |
|     for (int bufferIndex = 0; bufferIndex < mPaintBuffers.size(); ++bufferIndex)
 | |
|       mPaintBuffers.at(bufferIndex)->draw(&painter);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Event handler for a resize of the QCustomPlot widget. The viewport (which becomes the outer rect
 | |
|   of mPlotLayout) is resized appropriately. Finally a \ref replot is performed.
 | |
| */
 | |
| void QCustomPlot::resizeEvent(QResizeEvent *event)
 | |
| {
 | |
|   Q_UNUSED(event)
 | |
|   // resize and repaint the buffer:
 | |
|   setViewport(rect());
 | |
|   replot(rpQueuedRefresh); // queued refresh is important here, to prevent painting issues in some contexts (e.g. MDI subwindow)
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|  Event handler for when a double click occurs. Emits the \ref mouseDoubleClick signal, then
 | |
|  determines the layerable under the cursor and forwards the event to it. Finally, emits the
 | |
|  specialized signals when certain objecs are clicked (e.g. \ref plottableDoubleClick, \ref
 | |
|  axisDoubleClick, etc.).
 | |
|  
 | |
|  \see mousePressEvent, mouseReleaseEvent
 | |
| */
 | |
| void QCustomPlot::mouseDoubleClickEvent(QMouseEvent *event)
 | |
| {
 | |
|   emit mouseDoubleClick(event);
 | |
|   mMouseHasMoved = false;
 | |
|   mMousePressPos = event->pos();
 | |
|   
 | |
|   // determine layerable under the cursor (this event is called instead of the second press event in a double-click):
 | |
|   QList<QVariant> details;
 | |
|   QList<QCPLayerable*> candidates = layerableListAt(mMousePressPos, false, &details);
 | |
|   for (int i=0; i<candidates.size(); ++i)
 | |
|   {
 | |
|     event->accept(); // default impl of QCPLayerable's mouse events ignore the event, in that case propagate to next candidate in list
 | |
|     candidates.at(i)->mouseDoubleClickEvent(event, details.at(i));
 | |
|     if (event->isAccepted())
 | |
|     {
 | |
|       mMouseEventLayerable = candidates.at(i);
 | |
|       mMouseEventLayerableDetails = details.at(i);
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   // emit specialized object double click signals:
 | |
|   if (!candidates.isEmpty())
 | |
|   {
 | |
|     if (QCPAbstractPlottable *ap = qobject_cast<QCPAbstractPlottable*>(candidates.first()))
 | |
|     {
 | |
|       int dataIndex = 0;
 | |
|       if (!details.first().value<QCPDataSelection>().isEmpty())
 | |
|         dataIndex = details.first().value<QCPDataSelection>().dataRange().begin();
 | |
|       emit plottableDoubleClick(ap, dataIndex, event);
 | |
|     } else if (QCPAxis *ax = qobject_cast<QCPAxis*>(candidates.first()))
 | |
|       emit axisDoubleClick(ax, details.first().value<QCPAxis::SelectablePart>(), event);
 | |
|     else if (QCPAbstractItem *ai = qobject_cast<QCPAbstractItem*>(candidates.first()))
 | |
|       emit itemDoubleClick(ai, event);
 | |
|     else if (QCPLegend *lg = qobject_cast<QCPLegend*>(candidates.first()))
 | |
|       emit legendDoubleClick(lg, 0, event);
 | |
|     else if (QCPAbstractLegendItem *li = qobject_cast<QCPAbstractLegendItem*>(candidates.first()))
 | |
|       emit legendDoubleClick(li->parentLegend(), li, event);
 | |
|   }
 | |
|   
 | |
|   event->accept(); // in case QCPLayerable reimplementation manipulates event accepted state. In QWidget event system, QCustomPlot wants to accept the event.
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Event handler for when a mouse button is pressed. Emits the mousePress signal.
 | |
| 
 | |
|   If the current \ref setSelectionRectMode is not \ref QCP::srmNone, passes the event to the
 | |
|   selection rect. Otherwise determines the layerable under the cursor and forwards the event to it.
 | |
|   
 | |
|   \see mouseMoveEvent, mouseReleaseEvent
 | |
| */
 | |
| void QCustomPlot::mousePressEvent(QMouseEvent *event)
 | |
| {
 | |
|   emit mousePress(event);
 | |
|   // save some state to tell in releaseEvent whether it was a click:
 | |
|   mMouseHasMoved = false;
 | |
|   mMousePressPos = event->pos();
 | |
|   
 | |
|   if (mSelectionRect && mSelectionRectMode != QCP::srmNone)
 | |
|   {
 | |
|     if (mSelectionRectMode != QCP::srmZoom || qobject_cast<QCPAxisRect*>(axisRectAt(mMousePressPos))) // in zoom mode only activate selection rect if on an axis rect
 | |
|       mSelectionRect->startSelection(event);
 | |
|   } else
 | |
|   {
 | |
|     // no selection rect interaction, so forward event to layerable under the cursor:
 | |
|     QList<QVariant> details;
 | |
|     QList<QCPLayerable*> candidates = layerableListAt(mMousePressPos, false, &details);
 | |
|     for (int i=0; i<candidates.size(); ++i)
 | |
|     {
 | |
|       event->accept(); // default impl of QCPLayerable's mouse events ignore the event, in that case propagate to next candidate in list
 | |
|       candidates.at(i)->mousePressEvent(event, details.at(i));
 | |
|       if (event->isAccepted())
 | |
|       {
 | |
|         mMouseEventLayerable = candidates.at(i);
 | |
|         mMouseEventLayerableDetails = details.at(i);
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   event->accept(); // in case QCPLayerable reimplementation manipulates event accepted state. In QWidget event system, QCustomPlot wants to accept the event.
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Event handler for when the cursor is moved. Emits the \ref mouseMove signal.
 | |
| 
 | |
|   If the selection rect (\ref setSelectionRect) is currently active, the event is forwarded to it
 | |
|   in order to update the rect geometry.
 | |
|   
 | |
|   Otherwise, if a layout element has mouse capture focus (a mousePressEvent happened on top of the
 | |
|   layout element before), the mouseMoveEvent is forwarded to that element.
 | |
|   
 | |
|   \see mousePressEvent, mouseReleaseEvent
 | |
| */
 | |
| void QCustomPlot::mouseMoveEvent(QMouseEvent *event)
 | |
| {
 | |
|   emit mouseMove(event);
 | |
|   
 | |
|   if (!mMouseHasMoved && (mMousePressPos-event->pos()).manhattanLength() > 3)
 | |
|     mMouseHasMoved = true; // moved too far from mouse press position, don't handle as click on mouse release
 | |
|   
 | |
|   if (mSelectionRect && mSelectionRect->isActive())
 | |
|     mSelectionRect->moveSelection(event);
 | |
|   else if (mMouseEventLayerable) // call event of affected layerable:
 | |
|     mMouseEventLayerable->mouseMoveEvent(event, mMousePressPos);
 | |
|   
 | |
|   event->accept(); // in case QCPLayerable reimplementation manipulates event accepted state. In QWidget event system, QCustomPlot wants to accept the event.
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Event handler for when a mouse button is released. Emits the \ref mouseRelease signal.
 | |
| 
 | |
|   If the mouse was moved less than a certain threshold in any direction since the \ref
 | |
|   mousePressEvent, it is considered a click which causes the selection mechanism (if activated via
 | |
|   \ref setInteractions) to possibly change selection states accordingly. Further, specialized mouse
 | |
|   click signals are emitted (e.g. \ref plottableClick, \ref axisClick, etc.)
 | |
| 
 | |
|   If a layerable is the mouse capturer (a \ref mousePressEvent happened on top of the layerable
 | |
|   before), the \ref mouseReleaseEvent is forwarded to that element.
 | |
| 
 | |
|   \see mousePressEvent, mouseMoveEvent
 | |
| */
 | |
| void QCustomPlot::mouseReleaseEvent(QMouseEvent *event)
 | |
| {
 | |
|   emit mouseRelease(event);
 | |
|   
 | |
|   if (!mMouseHasMoved) // mouse hasn't moved (much) between press and release, so handle as click
 | |
|   {
 | |
|     if (mSelectionRect && mSelectionRect->isActive()) // a simple click shouldn't successfully finish a selection rect, so cancel it here
 | |
|       mSelectionRect->cancel();
 | |
|     if (event->button() == Qt::LeftButton)
 | |
|       processPointSelection(event);
 | |
|     
 | |
|     // emit specialized click signals of QCustomPlot instance:
 | |
|     if (QCPAbstractPlottable *ap = qobject_cast<QCPAbstractPlottable*>(mMouseEventLayerable))
 | |
|     {
 | |
|       int dataIndex = 0;
 | |
|       if (!mMouseEventLayerableDetails.value<QCPDataSelection>().isEmpty())
 | |
|         dataIndex = mMouseEventLayerableDetails.value<QCPDataSelection>().dataRange().begin();
 | |
|       emit plottableClick(ap, dataIndex, event);
 | |
|     } else if (QCPAxis *ax = qobject_cast<QCPAxis*>(mMouseEventLayerable))
 | |
|       emit axisClick(ax, mMouseEventLayerableDetails.value<QCPAxis::SelectablePart>(), event);
 | |
|     else if (QCPAbstractItem *ai = qobject_cast<QCPAbstractItem*>(mMouseEventLayerable))
 | |
|       emit itemClick(ai, event);
 | |
|     else if (QCPLegend *lg = qobject_cast<QCPLegend*>(mMouseEventLayerable))
 | |
|       emit legendClick(lg, 0, event);
 | |
|     else if (QCPAbstractLegendItem *li = qobject_cast<QCPAbstractLegendItem*>(mMouseEventLayerable))
 | |
|       emit legendClick(li->parentLegend(), li, event);
 | |
|   }
 | |
|   
 | |
|   if (mSelectionRect && mSelectionRect->isActive()) // Note: if a click was detected above, the selection rect is canceled there
 | |
|   {
 | |
|     // finish selection rect, the appropriate action will be taken via signal-slot connection:
 | |
|     mSelectionRect->endSelection(event);
 | |
|   } else
 | |
|   {
 | |
|     // call event of affected layerable:
 | |
|     if (mMouseEventLayerable)
 | |
|     {
 | |
|       mMouseEventLayerable->mouseReleaseEvent(event, mMousePressPos);
 | |
|       mMouseEventLayerable = 0;
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   if (noAntialiasingOnDrag())
 | |
|     replot(rpQueuedReplot);
 | |
|   
 | |
|   event->accept(); // in case QCPLayerable reimplementation manipulates event accepted state. In QWidget event system, QCustomPlot wants to accept the event.
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Event handler for mouse wheel events. First, the \ref mouseWheel signal is emitted. Then
 | |
|   determines the affected layerable and forwards the event to it.
 | |
| */
 | |
| void QCustomPlot::wheelEvent(QWheelEvent *event)
 | |
| {
 | |
|   emit mouseWheel(event);
 | |
|   // forward event to layerable under cursor:
 | |
|   QList<QCPLayerable*> candidates = layerableListAt(event->pos(), false);
 | |
|   for (int i=0; i<candidates.size(); ++i)
 | |
|   {
 | |
|     event->accept(); // default impl of QCPLayerable's mouse events ignore the event, in that case propagate to next candidate in list
 | |
|     candidates.at(i)->wheelEvent(event);
 | |
|     if (event->isAccepted())
 | |
|       break;
 | |
|   }
 | |
|   event->accept(); // in case QCPLayerable reimplementation manipulates event accepted state. In QWidget event system, QCustomPlot wants to accept the event.
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   This function draws the entire plot, including background pixmap, with the specified \a painter.
 | |
|   It does not make use of the paint buffers like \ref replot, so this is the function typically
 | |
|   used by saving/exporting methods such as \ref savePdf or \ref toPainter.
 | |
| 
 | |
|   Note that it does not fill the background with the background brush (as the user may specify with
 | |
|   \ref setBackground(const QBrush &brush)), this is up to the respective functions calling this
 | |
|   method.
 | |
| */
 | |
| void QCustomPlot::draw(QCPPainter *painter)
 | |
| {
 | |
|   updateLayout();
 | |
|   
 | |
|   // draw viewport background pixmap:
 | |
|   drawBackground(painter);
 | |
| 
 | |
|   // draw all layered objects (grid, axes, plottables, items, legend,...):
 | |
|   foreach (QCPLayer *layer, mLayers)
 | |
|     layer->draw(painter);
 | |
|   
 | |
|   /* Debug code to draw all layout element rects
 | |
|   foreach (QCPLayoutElement* el, findChildren<QCPLayoutElement*>())
 | |
|   {
 | |
|     painter->setBrush(Qt::NoBrush);
 | |
|     painter->setPen(QPen(QColor(0, 0, 0, 100), 0, Qt::DashLine));
 | |
|     painter->drawRect(el->rect());
 | |
|     painter->setPen(QPen(QColor(255, 0, 0, 100), 0, Qt::DashLine));
 | |
|     painter->drawRect(el->outerRect());
 | |
|   }
 | |
|   */
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Performs the layout update steps defined by \ref QCPLayoutElement::UpdatePhase, by calling \ref
 | |
|   QCPLayoutElement::update on the main plot layout.
 | |
| 
 | |
|   Here, the layout elements calculate their positions and margins, and prepare for the following
 | |
|   draw call.
 | |
| */
 | |
| void QCustomPlot::updateLayout()
 | |
| {
 | |
|   // run through layout phases:
 | |
|   mPlotLayout->update(QCPLayoutElement::upPreparation);
 | |
|   mPlotLayout->update(QCPLayoutElement::upMargins);
 | |
|   mPlotLayout->update(QCPLayoutElement::upLayout);
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Draws the viewport background pixmap of the plot.
 | |
|   
 | |
|   If a pixmap was provided via \ref setBackground, this function buffers the scaled version
 | |
|   depending on \ref setBackgroundScaled and \ref setBackgroundScaledMode and then draws it inside
 | |
|   the viewport with the provided \a painter. The scaled version is buffered in
 | |
|   mScaledBackgroundPixmap to prevent expensive rescaling at every redraw. It is only updated, when
 | |
|   the axis rect has changed in a way that requires a rescale of the background pixmap (this is
 | |
|   dependent on the \ref setBackgroundScaledMode), or when a differend axis background pixmap was
 | |
|   set.
 | |
|   
 | |
|   Note that this function does not draw a fill with the background brush
 | |
|   (\ref setBackground(const QBrush &brush)) beneath the pixmap.
 | |
|   
 | |
|   \see setBackground, setBackgroundScaled, setBackgroundScaledMode
 | |
| */
 | |
| void QCustomPlot::drawBackground(QCPPainter *painter)
 | |
| {
 | |
|   // Note: background color is handled in individual replot/save functions
 | |
| 
 | |
|   // draw background pixmap (on top of fill, if brush specified):
 | |
|   if (!mBackgroundPixmap.isNull())
 | |
|   {
 | |
|     if (mBackgroundScaled)
 | |
|     {
 | |
|       // check whether mScaledBackground needs to be updated:
 | |
|       QSize scaledSize(mBackgroundPixmap.size());
 | |
|       scaledSize.scale(mViewport.size(), mBackgroundScaledMode);
 | |
|       if (mScaledBackgroundPixmap.size() != scaledSize)
 | |
|         mScaledBackgroundPixmap = mBackgroundPixmap.scaled(mViewport.size(), mBackgroundScaledMode, Qt::SmoothTransformation);
 | |
|       painter->drawPixmap(mViewport.topLeft(), mScaledBackgroundPixmap, QRect(0, 0, mViewport.width(), mViewport.height()) & mScaledBackgroundPixmap.rect());
 | |
|     } else
 | |
|     {
 | |
|       painter->drawPixmap(mViewport.topLeft(), mBackgroundPixmap, QRect(0, 0, mViewport.width(), mViewport.height()));
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Goes through the layers and makes sure this QCustomPlot instance holds the correct number of
 | |
|   paint buffers and that they have the correct configuration (size, pixel ratio, etc.).
 | |
|   Allocations, reallocations and deletions of paint buffers are performed as necessary. It also
 | |
|   associates the paint buffers with the layers, so they draw themselves into the right buffer when
 | |
|   \ref QCPLayer::drawToPaintBuffer is called. This means it associates adjacent \ref
 | |
|   QCPLayer::lmLogical layers to a mutual paint buffer and creates dedicated paint buffers for
 | |
|   layers in \ref QCPLayer::lmBuffered mode.
 | |
| 
 | |
|   This method uses \ref createPaintBuffer to create new paint buffers.
 | |
| 
 | |
|   After this method, the paint buffers are empty (filled with \c Qt::transparent) and invalidated
 | |
|   (so an attempt to replot only a single buffered layer causes a full replot).
 | |
| 
 | |
|   This method is called in every \ref replot call, prior to actually drawing the layers (into their
 | |
|   associated paint buffer). If the paint buffers don't need changing/reallocating, this method
 | |
|   basically leaves them alone and thus finishes very fast.
 | |
| */
 | |
| void QCustomPlot::setupPaintBuffers()
 | |
| {
 | |
|   int bufferIndex = 0;
 | |
|   if (mPaintBuffers.isEmpty())
 | |
|     mPaintBuffers.append(QSharedPointer<QCPAbstractPaintBuffer>(createPaintBuffer()));
 | |
|   
 | |
|   for (int layerIndex = 0; layerIndex < mLayers.size(); ++layerIndex)
 | |
|   {
 | |
|     QCPLayer *layer = mLayers.at(layerIndex);
 | |
|     if (layer->mode() == QCPLayer::lmLogical)
 | |
|     {
 | |
|       layer->mPaintBuffer = mPaintBuffers.at(bufferIndex).toWeakRef();
 | |
|     } else if (layer->mode() == QCPLayer::lmBuffered)
 | |
|     {
 | |
|       ++bufferIndex;
 | |
|       if (bufferIndex >= mPaintBuffers.size())
 | |
|         mPaintBuffers.append(QSharedPointer<QCPAbstractPaintBuffer>(createPaintBuffer()));
 | |
|       layer->mPaintBuffer = mPaintBuffers.at(bufferIndex).toWeakRef();
 | |
|       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
 | |
|       {
 | |
|         ++bufferIndex;
 | |
|         if (bufferIndex >= mPaintBuffers.size())
 | |
|           mPaintBuffers.append(QSharedPointer<QCPAbstractPaintBuffer>(createPaintBuffer()));
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   // remove unneeded buffers:
 | |
|   while (mPaintBuffers.size()-1 > bufferIndex)
 | |
|     mPaintBuffers.removeLast();
 | |
|   // resize buffers to viewport size and clear contents:
 | |
|   for (int i=0; i<mPaintBuffers.size(); ++i)
 | |
|   {
 | |
|     mPaintBuffers.at(i)->setSize(viewport().size()); // won't do anything if already correct size
 | |
|     mPaintBuffers.at(i)->clear(Qt::transparent);
 | |
|     mPaintBuffers.at(i)->setInvalidated();
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   This method is used by \ref setupPaintBuffers when it needs to create new paint buffers.
 | |
| 
 | |
|   Depending on the current setting of \ref setOpenGl, and the current Qt version, different
 | |
|   backends (subclasses of \ref QCPAbstractPaintBuffer) are created, initialized with the proper
 | |
|   size and device pixel ratio, and returned.
 | |
| */
 | |
| QCPAbstractPaintBuffer *QCustomPlot::createPaintBuffer()
 | |
| {
 | |
|   if (mOpenGl)
 | |
|   {
 | |
| #if defined(QCP_OPENGL_FBO)
 | |
|     return new QCPPaintBufferGlFbo(viewport().size(), mBufferDevicePixelRatio, mGlContext, mGlPaintDevice);
 | |
| #elif defined(QCP_OPENGL_PBUFFER)
 | |
|     return new QCPPaintBufferGlPbuffer(viewport().size(), mBufferDevicePixelRatio, mOpenGlMultisamples);
 | |
| #else
 | |
|     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.";
 | |
|     return new QCPPaintBufferPixmap(viewport().size(), mBufferDevicePixelRatio);
 | |
| #endif
 | |
|   } else
 | |
|     return new QCPPaintBufferPixmap(viewport().size(), mBufferDevicePixelRatio);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   This method returns whether any of the paint buffers held by this QCustomPlot instance are
 | |
|   invalidated.
 | |
| 
 | |
|   If any buffer is invalidated, a partial replot (\ref QCPLayer::replot) is not allowed and always
 | |
|   causes a full replot (\ref QCustomPlot::replot) of all layers. This is the case when for example
 | |
|   the layer order has changed, new layers were added, layers were removed, or layer modes were
 | |
|   changed (\ref QCPLayer::setMode).
 | |
| 
 | |
|   \see QCPAbstractPaintBuffer::setInvalidated
 | |
| */
 | |
| bool QCustomPlot::hasInvalidatedPaintBuffers()
 | |
| {
 | |
|   for (int i=0; i<mPaintBuffers.size(); ++i)
 | |
|   {
 | |
|     if (mPaintBuffers.at(i)->invalidated())
 | |
|       return true;
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   When \ref setOpenGl is set to true, this method is used to initialize OpenGL (create a context,
 | |
|   surface, paint device).
 | |
| 
 | |
|   Returns true on success.
 | |
| 
 | |
|   If this method is successful, all paint buffers should be deleted and then reallocated by calling
 | |
|   \ref setupPaintBuffers, so the OpenGL-based paint buffer subclasses (\ref
 | |
|   QCPPaintBufferGlPbuffer, \ref QCPPaintBufferGlFbo) are used for subsequent replots.
 | |
| 
 | |
|   \see freeOpenGl
 | |
| */
 | |
| bool QCustomPlot::setupOpenGl()
 | |
| {
 | |
| #ifdef QCP_OPENGL_FBO
 | |
|   freeOpenGl();
 | |
|   QSurfaceFormat proposedSurfaceFormat;
 | |
|   proposedSurfaceFormat.setSamples(mOpenGlMultisamples);
 | |
| #ifdef QCP_OPENGL_OFFSCREENSURFACE
 | |
|   QOffscreenSurface *surface = new QOffscreenSurface;
 | |
| #else
 | |
|   QWindow *surface = new QWindow;
 | |
|   surface->setSurfaceType(QSurface::OpenGLSurface);
 | |
| #endif
 | |
|   surface->setFormat(proposedSurfaceFormat);
 | |
|   surface->create();
 | |
|   mGlSurface = QSharedPointer<QSurface>(surface);
 | |
|   mGlContext = QSharedPointer<QOpenGLContext>(new QOpenGLContext);
 | |
|   mGlContext->setFormat(mGlSurface->format());
 | |
|   if (!mGlContext->create())
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "Failed to create OpenGL context";
 | |
|     mGlContext.clear();
 | |
|     mGlSurface.clear();
 | |
|     return false;
 | |
|   }
 | |
|   if (!mGlContext->makeCurrent(mGlSurface.data())) // context needs to be current to create paint device
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "Failed to make opengl context current";
 | |
|     mGlContext.clear();
 | |
|     mGlSurface.clear();
 | |
|     return false;
 | |
|   }
 | |
|   if (!QOpenGLFramebufferObject::hasOpenGLFramebufferObjects())
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "OpenGL of this system doesn't support frame buffer objects";
 | |
|     mGlContext.clear();
 | |
|     mGlSurface.clear();
 | |
|     return false;
 | |
|   }
 | |
|   mGlPaintDevice = QSharedPointer<QOpenGLPaintDevice>(new QOpenGLPaintDevice);
 | |
|   return true;
 | |
| #elif defined(QCP_OPENGL_PBUFFER)
 | |
|   return QGLFormat::hasOpenGL();
 | |
| #else
 | |
|   return false;
 | |
| #endif
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   When \ref setOpenGl is set to false, this method is used to deinitialize OpenGL (releases the
 | |
|   context and frees resources).
 | |
| 
 | |
|   After OpenGL is disabled, all paint buffers should be deleted and then reallocated by calling
 | |
|   \ref setupPaintBuffers, so the standard software rendering paint buffer subclass (\ref
 | |
|   QCPPaintBufferPixmap) is used for subsequent replots.
 | |
| 
 | |
|   \see setupOpenGl
 | |
| */
 | |
| void QCustomPlot::freeOpenGl()
 | |
| {
 | |
| #ifdef QCP_OPENGL_FBO
 | |
|   mGlPaintDevice.clear();
 | |
|   mGlContext.clear();
 | |
|   mGlSurface.clear();
 | |
| #endif
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   This method is used by \ref QCPAxisRect::removeAxis to report removed axes to the QCustomPlot
 | |
|   so it may clear its QCustomPlot::xAxis, yAxis, xAxis2 and yAxis2 members accordingly.
 | |
| */
 | |
| void QCustomPlot::axisRemoved(QCPAxis *axis)
 | |
| {
 | |
|   if (xAxis == axis)
 | |
|     xAxis = 0;
 | |
|   if (xAxis2 == axis)
 | |
|     xAxis2 = 0;
 | |
|   if (yAxis == axis)
 | |
|     yAxis = 0;
 | |
|   if (yAxis2 == axis)
 | |
|     yAxis2 = 0;
 | |
|   
 | |
|   // Note: No need to take care of range drag axes and range zoom axes, because they are stored in smart pointers
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   This method is used by the QCPLegend destructor to report legend removal to the QCustomPlot so
 | |
|   it may clear its QCustomPlot::legend member accordingly.
 | |
| */
 | |
| void QCustomPlot::legendRemoved(QCPLegend *legend)
 | |
| {
 | |
|   if (this->legend == legend)
 | |
|     this->legend = 0;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   This slot is connected to the selection rect's \ref QCPSelectionRect::accepted signal when \ref
 | |
|   setSelectionRectMode is set to \ref QCP::srmSelect.
 | |
| 
 | |
|   First, it determines which axis rect was the origin of the selection rect judging by the starting
 | |
|   point of the selection. Then it goes through the plottables (\ref QCPAbstractPlottable1D to be
 | |
|   precise) associated with that axis rect and finds the data points that are in \a rect. It does
 | |
|   this by querying their \ref QCPAbstractPlottable1D::selectTestRect method.
 | |
|   
 | |
|   Then, the actual selection is done by calling the plottables' \ref
 | |
|   QCPAbstractPlottable::selectEvent, placing the found selected data points in the \a details
 | |
|   parameter as <tt>QVariant(\ref QCPDataSelection)</tt>. All plottables that weren't touched by \a
 | |
|   rect receive a \ref QCPAbstractPlottable::deselectEvent.
 | |
|   
 | |
|   \see processRectZoom
 | |
| */
 | |
| void QCustomPlot::processRectSelection(QRect rect, QMouseEvent *event)
 | |
| {
 | |
|   bool selectionStateChanged = false;
 | |
|   
 | |
|   if (mInteractions.testFlag(QCP::iSelectPlottables))
 | |
|   {
 | |
|     QMap<int, QPair<QCPAbstractPlottable*, QCPDataSelection> > potentialSelections; // map key is number of selected data points, so we have selections sorted by size
 | |
|     QRectF rectF(rect.normalized());
 | |
|     if (QCPAxisRect *affectedAxisRect = axisRectAt(rectF.topLeft()))
 | |
|     {
 | |
|       // determine plottables that were hit by the rect and thus are candidates for selection:
 | |
|       foreach (QCPAbstractPlottable *plottable, affectedAxisRect->plottables())
 | |
|       {
 | |
|         if (QCPPlottableInterface1D *plottableInterface = plottable->interface1D())
 | |
|         {
 | |
|           QCPDataSelection dataSel = plottableInterface->selectTestRect(rectF, true);
 | |
|           if (!dataSel.isEmpty())
 | |
|             potentialSelections.insertMulti(dataSel.dataPointCount(), QPair<QCPAbstractPlottable*, QCPDataSelection>(plottable, dataSel));
 | |
|         }
 | |
|       }
 | |
|       
 | |
|       if (!mInteractions.testFlag(QCP::iMultiSelect))
 | |
|       {
 | |
|         // only leave plottable with most selected points in map, since we will only select a single plottable:
 | |
|         if (!potentialSelections.isEmpty())
 | |
|         {
 | |
|           QMap<int, QPair<QCPAbstractPlottable*, QCPDataSelection> >::iterator it = potentialSelections.begin();
 | |
|           while (it != potentialSelections.end()-1) // erase all except last element
 | |
|             it = potentialSelections.erase(it);
 | |
|         }
 | |
|       }
 | |
|       
 | |
|       bool additive = event->modifiers().testFlag(mMultiSelectModifier);
 | |
|       // deselect all other layerables if not additive selection:
 | |
|       if (!additive)
 | |
|       {
 | |
|         // emit deselection except to those plottables who will be selected afterwards:
 | |
|         foreach (QCPLayer *layer, mLayers)
 | |
|         {
 | |
|           foreach (QCPLayerable *layerable, layer->children())
 | |
|           {
 | |
|             if ((potentialSelections.isEmpty() || potentialSelections.constBegin()->first != layerable) && mInteractions.testFlag(layerable->selectionCategory()))
 | |
|             {
 | |
|               bool selChanged = false;
 | |
|               layerable->deselectEvent(&selChanged);
 | |
|               selectionStateChanged |= selChanged;
 | |
|             }
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|       
 | |
|       // go through selections in reverse (largest selection first) and emit select events:
 | |
|       QMap<int, QPair<QCPAbstractPlottable*, QCPDataSelection> >::const_iterator it = potentialSelections.constEnd();
 | |
|       while (it != potentialSelections.constBegin())
 | |
|       {
 | |
|         --it;
 | |
|         if (mInteractions.testFlag(it.value().first->selectionCategory()))
 | |
|         {
 | |
|           bool selChanged = false;
 | |
|           it.value().first->selectEvent(event, additive, QVariant::fromValue(it.value().second), &selChanged);
 | |
|           selectionStateChanged |= selChanged;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   if (selectionStateChanged)
 | |
|   {
 | |
|     emit selectionChangedByUser();
 | |
|     replot(rpQueuedReplot);
 | |
|   } else if (mSelectionRect)
 | |
|     mSelectionRect->layer()->replot();
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   This slot is connected to the selection rect's \ref QCPSelectionRect::accepted signal when \ref
 | |
|   setSelectionRectMode is set to \ref QCP::srmZoom.
 | |
| 
 | |
|   It determines which axis rect was the origin of the selection rect judging by the starting point
 | |
|   of the selection, and then zooms the axes defined via \ref QCPAxisRect::setRangeZoomAxes to the
 | |
|   provided \a rect (see \ref QCPAxisRect::zoom).
 | |
|   
 | |
|   \see processRectSelection
 | |
| */
 | |
| void QCustomPlot::processRectZoom(QRect rect, QMouseEvent *event)
 | |
| {
 | |
|   Q_UNUSED(event)
 | |
|   if (QCPAxisRect *axisRect = axisRectAt(rect.topLeft()))
 | |
|   {
 | |
|     QList<QCPAxis*> affectedAxes = QList<QCPAxis*>() << axisRect->rangeZoomAxes(Qt::Horizontal) << axisRect->rangeZoomAxes(Qt::Vertical);
 | |
|     affectedAxes.removeAll(static_cast<QCPAxis*>(0));
 | |
|     axisRect->zoom(QRectF(rect), affectedAxes);
 | |
|   }
 | |
|   replot(rpQueuedReplot); // always replot to make selection rect disappear
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   This method is called when a simple left mouse click was detected on the QCustomPlot surface.
 | |
| 
 | |
|   It first determines the layerable that was hit by the click, and then calls its \ref
 | |
|   QCPLayerable::selectEvent. All other layerables receive a QCPLayerable::deselectEvent (unless the
 | |
|   multi-select modifier was pressed, see \ref setMultiSelectModifier).
 | |
| 
 | |
|   In this method the hit layerable is determined a second time using \ref layerableAt (after the
 | |
|   one in \ref mousePressEvent), because we want \a onlySelectable set to true this time. This
 | |
|   implies that the mouse event grabber (mMouseEventLayerable) may be a different one from the
 | |
|   clicked layerable determined here. For example, if a non-selectable layerable is in front of a
 | |
|   selectable layerable at the click position, the front layerable will receive mouse events but the
 | |
|   selectable one in the back will receive the \ref QCPLayerable::selectEvent.
 | |
| 
 | |
|   \see processRectSelection, QCPLayerable::selectTest
 | |
| */
 | |
| void QCustomPlot::processPointSelection(QMouseEvent *event)
 | |
| {
 | |
|   QVariant details;
 | |
|   QCPLayerable *clickedLayerable = layerableAt(event->pos(), true, &details);
 | |
|   bool selectionStateChanged = false;
 | |
|   bool additive = mInteractions.testFlag(QCP::iMultiSelect) && event->modifiers().testFlag(mMultiSelectModifier);
 | |
|   // deselect all other layerables if not additive selection:
 | |
|   if (!additive)
 | |
|   {
 | |
|     foreach (QCPLayer *layer, mLayers)
 | |
|     {
 | |
|       foreach (QCPLayerable *layerable, layer->children())
 | |
|       {
 | |
|         if (layerable != clickedLayerable && mInteractions.testFlag(layerable->selectionCategory()))
 | |
|         {
 | |
|           bool selChanged = false;
 | |
|           layerable->deselectEvent(&selChanged);
 | |
|           selectionStateChanged |= selChanged;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   if (clickedLayerable && mInteractions.testFlag(clickedLayerable->selectionCategory()))
 | |
|   {
 | |
|     // a layerable was actually clicked, call its selectEvent:
 | |
|     bool selChanged = false;
 | |
|     clickedLayerable->selectEvent(event, additive, details, &selChanged);
 | |
|     selectionStateChanged |= selChanged;
 | |
|   }
 | |
|   if (selectionStateChanged)
 | |
|   {
 | |
|     emit selectionChangedByUser();
 | |
|     replot(rpQueuedReplot);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Registers the specified plottable with this QCustomPlot and, if \ref setAutoAddPlottableToLegend
 | |
|   is enabled, adds it to the legend (QCustomPlot::legend). QCustomPlot takes ownership of the
 | |
|   plottable.
 | |
|   
 | |
|   Returns true on success, i.e. when \a plottable isn't already in this plot and the parent plot of
 | |
|   \a plottable is this QCustomPlot.
 | |
|   
 | |
|   This method is called automatically in the QCPAbstractPlottable base class constructor.
 | |
| */
 | |
| bool QCustomPlot::registerPlottable(QCPAbstractPlottable *plottable)
 | |
| {
 | |
|   if (mPlottables.contains(plottable))
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "plottable already added to this QCustomPlot:" << reinterpret_cast<quintptr>(plottable);
 | |
|     return false;
 | |
|   }
 | |
|   if (plottable->parentPlot() != this)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "plottable not created with this QCustomPlot as parent:" << reinterpret_cast<quintptr>(plottable);
 | |
|     return false;
 | |
|   }
 | |
|   
 | |
|   mPlottables.append(plottable);
 | |
|   // possibly add plottable to legend:
 | |
|   if (mAutoAddPlottableToLegend)
 | |
|     plottable->addToLegend();
 | |
|   if (!plottable->layer()) // usually the layer is already set in the constructor of the plottable (via QCPLayerable constructor)
 | |
|     plottable->setLayer(currentLayer());
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   In order to maintain the simplified graph interface of QCustomPlot, this method is called by the
 | |
|   QCPGraph constructor to register itself with this QCustomPlot's internal graph list. Returns true
 | |
|   on success, i.e. if \a graph is valid and wasn't already registered with this QCustomPlot.
 | |
|   
 | |
|   This graph specific registration happens in addition to the call to \ref registerPlottable by the
 | |
|   QCPAbstractPlottable base class.
 | |
| */
 | |
| bool QCustomPlot::registerGraph(QCPGraph *graph)
 | |
| {
 | |
|   if (!graph)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "passed graph is zero";
 | |
|     return false;
 | |
|   }
 | |
|   if (mGraphs.contains(graph))
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "graph already registered with this QCustomPlot";
 | |
|     return false;
 | |
|   }
 | |
|   
 | |
|   mGraphs.append(graph);
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Registers the specified item with this QCustomPlot. QCustomPlot takes ownership of the item.
 | |
|   
 | |
|   Returns true on success, i.e. when \a item wasn't already in the plot and the parent plot of \a
 | |
|   item is this QCustomPlot.
 | |
|   
 | |
|   This method is called automatically in the QCPAbstractItem base class constructor.
 | |
| */
 | |
| bool QCustomPlot::registerItem(QCPAbstractItem *item)
 | |
| {
 | |
|   if (mItems.contains(item))
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "item already added to this QCustomPlot:" << reinterpret_cast<quintptr>(item);
 | |
|     return false;
 | |
|   }
 | |
|   if (item->parentPlot() != this)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "item not created with this QCustomPlot as parent:" << reinterpret_cast<quintptr>(item);
 | |
|     return false;
 | |
|   }
 | |
|   
 | |
|   mItems.append(item);
 | |
|   if (!item->layer()) // usually the layer is already set in the constructor of the item (via QCPLayerable constructor)
 | |
|     item->setLayer(currentLayer());
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Assigns all layers their index (QCPLayer::mIndex) in the mLayers list. This method is thus called
 | |
|   after every operation that changes the layer indices, like layer removal, layer creation, layer
 | |
|   moving.
 | |
| */
 | |
| void QCustomPlot::updateLayerIndices() const
 | |
| {
 | |
|   for (int i=0; i<mLayers.size(); ++i)
 | |
|     mLayers.at(i)->mIndex = i;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Returns the top-most layerable at pixel position \a pos. If \a onlySelectable is set to true,
 | |
|   only those layerables that are selectable will be considered. (Layerable subclasses communicate
 | |
|   their selectability via the QCPLayerable::selectTest method, by returning -1.)
 | |
| 
 | |
|   \a selectionDetails is an output parameter that contains selection specifics of the affected
 | |
|   layerable. This is useful if the respective layerable shall be given a subsequent
 | |
|   QCPLayerable::selectEvent (like in \ref mouseReleaseEvent). \a selectionDetails usually contains
 | |
|   information about which part of the layerable was hit, in multi-part layerables (e.g.
 | |
|   QCPAxis::SelectablePart). If the layerable is a plottable, \a selectionDetails contains a \ref
 | |
|   QCPDataSelection instance with the single data point which is closest to \a pos.
 | |
|   
 | |
|   \see layerableListAt, layoutElementAt, axisRectAt
 | |
| */
 | |
| QCPLayerable *QCustomPlot::layerableAt(const QPointF &pos, bool onlySelectable, QVariant *selectionDetails) const
 | |
| {
 | |
|   QList<QVariant> details;
 | |
|   QList<QCPLayerable*> candidates = layerableListAt(pos, onlySelectable, selectionDetails ? &details : 0);
 | |
|   if (selectionDetails && !details.isEmpty())
 | |
|     *selectionDetails = details.first();
 | |
|   if (!candidates.isEmpty())
 | |
|     return candidates.first();
 | |
|   else
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Returns the layerables at pixel position \a pos. If \a onlySelectable is set to true, only those
 | |
|   layerables that are selectable will be considered. (Layerable subclasses communicate their
 | |
|   selectability via the QCPLayerable::selectTest method, by returning -1.)
 | |
| 
 | |
|   The returned list is sorted by the layerable/drawing order. If you only need to know the top-most
 | |
|   layerable, rather use \ref layerableAt.
 | |
| 
 | |
|   \a selectionDetails is an output parameter that contains selection specifics of the affected
 | |
|   layerable. This is useful if the respective layerable shall be given a subsequent
 | |
|   QCPLayerable::selectEvent (like in \ref mouseReleaseEvent). \a selectionDetails usually contains
 | |
|   information about which part of the layerable was hit, in multi-part layerables (e.g.
 | |
|   QCPAxis::SelectablePart). If the layerable is a plottable, \a selectionDetails contains a \ref
 | |
|   QCPDataSelection instance with the single data point which is closest to \a pos.
 | |
|   
 | |
|   \see layerableAt, layoutElementAt, axisRectAt
 | |
| */
 | |
| QList<QCPLayerable*> QCustomPlot::layerableListAt(const QPointF &pos, bool onlySelectable, QList<QVariant> *selectionDetails) const
 | |
| {
 | |
|   QList<QCPLayerable*> result;
 | |
|   for (int layerIndex=mLayers.size()-1; layerIndex>=0; --layerIndex)
 | |
|   {
 | |
|     const QList<QCPLayerable*> layerables = mLayers.at(layerIndex)->children();
 | |
|     for (int i=layerables.size()-1; i>=0; --i)
 | |
|     {
 | |
|       if (!layerables.at(i)->realVisibility())
 | |
|         continue;
 | |
|       QVariant details;
 | |
|       double dist = layerables.at(i)->selectTest(pos, onlySelectable, selectionDetails ? &details : 0);
 | |
|       if (dist >= 0 && dist < selectionTolerance())
 | |
|       {
 | |
|         result.append(layerables.at(i));
 | |
|         if (selectionDetails)
 | |
|           selectionDetails->append(details);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Saves the plot to a rastered image file \a fileName in the image format \a format. The plot is
 | |
|   sized to \a width and \a height in pixels and scaled with \a scale. (width 100 and scale 2.0 lead
 | |
|   to a full resolution file with width 200.) If the \a format supports compression, \a quality may
 | |
|   be between 0 and 100 to control it.
 | |
| 
 | |
|   Returns true on success. If this function fails, most likely the given \a format isn't supported
 | |
|   by the system, see Qt docs about QImageWriter::supportedImageFormats().
 | |
| 
 | |
|   The \a resolution will be written to the image file header (if the file format supports this) and
 | |
|   has no direct consequence for the quality or the pixel size. However, if opening the image with a
 | |
|   tool which respects the metadata, it will be able to scale the image to match either a given size
 | |
|   in real units of length (inch, centimeters, etc.), or the target display DPI. You can specify in
 | |
|   which units \a resolution is given, by setting \a resolutionUnit. The \a resolution is converted
 | |
|   to the format's expected resolution unit internally.
 | |
| 
 | |
|   \see saveBmp, saveJpg, savePng, savePdf
 | |
| */
 | |
| bool QCustomPlot::saveRastered(const QString &fileName, int width, int height, double scale, const char *format, int quality, int resolution, QCP::ResolutionUnit resolutionUnit)
 | |
| {
 | |
|   QImage buffer = toPixmap(width, height, scale).toImage();
 | |
|   
 | |
|   int dotsPerMeter = 0;
 | |
|   switch (resolutionUnit)
 | |
|   {
 | |
|     case QCP::ruDotsPerMeter: dotsPerMeter = resolution; break;
 | |
|     case QCP::ruDotsPerCentimeter: dotsPerMeter = resolution*100; break;
 | |
|     case QCP::ruDotsPerInch: dotsPerMeter = resolution/0.0254; break;
 | |
|   }
 | |
|   buffer.setDotsPerMeterX(dotsPerMeter); // this is saved together with some image formats, e.g. PNG, and is relevant when opening image in other tools
 | |
|   buffer.setDotsPerMeterY(dotsPerMeter); // this is saved together with some image formats, e.g. PNG, and is relevant when opening image in other tools
 | |
|   if (!buffer.isNull())
 | |
|     return buffer.save(fileName, format, quality);
 | |
|   else
 | |
|     return false;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Renders the plot to a pixmap and returns it.
 | |
|   
 | |
|   The plot is sized to \a width and \a height in pixels and scaled with \a scale. (width 100 and
 | |
|   scale 2.0 lead to a full resolution pixmap with width 200.)
 | |
|   
 | |
|   \see toPainter, saveRastered, saveBmp, savePng, saveJpg, savePdf
 | |
| */
 | |
| QPixmap QCustomPlot::toPixmap(int width, int height, double scale)
 | |
| {
 | |
|   // this method is somewhat similar to toPainter. Change something here, and a change in toPainter might be necessary, too.
 | |
|   int newWidth, newHeight;
 | |
|   if (width == 0 || height == 0)
 | |
|   {
 | |
|     newWidth = this->width();
 | |
|     newHeight = this->height();
 | |
|   } else
 | |
|   {
 | |
|     newWidth = width;
 | |
|     newHeight = height;
 | |
|   }
 | |
|   int scaledWidth = qRound(scale*newWidth);
 | |
|   int scaledHeight = qRound(scale*newHeight);
 | |
| 
 | |
|   QPixmap result(scaledWidth, scaledHeight);
 | |
|   result.fill(mBackgroundBrush.style() == Qt::SolidPattern ? mBackgroundBrush.color() : Qt::transparent); // if using non-solid pattern, make transparent now and draw brush pattern later
 | |
|   QCPPainter painter;
 | |
|   painter.begin(&result);
 | |
|   if (painter.isActive())
 | |
|   {
 | |
|     QRect oldViewport = viewport();
 | |
|     setViewport(QRect(0, 0, newWidth, newHeight));
 | |
|     painter.setMode(QCPPainter::pmNoCaching);
 | |
|     if (!qFuzzyCompare(scale, 1.0))
 | |
|     {
 | |
|       if (scale > 1.0) // for scale < 1 we always want cosmetic pens where possible, because else lines might disappear for very small scales
 | |
|         painter.setMode(QCPPainter::pmNonCosmetic);
 | |
|       painter.scale(scale, scale);
 | |
|     }
 | |
|     if (mBackgroundBrush.style() != Qt::SolidPattern && mBackgroundBrush.style() != Qt::NoBrush) // solid fills were done a few lines above with QPixmap::fill
 | |
|       painter.fillRect(mViewport, mBackgroundBrush);
 | |
|     draw(&painter);
 | |
|     setViewport(oldViewport);
 | |
|     painter.end();
 | |
|   } else // might happen if pixmap has width or height zero
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "Couldn't activate painter on pixmap";
 | |
|     return QPixmap();
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Renders the plot using the passed \a painter.
 | |
|   
 | |
|   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
 | |
|   appear scaled accordingly.
 | |
|   
 | |
|   \note If you are restricted to using a QPainter (instead of QCPPainter), create a temporary QPicture and open a QCPPainter
 | |
|   on it. Then call \ref toPainter with this QCPPainter. After ending the paint operation on the picture, draw it with
 | |
|   the QPainter. This will reproduce the painter actions the QCPPainter took, with a QPainter.
 | |
|   
 | |
|   \see toPixmap
 | |
| */
 | |
| void QCustomPlot::toPainter(QCPPainter *painter, int width, int height)
 | |
| {
 | |
|   // this method is somewhat similar to toPixmap. Change something here, and a change in toPixmap might be necessary, too.
 | |
|   int newWidth, newHeight;
 | |
|   if (width == 0 || height == 0)
 | |
|   {
 | |
|     newWidth = this->width();
 | |
|     newHeight = this->height();
 | |
|   } else
 | |
|   {
 | |
|     newWidth = width;
 | |
|     newHeight = height;
 | |
|   }
 | |
| 
 | |
|   if (painter->isActive())
 | |
|   {
 | |
|     QRect oldViewport = viewport();
 | |
|     setViewport(QRect(0, 0, newWidth, newHeight));
 | |
|     painter->setMode(QCPPainter::pmNoCaching);
 | |
|     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
 | |
|       painter->fillRect(mViewport, mBackgroundBrush);
 | |
|     draw(painter);
 | |
|     setViewport(oldViewport);
 | |
|   } else
 | |
|     qDebug() << Q_FUNC_INFO << "Passed painter is not active";
 | |
| }
 | |
| /* end of 'src/core.cpp' */
 | |
| 
 | |
| //amalgamation: add plottable1d.cpp
 | |
| 
 | |
| /* including file 'src/colorgradient.cpp', size 24646                        */
 | |
| /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */
 | |
| 
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPColorGradient
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPColorGradient
 | |
|   \brief Defines a color gradient for use with e.g. \ref QCPColorMap
 | |
|   
 | |
|   This class describes a color gradient which can be used to encode data with color. For example,
 | |
|   QCPColorMap and QCPColorScale have \ref QCPColorMap::setGradient "setGradient" methods which
 | |
|   take an instance of this class. Colors are set with \ref setColorStopAt(double position, const QColor &color)
 | |
|   with a \a position from 0 to 1. In between these defined color positions, the
 | |
|   color will be interpolated linearly either in RGB or HSV space, see \ref setColorInterpolation.
 | |
| 
 | |
|   Alternatively, load one of the preset color gradients shown in the image below, with \ref
 | |
|   loadPreset, or by directly specifying the preset in the constructor.
 | |
|   
 | |
|   Apart from red, green and blue components, the gradient also interpolates the alpha values of the
 | |
|   configured color stops. This allows to display some portions of the data range as transparent in
 | |
|   the plot.
 | |
|   
 | |
|   \image html QCPColorGradient.png
 | |
|   
 | |
|   The \ref QCPColorGradient(GradientPreset preset) constructor allows directly converting a \ref
 | |
|   GradientPreset to a QCPColorGradient. This means that you can directly pass \ref GradientPreset
 | |
|   to all the \a setGradient methods, e.g.:
 | |
|   \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcolorgradient-setgradient
 | |
|   
 | |
|   The total number of levels used in the gradient can be set with \ref setLevelCount. Whether the
 | |
|   color gradient shall be applied periodically (wrapping around) to data values that lie outside
 | |
|   the data range specified on the plottable instance can be controlled with \ref setPeriodic.
 | |
| */
 | |
| 
 | |
| /*!
 | |
|   Constructs a new, empty QCPColorGradient with no predefined color stops. You can add own color
 | |
|   stops with \ref setColorStopAt.
 | |
| 
 | |
|   The color level count is initialized to 350.
 | |
| */
 | |
| QCPColorGradient::QCPColorGradient() :
 | |
|   mLevelCount(350),
 | |
|   mColorInterpolation(ciRGB),
 | |
|   mPeriodic(false),
 | |
|   mColorBufferInvalidated(true)
 | |
| {
 | |
|   mColorBuffer.fill(qRgb(0, 0, 0), mLevelCount);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Constructs a new QCPColorGradient initialized with the colors and color interpolation according
 | |
|   to \a preset.
 | |
| 
 | |
|   The color level count is initialized to 350.
 | |
| */
 | |
| QCPColorGradient::QCPColorGradient(GradientPreset preset) :
 | |
|   mLevelCount(350),
 | |
|   mColorInterpolation(ciRGB),
 | |
|   mPeriodic(false),
 | |
|   mColorBufferInvalidated(true)
 | |
| {
 | |
|   mColorBuffer.fill(qRgb(0, 0, 0), mLevelCount);
 | |
|   loadPreset(preset);
 | |
| }
 | |
| 
 | |
| /* undocumented operator */
 | |
| bool QCPColorGradient::operator==(const QCPColorGradient &other) const
 | |
| {
 | |
|   return ((other.mLevelCount == this->mLevelCount) &&
 | |
|           (other.mColorInterpolation == this->mColorInterpolation) &&
 | |
|           (other.mPeriodic == this->mPeriodic) &&
 | |
|           (other.mColorStops == this->mColorStops));
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the number of discretization levels of the color gradient to \a n. The default is 350 which
 | |
|   is typically enough to create a smooth appearance. The minimum number of levels is 2.
 | |
| 
 | |
|   \image html QCPColorGradient-levelcount.png
 | |
| */
 | |
| void QCPColorGradient::setLevelCount(int n)
 | |
| {
 | |
|   if (n < 2)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "n must be greater or equal 2 but was" << n;
 | |
|     n = 2;
 | |
|   }
 | |
|   if (n != mLevelCount)
 | |
|   {
 | |
|     mLevelCount = n;
 | |
|     mColorBufferInvalidated = true;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets at which positions from 0 to 1 which color shall occur. The positions are the keys, the
 | |
|   colors are the values of the passed QMap \a colorStops. In between these color stops, the color
 | |
|   is interpolated according to \ref setColorInterpolation.
 | |
|   
 | |
|   A more convenient way to create a custom gradient may be to clear all color stops with \ref
 | |
|   clearColorStops (or creating a new, empty QCPColorGradient) and then adding them one by one with
 | |
|   \ref setColorStopAt.
 | |
|   
 | |
|   \see clearColorStops
 | |
| */
 | |
| void QCPColorGradient::setColorStops(const QMap<double, QColor> &colorStops)
 | |
| {
 | |
|   mColorStops = colorStops;
 | |
|   mColorBufferInvalidated = true;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the \a color the gradient will have at the specified \a position (from 0 to 1). In between
 | |
|   these color stops, the color is interpolated according to \ref setColorInterpolation.
 | |
|   
 | |
|   \see setColorStops, clearColorStops
 | |
| */
 | |
| void QCPColorGradient::setColorStopAt(double position, const QColor &color)
 | |
| {
 | |
|   mColorStops.insert(position, color);
 | |
|   mColorBufferInvalidated = true;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets whether the colors in between the configured color stops (see \ref setColorStopAt) shall be
 | |
|   interpolated linearly in RGB or in HSV color space.
 | |
|   
 | |
|   For example, a sweep in RGB space from red to green will have a muddy brown intermediate color,
 | |
|   whereas in HSV space the intermediate color is yellow.
 | |
| */
 | |
| void QCPColorGradient::setColorInterpolation(QCPColorGradient::ColorInterpolation interpolation)
 | |
| {
 | |
|   if (interpolation != mColorInterpolation)
 | |
|   {
 | |
|     mColorInterpolation = interpolation;
 | |
|     mColorBufferInvalidated = true;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets whether data points that are outside the configured data range (e.g. \ref
 | |
|   QCPColorMap::setDataRange) are colored by periodically repeating the color gradient or whether
 | |
|   they all have the same color, corresponding to the respective gradient boundary color.
 | |
|   
 | |
|   \image html QCPColorGradient-periodic.png
 | |
|   
 | |
|   As shown in the image above, gradients that have the same start and end color are especially
 | |
|   suitable for a periodic gradient mapping, since they produce smooth color transitions throughout
 | |
|   the color map. A preset that has this property is \ref gpHues.
 | |
|   
 | |
|   In practice, using periodic color gradients makes sense when the data corresponds to a periodic
 | |
|   dimension, such as an angle or a phase. If this is not the case, the color encoding might become
 | |
|   ambiguous, because multiple different data values are shown as the same color.
 | |
| */
 | |
| void QCPColorGradient::setPeriodic(bool enabled)
 | |
| {
 | |
|   mPeriodic = enabled;
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   This method is used to quickly convert a \a data array to colors. The colors will be output in
 | |
|   the array \a scanLine. Both \a data and \a scanLine must have the length \a n when passed to this
 | |
|   function. The data range that shall be used for mapping the data value to the gradient is passed
 | |
|   in \a range. \a logarithmic indicates whether the data values shall be mapped to colors
 | |
|   logarithmically.
 | |
| 
 | |
|   if \a data actually contains 2D-data linearized via <tt>[row*columnCount + column]</tt>, you can
 | |
|   set \a dataIndexFactor to <tt>columnCount</tt> to convert a column instead of a row of the data
 | |
|   array, in \a scanLine. \a scanLine will remain a regular (1D) array. This works because \a data
 | |
|   is addressed <tt>data[i*dataIndexFactor]</tt>.
 | |
|   
 | |
|   Use the overloaded method to additionally provide alpha map data.
 | |
| 
 | |
|   The QRgb values that are placed in \a scanLine have their r, g and b components premultiplied
 | |
|   with alpha (see QImage::Format_ARGB32_Premultiplied).
 | |
| */
 | |
| void QCPColorGradient::colorize(const double *data, const QCPRange &range, QRgb *scanLine, int n, int dataIndexFactor, bool logarithmic)
 | |
| {
 | |
|   // If you change something here, make sure to also adapt color() and the other colorize() overload
 | |
|   if (!data)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "null pointer given as data";
 | |
|     return;
 | |
|   }
 | |
|   if (!scanLine)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "null pointer given as scanLine";
 | |
|     return;
 | |
|   }
 | |
|   if (mColorBufferInvalidated)
 | |
|     updateColorBuffer();
 | |
|   
 | |
|   if (!logarithmic)
 | |
|   {
 | |
|     const double posToIndexFactor = (mLevelCount-1)/range.size();
 | |
|     if (mPeriodic)
 | |
|     {
 | |
|       for (int i=0; i<n; ++i)
 | |
|       {
 | |
|         int index = (int)((data[dataIndexFactor*i]-range.lower)*posToIndexFactor) % mLevelCount;
 | |
|         if (index < 0)
 | |
|           index += mLevelCount;
 | |
|         scanLine[i] = mColorBuffer.at(index);
 | |
|       }
 | |
|     } else
 | |
|     {
 | |
|       for (int i=0; i<n; ++i)
 | |
|       {
 | |
|         int index = (data[dataIndexFactor*i]-range.lower)*posToIndexFactor;
 | |
|         if (index < 0)
 | |
|           index = 0;
 | |
|         else if (index >= mLevelCount)
 | |
|           index = mLevelCount-1;
 | |
|         scanLine[i] = mColorBuffer.at(index);
 | |
|       }
 | |
|     }
 | |
|   } else // logarithmic == true
 | |
|   {
 | |
|     if (mPeriodic)
 | |
|     {
 | |
|       for (int i=0; i<n; ++i)
 | |
|       {
 | |
|         int index = (int)(qLn(data[dataIndexFactor*i]/range.lower)/qLn(range.upper/range.lower)*(mLevelCount-1)) % mLevelCount;
 | |
|         if (index < 0)
 | |
|           index += mLevelCount;
 | |
|         scanLine[i] = mColorBuffer.at(index);
 | |
|       }
 | |
|     } else
 | |
|     {
 | |
|       for (int i=0; i<n; ++i)
 | |
|       {
 | |
|         int index = qLn(data[dataIndexFactor*i]/range.lower)/qLn(range.upper/range.lower)*(mLevelCount-1);
 | |
|         if (index < 0)
 | |
|           index = 0;
 | |
|         else if (index >= mLevelCount)
 | |
|           index = mLevelCount-1;
 | |
|         scanLine[i] = mColorBuffer.at(index);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
| 
 | |
|   Additionally to the other overload of \ref colorize, this method takes the array \a alpha, which
 | |
|   has the same size and structure as \a data and encodes the alpha information per data point.
 | |
| 
 | |
|   The QRgb values that are placed in \a scanLine have their r, g and b components premultiplied
 | |
|   with alpha (see QImage::Format_ARGB32_Premultiplied).
 | |
| */
 | |
| void QCPColorGradient::colorize(const double *data, const unsigned char *alpha, const QCPRange &range, QRgb *scanLine, int n, int dataIndexFactor, bool logarithmic)
 | |
| {
 | |
|   // If you change something here, make sure to also adapt color() and the other colorize() overload
 | |
|   if (!data)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "null pointer given as data";
 | |
|     return;
 | |
|   }
 | |
|   if (!alpha)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "null pointer given as alpha";
 | |
|     return;
 | |
|   }
 | |
|   if (!scanLine)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "null pointer given as scanLine";
 | |
|     return;
 | |
|   }
 | |
|   if (mColorBufferInvalidated)
 | |
|     updateColorBuffer();
 | |
|   
 | |
|   if (!logarithmic)
 | |
|   {
 | |
|     const double posToIndexFactor = (mLevelCount-1)/range.size();
 | |
|     if (mPeriodic)
 | |
|     {
 | |
|       for (int i=0; i<n; ++i)
 | |
|       {
 | |
|         int index = (int)((data[dataIndexFactor*i]-range.lower)*posToIndexFactor) % mLevelCount;
 | |
|         if (index < 0)
 | |
|           index += mLevelCount;
 | |
|         if (alpha[dataIndexFactor*i] == 255)
 | |
|         {
 | |
|           scanLine[i] = mColorBuffer.at(index);
 | |
|         } else
 | |
|         {
 | |
|           const QRgb rgb = mColorBuffer.at(index);
 | |
|           const float alphaF = alpha[dataIndexFactor*i]/255.0f;
 | |
|           scanLine[i] = qRgba(qRed(rgb)*alphaF, qGreen(rgb)*alphaF, qBlue(rgb)*alphaF, qAlpha(rgb)*alphaF);
 | |
|         }
 | |
|       }
 | |
|     } else
 | |
|     {
 | |
|       for (int i=0; i<n; ++i)
 | |
|       {
 | |
|         int index = (data[dataIndexFactor*i]-range.lower)*posToIndexFactor;
 | |
|         if (index < 0)
 | |
|           index = 0;
 | |
|         else if (index >= mLevelCount)
 | |
|           index = mLevelCount-1;
 | |
|         if (alpha[dataIndexFactor*i] == 255)
 | |
|         {
 | |
|           scanLine[i] = mColorBuffer.at(index);
 | |
|         } else
 | |
|         {
 | |
|           const QRgb rgb = mColorBuffer.at(index);
 | |
|           const float alphaF = alpha[dataIndexFactor*i]/255.0f;
 | |
|           scanLine[i] = qRgba(qRed(rgb)*alphaF, qGreen(rgb)*alphaF, qBlue(rgb)*alphaF, qAlpha(rgb)*alphaF);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   } else // logarithmic == true
 | |
|   {
 | |
|     if (mPeriodic)
 | |
|     {
 | |
|       for (int i=0; i<n; ++i)
 | |
|       {
 | |
|         int index = (int)(qLn(data[dataIndexFactor*i]/range.lower)/qLn(range.upper/range.lower)*(mLevelCount-1)) % mLevelCount;
 | |
|         if (index < 0)
 | |
|           index += mLevelCount;
 | |
|         if (alpha[dataIndexFactor*i] == 255)
 | |
|         {
 | |
|           scanLine[i] = mColorBuffer.at(index);
 | |
|         } else
 | |
|         {
 | |
|           const QRgb rgb = mColorBuffer.at(index);
 | |
|           const float alphaF = alpha[dataIndexFactor*i]/255.0f;
 | |
|           scanLine[i] = qRgba(qRed(rgb)*alphaF, qGreen(rgb)*alphaF, qBlue(rgb)*alphaF, qAlpha(rgb)*alphaF);
 | |
|         }
 | |
|       }
 | |
|     } else
 | |
|     {
 | |
|       for (int i=0; i<n; ++i)
 | |
|       {
 | |
|         int index = qLn(data[dataIndexFactor*i]/range.lower)/qLn(range.upper/range.lower)*(mLevelCount-1);
 | |
|         if (index < 0)
 | |
|           index = 0;
 | |
|         else if (index >= mLevelCount)
 | |
|           index = mLevelCount-1;
 | |
|         if (alpha[dataIndexFactor*i] == 255)
 | |
|         {
 | |
|           scanLine[i] = mColorBuffer.at(index);
 | |
|         } else
 | |
|         {
 | |
|           const QRgb rgb = mColorBuffer.at(index);
 | |
|           const float alphaF = alpha[dataIndexFactor*i]/255.0f;
 | |
|           scanLine[i] = qRgba(qRed(rgb)*alphaF, qGreen(rgb)*alphaF, qBlue(rgb)*alphaF, qAlpha(rgb)*alphaF);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   This method is used to colorize a single data value given in \a position, to colors. The data
 | |
|   range that shall be used for mapping the data value to the gradient is passed in \a range. \a
 | |
|   logarithmic indicates whether the data value shall be mapped to a color logarithmically.
 | |
| 
 | |
|   If an entire array of data values shall be converted, rather use \ref colorize, for better
 | |
|   performance.
 | |
| 
 | |
|   The returned QRgb has its r, g and b components premultiplied with alpha (see
 | |
|   QImage::Format_ARGB32_Premultiplied).
 | |
| */
 | |
| QRgb QCPColorGradient::color(double position, const QCPRange &range, bool logarithmic)
 | |
| {
 | |
|   // If you change something here, make sure to also adapt ::colorize()
 | |
|   if (mColorBufferInvalidated)
 | |
|     updateColorBuffer();
 | |
|   int index = 0;
 | |
|   if (!logarithmic)
 | |
|     index = (position-range.lower)*(mLevelCount-1)/range.size();
 | |
|   else
 | |
|     index = qLn(position/range.lower)/qLn(range.upper/range.lower)*(mLevelCount-1);
 | |
|   if (mPeriodic)
 | |
|   {
 | |
|     index = index % mLevelCount;
 | |
|     if (index < 0)
 | |
|       index += mLevelCount;
 | |
|   } else
 | |
|   {
 | |
|     if (index < 0)
 | |
|       index = 0;
 | |
|     else if (index >= mLevelCount)
 | |
|       index = mLevelCount-1;
 | |
|   }
 | |
|   return mColorBuffer.at(index);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Clears the current color stops and loads the specified \a preset. A preset consists of predefined
 | |
|   color stops and the corresponding color interpolation method.
 | |
|   
 | |
|   The available presets are:
 | |
|   \image html QCPColorGradient.png
 | |
| */
 | |
| void QCPColorGradient::loadPreset(GradientPreset preset)
 | |
| {
 | |
|   clearColorStops();
 | |
|   switch (preset)
 | |
|   {
 | |
|     case gpGrayscale:
 | |
|       setColorInterpolation(ciRGB);
 | |
|       setColorStopAt(0, Qt::black);
 | |
|       setColorStopAt(1, Qt::white);
 | |
|       break;
 | |
|     case gpHot:
 | |
|       setColorInterpolation(ciRGB);
 | |
|       setColorStopAt(0, QColor(50, 0, 0));
 | |
|       setColorStopAt(0.2, QColor(180, 10, 0));
 | |
|       setColorStopAt(0.4, QColor(245, 50, 0));
 | |
|       setColorStopAt(0.6, QColor(255, 150, 10));
 | |
|       setColorStopAt(0.8, QColor(255, 255, 50));
 | |
|       setColorStopAt(1, QColor(255, 255, 255));
 | |
|       break;
 | |
|     case gpCold:
 | |
|       setColorInterpolation(ciRGB);
 | |
|       setColorStopAt(0, QColor(0, 0, 50));
 | |
|       setColorStopAt(0.2, QColor(0, 10, 180));
 | |
|       setColorStopAt(0.4, QColor(0, 50, 245));
 | |
|       setColorStopAt(0.6, QColor(10, 150, 255));
 | |
|       setColorStopAt(0.8, QColor(50, 255, 255));
 | |
|       setColorStopAt(1, QColor(255, 255, 255));
 | |
|       break;
 | |
|     case gpNight:
 | |
|       setColorInterpolation(ciHSV);
 | |
|       setColorStopAt(0, QColor(10, 20, 30));
 | |
|       setColorStopAt(1, QColor(250, 255, 250));
 | |
|       break;
 | |
|     case gpCandy:
 | |
|       setColorInterpolation(ciHSV);
 | |
|       setColorStopAt(0, QColor(0, 0, 255));
 | |
|       setColorStopAt(1, QColor(255, 250, 250));
 | |
|       break;
 | |
|     case gpGeography:
 | |
|       setColorInterpolation(ciRGB);
 | |
|       setColorStopAt(0, QColor(70, 170, 210));
 | |
|       setColorStopAt(0.20, QColor(90, 160, 180));
 | |
|       setColorStopAt(0.25, QColor(45, 130, 175));
 | |
|       setColorStopAt(0.30, QColor(100, 140, 125));
 | |
|       setColorStopAt(0.5, QColor(100, 140, 100));
 | |
|       setColorStopAt(0.6, QColor(130, 145, 120));
 | |
|       setColorStopAt(0.7, QColor(140, 130, 120));
 | |
|       setColorStopAt(0.9, QColor(180, 190, 190));
 | |
|       setColorStopAt(1, QColor(210, 210, 230));
 | |
|       break;
 | |
|     case gpIon:
 | |
|       setColorInterpolation(ciHSV);
 | |
|       setColorStopAt(0, QColor(50, 10, 10));
 | |
|       setColorStopAt(0.45, QColor(0, 0, 255));
 | |
|       setColorStopAt(0.8, QColor(0, 255, 255));
 | |
|       setColorStopAt(1, QColor(0, 255, 0));
 | |
|       break;
 | |
|     case gpThermal:
 | |
|       setColorInterpolation(ciRGB);
 | |
|       setColorStopAt(0, QColor(0, 0, 50));
 | |
|       setColorStopAt(0.15, QColor(20, 0, 120));
 | |
|       setColorStopAt(0.33, QColor(200, 30, 140));
 | |
|       setColorStopAt(0.6, QColor(255, 100, 0));
 | |
|       setColorStopAt(0.85, QColor(255, 255, 40));
 | |
|       setColorStopAt(1, QColor(255, 255, 255));
 | |
|       break;
 | |
|     case gpPolar:
 | |
|       setColorInterpolation(ciRGB);
 | |
|       setColorStopAt(0, QColor(50, 255, 255));
 | |
|       setColorStopAt(0.18, QColor(10, 70, 255));
 | |
|       setColorStopAt(0.28, QColor(10, 10, 190));
 | |
|       setColorStopAt(0.5, QColor(0, 0, 0));
 | |
|       setColorStopAt(0.72, QColor(190, 10, 10));
 | |
|       setColorStopAt(0.82, QColor(255, 70, 10));
 | |
|       setColorStopAt(1, QColor(255, 255, 50));
 | |
|       break;
 | |
|     case gpSpectrum:
 | |
|       setColorInterpolation(ciHSV);
 | |
|       setColorStopAt(0, QColor(50, 0, 50));
 | |
|       setColorStopAt(0.15, QColor(0, 0, 255));
 | |
|       setColorStopAt(0.35, QColor(0, 255, 255));
 | |
|       setColorStopAt(0.6, QColor(255, 255, 0));
 | |
|       setColorStopAt(0.75, QColor(255, 30, 0));
 | |
|       setColorStopAt(1, QColor(50, 0, 0));
 | |
|       break;
 | |
|     case gpJet:
 | |
|       setColorInterpolation(ciRGB);
 | |
|       setColorStopAt(0, QColor(0, 0, 100));
 | |
|       setColorStopAt(0.15, QColor(0, 50, 255));
 | |
|       setColorStopAt(0.35, QColor(0, 255, 255));
 | |
|       setColorStopAt(0.65, QColor(255, 255, 0));
 | |
|       setColorStopAt(0.85, QColor(255, 30, 0));
 | |
|       setColorStopAt(1, QColor(100, 0, 0));
 | |
|       break;
 | |
|     case gpHues:
 | |
|       setColorInterpolation(ciHSV);
 | |
|       setColorStopAt(0, QColor(255, 0, 0));
 | |
|       setColorStopAt(1.0/3.0, QColor(0, 0, 255));
 | |
|       setColorStopAt(2.0/3.0, QColor(0, 255, 0));
 | |
|       setColorStopAt(1, QColor(255, 0, 0));
 | |
|       break;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Clears all color stops.
 | |
|   
 | |
|   \see setColorStops, setColorStopAt
 | |
| */
 | |
| void QCPColorGradient::clearColorStops()
 | |
| {
 | |
|   mColorStops.clear();
 | |
|   mColorBufferInvalidated = true;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns an inverted gradient. The inverted gradient has all properties as this \ref
 | |
|   QCPColorGradient, but the order of the color stops is inverted.
 | |
|   
 | |
|   \see setColorStops, setColorStopAt
 | |
| */
 | |
| QCPColorGradient QCPColorGradient::inverted() const
 | |
| {
 | |
|   QCPColorGradient result(*this);
 | |
|   result.clearColorStops();
 | |
|   for (QMap<double, QColor>::const_iterator it=mColorStops.constBegin(); it!=mColorStops.constEnd(); ++it)
 | |
|     result.setColorStopAt(1.0-it.key(), it.value());
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns true if the color gradient uses transparency, i.e. if any of the configured color stops
 | |
|   has an alpha value below 255.
 | |
| */
 | |
| bool QCPColorGradient::stopsUseAlpha() const
 | |
| {
 | |
|   for (QMap<double, QColor>::const_iterator it=mColorStops.constBegin(); it!=mColorStops.constEnd(); ++it)
 | |
|   {
 | |
|     if (it.value().alpha() < 255)
 | |
|       return true;
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Updates the internal color buffer which will be used by \ref colorize and \ref color, to quickly
 | |
|   convert positions to colors. This is where the interpolation between color stops is calculated.
 | |
| */
 | |
| void QCPColorGradient::updateColorBuffer()
 | |
| {
 | |
|   if (mColorBuffer.size() != mLevelCount)
 | |
|     mColorBuffer.resize(mLevelCount);
 | |
|   if (mColorStops.size() > 1)
 | |
|   {
 | |
|     double indexToPosFactor = 1.0/(double)(mLevelCount-1);
 | |
|     const bool useAlpha = stopsUseAlpha();
 | |
|     for (int i=0; i<mLevelCount; ++i)
 | |
|     {
 | |
|       double position = i*indexToPosFactor;
 | |
|       QMap<double, QColor>::const_iterator it = mColorStops.lowerBound(position);
 | |
|       if (it == mColorStops.constEnd()) // position is on or after last stop, use color of last stop
 | |
|       {
 | |
|         mColorBuffer[i] = (it-1).value().rgba();
 | |
|       } else if (it == mColorStops.constBegin()) // position is on or before first stop, use color of first stop
 | |
|       {
 | |
|         mColorBuffer[i] = it.value().rgba();
 | |
|       } else // position is in between stops (or on an intermediate stop), interpolate color
 | |
|       {
 | |
|         QMap<double, QColor>::const_iterator high = it;
 | |
|         QMap<double, QColor>::const_iterator low = it-1;
 | |
|         double t = (position-low.key())/(high.key()-low.key()); // interpolation factor 0..1
 | |
|         switch (mColorInterpolation)
 | |
|         {
 | |
|           case ciRGB:
 | |
|           {
 | |
|             if (useAlpha)
 | |
|             {
 | |
|               const int alpha = (1-t)*low.value().alpha() + t*high.value().alpha();
 | |
|               const float alphaPremultiplier = alpha/255.0f; // since we use QImage::Format_ARGB32_Premultiplied
 | |
|               mColorBuffer[i] = qRgba(((1-t)*low.value().red() + t*high.value().red())*alphaPremultiplier,
 | |
|                                       ((1-t)*low.value().green() + t*high.value().green())*alphaPremultiplier,
 | |
|                                       ((1-t)*low.value().blue() + t*high.value().blue())*alphaPremultiplier,
 | |
|                                       alpha);
 | |
|             } else
 | |
|             {
 | |
|               mColorBuffer[i] = qRgb(((1-t)*low.value().red() + t*high.value().red()),
 | |
|                                      ((1-t)*low.value().green() + t*high.value().green()),
 | |
|                                      ((1-t)*low.value().blue() + t*high.value().blue()));
 | |
|             }
 | |
|             break;
 | |
|           }
 | |
|           case ciHSV:
 | |
|           {
 | |
|             QColor lowHsv = low.value().toHsv();
 | |
|             QColor highHsv = high.value().toHsv();
 | |
|             double hue = 0;
 | |
|             double hueDiff = highHsv.hueF()-lowHsv.hueF();
 | |
|             if (hueDiff > 0.5)
 | |
|               hue = lowHsv.hueF() - t*(1.0-hueDiff);
 | |
|             else if (hueDiff < -0.5)
 | |
|               hue = lowHsv.hueF() + t*(1.0+hueDiff);
 | |
|             else
 | |
|               hue = lowHsv.hueF() + t*hueDiff;
 | |
|             if (hue < 0) hue += 1.0;
 | |
|             else if (hue >= 1.0) hue -= 1.0;
 | |
|             if (useAlpha)
 | |
|             {
 | |
|               const QRgb rgb = QColor::fromHsvF(hue,
 | |
|                                                 (1-t)*lowHsv.saturationF() + t*highHsv.saturationF(),
 | |
|                                                 (1-t)*lowHsv.valueF() + t*highHsv.valueF()).rgb();
 | |
|               const float alpha = (1-t)*lowHsv.alphaF() + t*highHsv.alphaF();
 | |
|               mColorBuffer[i] = qRgba(qRed(rgb)*alpha, qGreen(rgb)*alpha, qBlue(rgb)*alpha, 255*alpha);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|               mColorBuffer[i] = QColor::fromHsvF(hue,
 | |
|                                                  (1-t)*lowHsv.saturationF() + t*highHsv.saturationF(),
 | |
|                                                  (1-t)*lowHsv.valueF() + t*highHsv.valueF()).rgb();
 | |
|             }
 | |
|             break;
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   } else if (mColorStops.size() == 1)
 | |
|   {
 | |
|     const QRgb rgb = mColorStops.constBegin().value().rgb();
 | |
|     const float alpha = mColorStops.constBegin().value().alphaF();
 | |
|     mColorBuffer.fill(qRgba(qRed(rgb)*alpha, qGreen(rgb)*alpha, qBlue(rgb)*alpha, 255*alpha));
 | |
|   } else // mColorStops is empty, fill color buffer with black
 | |
|   {
 | |
|     mColorBuffer.fill(qRgb(0, 0, 0));
 | |
|   }
 | |
|   mColorBufferInvalidated = false;
 | |
| }
 | |
| /* end of 'src/colorgradient.cpp' */
 | |
| 
 | |
| 
 | |
| /* including file 'src/selectiondecorator-bracket.cpp', size 12313           */
 | |
| /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPSelectionDecoratorBracket
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPSelectionDecoratorBracket
 | |
|   \brief A selection decorator which draws brackets around each selected data segment
 | |
|   
 | |
|   Additionally to the regular highlighting of selected segments via color, fill and scatter style,
 | |
|   this \ref QCPSelectionDecorator subclass draws markers at the begin and end of each selected data
 | |
|   segment of the plottable.
 | |
|   
 | |
|   The shape of the markers can be controlled with \ref setBracketStyle, \ref setBracketWidth and
 | |
|   \ref setBracketHeight. The color/fill can be controlled with \ref setBracketPen and \ref
 | |
|   setBracketBrush.
 | |
|   
 | |
|   To introduce custom bracket styles, it is only necessary to sublcass \ref
 | |
|   QCPSelectionDecoratorBracket and reimplement \ref drawBracket. The rest will be managed by the
 | |
|   base class.
 | |
| */
 | |
| 
 | |
| /*!
 | |
|   Creates a new QCPSelectionDecoratorBracket instance with default values.
 | |
| */
 | |
| QCPSelectionDecoratorBracket::QCPSelectionDecoratorBracket() :
 | |
|   mBracketPen(QPen(Qt::black)),
 | |
|   mBracketBrush(Qt::NoBrush),
 | |
|   mBracketWidth(5),
 | |
|   mBracketHeight(50),
 | |
|   mBracketStyle(bsSquareBracket),
 | |
|   mTangentToData(false),
 | |
|   mTangentAverage(2)
 | |
| {
 | |
|   
 | |
| }
 | |
| 
 | |
| QCPSelectionDecoratorBracket::~QCPSelectionDecoratorBracket()
 | |
| {
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the pen that will be used to draw the brackets at the beginning and end of each selected
 | |
|   data segment.
 | |
| */
 | |
| void QCPSelectionDecoratorBracket::setBracketPen(const QPen &pen)
 | |
| {
 | |
|   mBracketPen = pen;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the brush that will be used to draw the brackets at the beginning and end of each selected
 | |
|   data segment.
 | |
| */
 | |
| void QCPSelectionDecoratorBracket::setBracketBrush(const QBrush &brush)
 | |
| {
 | |
|   mBracketBrush = brush;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the width of the drawn bracket. The width dimension is always parallel to the key axis of
 | |
|   the data, or the tangent direction of the current data slope, if \ref setTangentToData is
 | |
|   enabled.
 | |
| */
 | |
| void QCPSelectionDecoratorBracket::setBracketWidth(int width)
 | |
| {
 | |
|   mBracketWidth = width;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the height of the drawn bracket. The height dimension is always perpendicular to the key axis
 | |
|   of the data, or the tangent direction of the current data slope, if \ref setTangentToData is
 | |
|   enabled.
 | |
| */
 | |
| void QCPSelectionDecoratorBracket::setBracketHeight(int height)
 | |
| {
 | |
|   mBracketHeight = height;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the shape that the bracket/marker will have.
 | |
|   
 | |
|   \see setBracketWidth, setBracketHeight
 | |
| */
 | |
| void QCPSelectionDecoratorBracket::setBracketStyle(QCPSelectionDecoratorBracket::BracketStyle style)
 | |
| {
 | |
|   mBracketStyle = style;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets whether the brackets will be rotated such that they align with the slope of the data at the
 | |
|   position that they appear in.
 | |
|   
 | |
|   For noisy data, it might be more visually appealing to average the slope over multiple data
 | |
|   points. This can be configured via \ref setTangentAverage.
 | |
| */
 | |
| void QCPSelectionDecoratorBracket::setTangentToData(bool enabled)
 | |
| {
 | |
|   mTangentToData = enabled;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Controls over how many data points the slope shall be averaged, when brackets shall be aligned
 | |
|   with the data (if \ref setTangentToData is true).
 | |
|   
 | |
|   From the position of the bracket, \a pointCount points towards the selected data range will be
 | |
|   taken into account. The smallest value of \a pointCount is 1, which is effectively equivalent to
 | |
|   disabling \ref setTangentToData.
 | |
| */
 | |
| void QCPSelectionDecoratorBracket::setTangentAverage(int pointCount)
 | |
| {
 | |
|   mTangentAverage = pointCount;
 | |
|   if (mTangentAverage < 1)
 | |
|     mTangentAverage = 1;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Draws the bracket shape with \a painter. The parameter \a direction is either -1 or 1 and
 | |
|   indicates whether the bracket shall point to the left or the right (i.e. is a closing or opening
 | |
|   bracket, respectively).
 | |
|   
 | |
|   The passed \a painter already contains all transformations that are necessary to position and
 | |
|   rotate the bracket appropriately. Painting operations can be performed as if drawing upright
 | |
|   brackets on flat data with horizontal key axis, with (0, 0) being the center of the bracket.
 | |
|   
 | |
|   If you wish to sublcass \ref QCPSelectionDecoratorBracket in order to provide custom bracket
 | |
|   shapes (see \ref QCPSelectionDecoratorBracket::bsUserStyle), this is the method you should
 | |
|   reimplement.
 | |
| */
 | |
| void QCPSelectionDecoratorBracket::drawBracket(QCPPainter *painter, int direction) const
 | |
| {
 | |
|   switch (mBracketStyle)
 | |
|   {
 | |
|     case bsSquareBracket:
 | |
|     {
 | |
|       painter->drawLine(QLineF(mBracketWidth*direction, -mBracketHeight*0.5, 0, -mBracketHeight*0.5));
 | |
|       painter->drawLine(QLineF(mBracketWidth*direction, mBracketHeight*0.5, 0, mBracketHeight*0.5));
 | |
|       painter->drawLine(QLineF(0, -mBracketHeight*0.5, 0, mBracketHeight*0.5));
 | |
|       break;
 | |
|     }
 | |
|     case bsHalfEllipse:
 | |
|     {
 | |
|       painter->drawArc(-mBracketWidth*0.5, -mBracketHeight*0.5, mBracketWidth, mBracketHeight, -90*16, -180*16*direction);
 | |
|       break;
 | |
|     }
 | |
|     case bsEllipse:
 | |
|     {
 | |
|       painter->drawEllipse(-mBracketWidth*0.5, -mBracketHeight*0.5, mBracketWidth, mBracketHeight);
 | |
|       break;
 | |
|     }
 | |
|     case bsPlus:
 | |
|     {
 | |
|       painter->drawLine(QLineF(0, -mBracketHeight*0.5, 0, mBracketHeight*0.5));
 | |
|       painter->drawLine(QLineF(-mBracketWidth*0.5, 0, mBracketWidth*0.5, 0));
 | |
|       break;
 | |
|     }
 | |
|     default:
 | |
|     {
 | |
|       qDebug() << Q_FUNC_INFO << "unknown/custom bracket style can't be handeld by default implementation:" << static_cast<int>(mBracketStyle);
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Draws the bracket decoration on the data points at the begin and end of each selected data
 | |
|   segment given in \a seletion.
 | |
|   
 | |
|   It uses the method \ref drawBracket to actually draw the shapes.
 | |
|   
 | |
|   \seebaseclassmethod
 | |
| */
 | |
| void QCPSelectionDecoratorBracket::drawDecoration(QCPPainter *painter, QCPDataSelection selection)
 | |
| {
 | |
|   if (!mPlottable || selection.isEmpty()) return;
 | |
|   
 | |
|   if (QCPPlottableInterface1D *interface1d = mPlottable->interface1D())
 | |
|   {
 | |
|     foreach (const QCPDataRange &dataRange, selection.dataRanges())
 | |
|     {
 | |
|       // determine position and (if tangent mode is enabled) angle of brackets:
 | |
|       int openBracketDir = (mPlottable->keyAxis() && !mPlottable->keyAxis()->rangeReversed()) ? 1 : -1;
 | |
|       int closeBracketDir = -openBracketDir;
 | |
|       QPointF openBracketPos = getPixelCoordinates(interface1d, dataRange.begin());
 | |
|       QPointF closeBracketPos = getPixelCoordinates(interface1d, dataRange.end()-1);
 | |
|       double openBracketAngle = 0;
 | |
|       double closeBracketAngle = 0;
 | |
|       if (mTangentToData)
 | |
|       {
 | |
|         openBracketAngle = getTangentAngle(interface1d, dataRange.begin(), openBracketDir);
 | |
|         closeBracketAngle = getTangentAngle(interface1d, dataRange.end()-1, closeBracketDir);
 | |
|       }
 | |
|       // draw opening bracket:
 | |
|       QTransform oldTransform = painter->transform();
 | |
|       painter->setPen(mBracketPen);
 | |
|       painter->setBrush(mBracketBrush);
 | |
|       painter->translate(openBracketPos);
 | |
|       painter->rotate(openBracketAngle/M_PI*180.0);
 | |
|       drawBracket(painter, openBracketDir);
 | |
|       painter->setTransform(oldTransform);
 | |
|       // draw closing bracket:
 | |
|       painter->setPen(mBracketPen);
 | |
|       painter->setBrush(mBracketBrush);
 | |
|       painter->translate(closeBracketPos);
 | |
|       painter->rotate(closeBracketAngle/M_PI*180.0);
 | |
|       drawBracket(painter, closeBracketDir);
 | |
|       painter->setTransform(oldTransform);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   If \ref setTangentToData is enabled, brackets need to be rotated according to the data slope.
 | |
|   This method returns the angle in radians by which a bracket at the given \a dataIndex must be
 | |
|   rotated.
 | |
|   
 | |
|   The parameter \a direction must be set to either -1 or 1, representing whether it is an opening
 | |
|   or closing bracket. Since for slope calculation multiple data points are required, this defines
 | |
|   the direction in which the algorithm walks, starting at \a dataIndex, to average those data
 | |
|   points. (see \ref setTangentToData and \ref setTangentAverage)
 | |
|   
 | |
|   \a interface1d is the interface to the plottable's data which is used to query data coordinates.
 | |
| */
 | |
| double QCPSelectionDecoratorBracket::getTangentAngle(const QCPPlottableInterface1D *interface1d, int dataIndex, int direction) const
 | |
| {
 | |
|   if (!interface1d || dataIndex < 0 || dataIndex >= interface1d->dataCount())
 | |
|     return 0;
 | |
|   direction = direction < 0 ? -1 : 1; // enforce direction is either -1 or 1
 | |
|   
 | |
|   // how many steps we can actually go from index in the given direction without exceeding data bounds:
 | |
|   int averageCount;
 | |
|   if (direction < 0)
 | |
|     averageCount = qMin(mTangentAverage, dataIndex);
 | |
|   else
 | |
|     averageCount = qMin(mTangentAverage, interface1d->dataCount()-1-dataIndex);
 | |
|   qDebug() << averageCount;
 | |
|   // calculate point average of averageCount points:
 | |
|   QVector<QPointF> points(averageCount);
 | |
|   QPointF pointsAverage;
 | |
|   int currentIndex = dataIndex;
 | |
|   for (int i=0; i<averageCount; ++i)
 | |
|   {
 | |
|     points[i] = getPixelCoordinates(interface1d, currentIndex);
 | |
|     pointsAverage += points[i];
 | |
|     currentIndex += direction;
 | |
|   }
 | |
|   pointsAverage /= (double)averageCount;
 | |
|   
 | |
|   // calculate slope of linear regression through points:
 | |
|   double numSum = 0;
 | |
|   double denomSum = 0;
 | |
|   for (int i=0; i<averageCount; ++i)
 | |
|   {
 | |
|     const double dx = points.at(i).x()-pointsAverage.x();
 | |
|     const double dy = points.at(i).y()-pointsAverage.y();
 | |
|     numSum += dx*dy;
 | |
|     denomSum += dx*dx;
 | |
|   }
 | |
|   if (!qFuzzyIsNull(denomSum) && !qFuzzyIsNull(numSum))
 | |
|   {
 | |
|     return qAtan2(numSum, denomSum);
 | |
|   } else // undetermined angle, probably mTangentAverage == 1, so using only one data point
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns the pixel coordinates of the data point at \a dataIndex, using \a interface1d to access
 | |
|   the data points.
 | |
| */
 | |
| QPointF QCPSelectionDecoratorBracket::getPixelCoordinates(const QCPPlottableInterface1D *interface1d, int dataIndex) const
 | |
| {
 | |
|   QCPAxis *keyAxis = mPlottable->keyAxis();
 | |
|   QCPAxis *valueAxis = mPlottable->valueAxis();
 | |
|   if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return QPointF(0, 0); }
 | |
|   
 | |
|   if (keyAxis->orientation() == Qt::Horizontal)
 | |
|     return QPointF(keyAxis->coordToPixel(interface1d->dataMainKey(dataIndex)), valueAxis->coordToPixel(interface1d->dataMainValue(dataIndex)));
 | |
|   else
 | |
|     return QPointF(valueAxis->coordToPixel(interface1d->dataMainValue(dataIndex)), keyAxis->coordToPixel(interface1d->dataMainKey(dataIndex)));
 | |
| }
 | |
| /* end of 'src/selectiondecorator-bracket.cpp' */
 | |
| 
 | |
| 
 | |
| /* including file 'src/layoutelements/layoutelement-axisrect.cpp', size 47509 */
 | |
| /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200  */
 | |
| 
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPAxisRect
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPAxisRect
 | |
|   \brief Holds multiple axes and arranges them in a rectangular shape.
 | |
|   
 | |
|   This class represents an axis rect, a rectangular area that is bounded on all sides with an
 | |
|   arbitrary number of axes.
 | |
|   
 | |
|   Initially QCustomPlot has one axis rect, accessible via QCustomPlot::axisRect(). However, the
 | |
|   layout system allows to have multiple axis rects, e.g. arranged in a grid layout
 | |
|   (QCustomPlot::plotLayout).
 | |
|   
 | |
|   By default, QCPAxisRect comes with four axes, at bottom, top, left and right. They can be
 | |
|   accessed via \ref axis by providing the respective axis type (\ref QCPAxis::AxisType) and index.
 | |
|   If you need all axes in the axis rect, use \ref axes. The top and right axes are set to be
 | |
|   invisible initially (QCPAxis::setVisible). To add more axes to a side, use \ref addAxis or \ref
 | |
|   addAxes. To remove an axis, use \ref removeAxis.
 | |
|   
 | |
|   The axis rect layerable itself only draws a background pixmap or color, if specified (\ref
 | |
|   setBackground). It is placed on the "background" layer initially (see \ref QCPLayer for an
 | |
|   explanation of the QCustomPlot layer system). The axes that are held by the axis rect can be
 | |
|   placed on other layers, independently of the axis rect.
 | |
|   
 | |
|   Every axis rect has a child layout of type \ref QCPLayoutInset. It is accessible via \ref
 | |
|   insetLayout and can be used to have other layout elements (or even other layouts with multiple
 | |
|   elements) hovering inside the axis rect.
 | |
|   
 | |
|   If an axis rect is clicked and dragged, it processes this by moving certain axis ranges. The
 | |
|   behaviour can be controlled with \ref setRangeDrag and \ref setRangeDragAxes. If the mouse wheel
 | |
|   is scrolled while the cursor is on the axis rect, certain axes are scaled. This is controllable
 | |
|   via \ref setRangeZoom, \ref setRangeZoomAxes and \ref setRangeZoomFactor. These interactions are
 | |
|   only enabled if \ref QCustomPlot::setInteractions contains \ref QCP::iRangeDrag and \ref
 | |
|   QCP::iRangeZoom.
 | |
|   
 | |
|   \image html AxisRectSpacingOverview.png
 | |
|   <center>Overview of the spacings and paddings that define the geometry of an axis. The dashed
 | |
|   line on the far left indicates the viewport/widget border.</center>
 | |
| */
 | |
| 
 | |
| /* start documentation of inline functions */
 | |
| 
 | |
| /*! \fn QCPLayoutInset *QCPAxisRect::insetLayout() const
 | |
|   
 | |
|   Returns the inset layout of this axis rect. It can be used to place other layout elements (or
 | |
|   even layouts with multiple other elements) inside/on top of an axis rect.
 | |
|   
 | |
|   \see QCPLayoutInset
 | |
| */
 | |
| 
 | |
| /*! \fn int QCPAxisRect::left() const
 | |
|   
 | |
|   Returns the pixel position of the left border of this axis rect. Margins are not taken into
 | |
|   account here, so the returned value is with respect to the inner \ref rect.
 | |
| */
 | |
| 
 | |
| /*! \fn int QCPAxisRect::right() const
 | |
|   
 | |
|   Returns the pixel position of the right border of this axis rect. Margins are not taken into
 | |
|   account here, so the returned value is with respect to the inner \ref rect.
 | |
| */
 | |
| 
 | |
| /*! \fn int QCPAxisRect::top() const
 | |
|   
 | |
|   Returns the pixel position of the top border of this axis rect. Margins are not taken into
 | |
|   account here, so the returned value is with respect to the inner \ref rect.
 | |
| */
 | |
| 
 | |
| /*! \fn int QCPAxisRect::bottom() const
 | |
|   
 | |
|   Returns the pixel position of the bottom border of this axis rect. Margins are not taken into
 | |
|   account here, so the returned value is with respect to the inner \ref rect.
 | |
| */
 | |
| 
 | |
| /*! \fn int QCPAxisRect::width() const
 | |
|   
 | |
|   Returns the pixel width of this axis rect. Margins are not taken into account here, so the
 | |
|   returned value is with respect to the inner \ref rect.
 | |
| */
 | |
| 
 | |
| /*! \fn int QCPAxisRect::height() const
 | |
|   
 | |
|   Returns the pixel height of this axis rect. Margins are not taken into account here, so the
 | |
|   returned value is with respect to the inner \ref rect.
 | |
| */
 | |
| 
 | |
| /*! \fn QSize QCPAxisRect::size() const
 | |
|   
 | |
|   Returns the pixel size of this axis rect. Margins are not taken into account here, so the
 | |
|   returned value is with respect to the inner \ref rect.
 | |
| */
 | |
| 
 | |
| /*! \fn QPoint QCPAxisRect::topLeft() const
 | |
|   
 | |
|   Returns the top left corner of this axis rect in pixels. Margins are not taken into account here,
 | |
|   so the returned value is with respect to the inner \ref rect.
 | |
| */
 | |
| 
 | |
| /*! \fn QPoint QCPAxisRect::topRight() const
 | |
|   
 | |
|   Returns the top right corner of this axis rect in pixels. Margins are not taken into account
 | |
|   here, so the returned value is with respect to the inner \ref rect.
 | |
| */
 | |
| 
 | |
| /*! \fn QPoint QCPAxisRect::bottomLeft() const
 | |
|   
 | |
|   Returns the bottom left corner of this axis rect in pixels. Margins are not taken into account
 | |
|   here, so the returned value is with respect to the inner \ref rect.
 | |
| */
 | |
| 
 | |
| /*! \fn QPoint QCPAxisRect::bottomRight() const
 | |
|   
 | |
|   Returns the bottom right corner of this axis rect in pixels. Margins are not taken into account
 | |
|   here, so the returned value is with respect to the inner \ref rect.
 | |
| */
 | |
| 
 | |
| /*! \fn QPoint QCPAxisRect::center() const
 | |
|   
 | |
|   Returns the center of this axis rect in pixels. Margins are not taken into account here, so the
 | |
|   returned value is with respect to the inner \ref rect.
 | |
| */
 | |
| 
 | |
| /* end documentation of inline functions */
 | |
| 
 | |
| /*!
 | |
|   Creates a QCPAxisRect instance and sets default values. An axis is added for each of the four
 | |
|   sides, the top and right axes are set invisible initially.
 | |
| */
 | |
| QCPAxisRect::QCPAxisRect(QCustomPlot *parentPlot, bool setupDefaultAxes) :
 | |
|   QCPLayoutElement(parentPlot),
 | |
|   mBackgroundBrush(Qt::NoBrush),
 | |
|   mBackgroundScaled(true),
 | |
|   mBackgroundScaledMode(Qt::KeepAspectRatioByExpanding),
 | |
|   mInsetLayout(new QCPLayoutInset),
 | |
|   mRangeDrag(Qt::Horizontal|Qt::Vertical),
 | |
|   mRangeZoom(Qt::Horizontal|Qt::Vertical),
 | |
|   mRangeZoomFactorHorz(0.85),
 | |
|   mRangeZoomFactorVert(0.85),
 | |
|   mDragging(false)
 | |
| {
 | |
|   mInsetLayout->initializeParentPlot(mParentPlot);
 | |
|   mInsetLayout->setParentLayerable(this);
 | |
|   mInsetLayout->setParent(this);
 | |
|   
 | |
|   setMinimumSize(50, 50);
 | |
|   setMinimumMargins(QMargins(15, 15, 15, 15));
 | |
|   mAxes.insert(QCPAxis::atLeft, QList<QCPAxis*>());
 | |
|   mAxes.insert(QCPAxis::atRight, QList<QCPAxis*>());
 | |
|   mAxes.insert(QCPAxis::atTop, QList<QCPAxis*>());
 | |
|   mAxes.insert(QCPAxis::atBottom, QList<QCPAxis*>());
 | |
|   
 | |
|   if (setupDefaultAxes)
 | |
|   {
 | |
|     QCPAxis *xAxis = addAxis(QCPAxis::atBottom);
 | |
|     QCPAxis *yAxis = addAxis(QCPAxis::atLeft);
 | |
|     QCPAxis *xAxis2 = addAxis(QCPAxis::atTop);
 | |
|     QCPAxis *yAxis2 = addAxis(QCPAxis::atRight);
 | |
|     setRangeDragAxes(xAxis, yAxis);
 | |
|     setRangeZoomAxes(xAxis, yAxis);
 | |
|     xAxis2->setVisible(false);
 | |
|     yAxis2->setVisible(false);
 | |
|     xAxis->grid()->setVisible(true);
 | |
|     yAxis->grid()->setVisible(true);
 | |
|     xAxis2->grid()->setVisible(false);
 | |
|     yAxis2->grid()->setVisible(false);
 | |
|     xAxis2->grid()->setZeroLinePen(Qt::NoPen);
 | |
|     yAxis2->grid()->setZeroLinePen(Qt::NoPen);
 | |
|     xAxis2->grid()->setVisible(false);
 | |
|     yAxis2->grid()->setVisible(false);
 | |
|   }
 | |
| }
 | |
| 
 | |
| QCPAxisRect::~QCPAxisRect()
 | |
| {
 | |
|   delete mInsetLayout;
 | |
|   mInsetLayout = 0;
 | |
|   
 | |
|   QList<QCPAxis*> axesList = axes();
 | |
|   for (int i=0; i<axesList.size(); ++i)
 | |
|     removeAxis(axesList.at(i));
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the number of axes on the axis rect side specified with \a type.
 | |
|   
 | |
|   \see axis
 | |
| */
 | |
| int QCPAxisRect::axisCount(QCPAxis::AxisType type) const
 | |
| {
 | |
|   return mAxes.value(type).size();
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the axis with the given \a index on the axis rect side specified with \a type.
 | |
|   
 | |
|   \see axisCount, axes
 | |
| */
 | |
| QCPAxis *QCPAxisRect::axis(QCPAxis::AxisType type, int index) const
 | |
| {
 | |
|   QList<QCPAxis*> ax(mAxes.value(type));
 | |
|   if (index >= 0 && index < ax.size())
 | |
|   {
 | |
|     return ax.at(index);
 | |
|   } else
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "Axis index out of bounds:" << index;
 | |
|     return 0;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns all axes on the axis rect sides specified with \a types.
 | |
|   
 | |
|   \a types may be a single \ref QCPAxis::AxisType or an <tt>or</tt>-combination, to get the axes of
 | |
|   multiple sides.
 | |
|   
 | |
|   \see axis
 | |
| */
 | |
| QList<QCPAxis*> QCPAxisRect::axes(QCPAxis::AxisTypes types) const
 | |
| {
 | |
|   QList<QCPAxis*> result;
 | |
|   if (types.testFlag(QCPAxis::atLeft))
 | |
|     result << mAxes.value(QCPAxis::atLeft);
 | |
|   if (types.testFlag(QCPAxis::atRight))
 | |
|     result << mAxes.value(QCPAxis::atRight);
 | |
|   if (types.testFlag(QCPAxis::atTop))
 | |
|     result << mAxes.value(QCPAxis::atTop);
 | |
|   if (types.testFlag(QCPAxis::atBottom))
 | |
|     result << mAxes.value(QCPAxis::atBottom);
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Returns all axes of this axis rect.
 | |
| */
 | |
| QList<QCPAxis*> QCPAxisRect::axes() const
 | |
| {
 | |
|   QList<QCPAxis*> result;
 | |
|   QHashIterator<QCPAxis::AxisType, QList<QCPAxis*> > it(mAxes);
 | |
|   while (it.hasNext())
 | |
|   {
 | |
|     it.next();
 | |
|     result << it.value();
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Adds a new axis to the axis rect side specified with \a type, and returns it. If \a axis is 0, a
 | |
|   new QCPAxis instance is created internally. QCustomPlot owns the returned axis, so if you want to
 | |
|   remove an axis, use \ref removeAxis instead of deleting it manually.
 | |
| 
 | |
|   You may inject QCPAxis instances (or sublasses of QCPAxis) by setting \a axis to an axis that was
 | |
|   previously created outside QCustomPlot. It is important to note that QCustomPlot takes ownership
 | |
|   of the axis, so you may not delete it afterwards. Further, the \a axis must have been created
 | |
|   with this axis rect as parent and with the same axis type as specified in \a type. If this is not
 | |
|   the case, a debug output is generated, the axis is not added, and the method returns 0.
 | |
| 
 | |
|   This method can not be used to move \a axis between axis rects. The same \a axis instance must
 | |
|   not be added multiple times to the same or different axis rects.
 | |
| 
 | |
|   If an axis rect side already contains one or more axes, the lower and upper endings of the new
 | |
|   axis (\ref QCPAxis::setLowerEnding, \ref QCPAxis::setUpperEnding) are set to \ref
 | |
|   QCPLineEnding::esHalfBar.
 | |
| 
 | |
|   \see addAxes, setupFullAxesBox
 | |
| */
 | |
| QCPAxis *QCPAxisRect::addAxis(QCPAxis::AxisType type, QCPAxis *axis)
 | |
| {
 | |
|   QCPAxis *newAxis = axis;
 | |
|   if (!newAxis)
 | |
|   {
 | |
|     newAxis = new QCPAxis(this, type);
 | |
|   } else // user provided existing axis instance, do some sanity checks
 | |
|   {
 | |
|     if (newAxis->axisType() != type)
 | |
|     {
 | |
|       qDebug() << Q_FUNC_INFO << "passed axis has different axis type than specified in type parameter";
 | |
|       return 0;
 | |
|     }
 | |
|     if (newAxis->axisRect() != this)
 | |
|     {
 | |
|       qDebug() << Q_FUNC_INFO << "passed axis doesn't have this axis rect as parent axis rect";
 | |
|       return 0;
 | |
|     }
 | |
|     if (axes().contains(newAxis))
 | |
|     {
 | |
|       qDebug() << Q_FUNC_INFO << "passed axis is already owned by this axis rect";
 | |
|       return 0;
 | |
|     }
 | |
|   }
 | |
|   if (mAxes[type].size() > 0) // multiple axes on one side, add half-bar axis ending to additional axes with offset
 | |
|   {
 | |
|     bool invert = (type == QCPAxis::atRight) || (type == QCPAxis::atBottom);
 | |
|     newAxis->setLowerEnding(QCPLineEnding(QCPLineEnding::esHalfBar, 6, 10, !invert));
 | |
|     newAxis->setUpperEnding(QCPLineEnding(QCPLineEnding::esHalfBar, 6, 10, invert));
 | |
|   }
 | |
|   mAxes[type].append(newAxis);
 | |
|   
 | |
|   // reset convenience axis pointers on parent QCustomPlot if they are unset:
 | |
|   if (mParentPlot && mParentPlot->axisRectCount() > 0 && mParentPlot->axisRect(0) == this)
 | |
|   {
 | |
|     switch (type)
 | |
|     {
 | |
|       case QCPAxis::atBottom: { if (!mParentPlot->xAxis) mParentPlot->xAxis = newAxis; break; }
 | |
|       case QCPAxis::atLeft: { if (!mParentPlot->yAxis) mParentPlot->yAxis = newAxis; break; }
 | |
|       case QCPAxis::atTop: { if (!mParentPlot->xAxis2) mParentPlot->xAxis2 = newAxis; break; }
 | |
|       case QCPAxis::atRight: { if (!mParentPlot->yAxis2) mParentPlot->yAxis2 = newAxis; break; }
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   return newAxis;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Adds a new axis with \ref addAxis to each axis rect side specified in \a types. This may be an
 | |
|   <tt>or</tt>-combination of QCPAxis::AxisType, so axes can be added to multiple sides at once.
 | |
|   
 | |
|   Returns a list of the added axes.
 | |
|   
 | |
|   \see addAxis, setupFullAxesBox
 | |
| */
 | |
| QList<QCPAxis*> QCPAxisRect::addAxes(QCPAxis::AxisTypes types)
 | |
| {
 | |
|   QList<QCPAxis*> result;
 | |
|   if (types.testFlag(QCPAxis::atLeft))
 | |
|     result << addAxis(QCPAxis::atLeft);
 | |
|   if (types.testFlag(QCPAxis::atRight))
 | |
|     result << addAxis(QCPAxis::atRight);
 | |
|   if (types.testFlag(QCPAxis::atTop))
 | |
|     result << addAxis(QCPAxis::atTop);
 | |
|   if (types.testFlag(QCPAxis::atBottom))
 | |
|     result << addAxis(QCPAxis::atBottom);
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Removes the specified \a axis from the axis rect and deletes it.
 | |
|   
 | |
|   Returns true on success, i.e. if \a axis was a valid axis in this axis rect.
 | |
|   
 | |
|   \see addAxis
 | |
| */
 | |
| bool QCPAxisRect::removeAxis(QCPAxis *axis)
 | |
| {
 | |
|   // don't access axis->axisType() to provide safety when axis is an invalid pointer, rather go through all axis containers:
 | |
|   QHashIterator<QCPAxis::AxisType, QList<QCPAxis*> > it(mAxes);
 | |
|   while (it.hasNext())
 | |
|   {
 | |
|     it.next();
 | |
|     if (it.value().contains(axis))
 | |
|     {
 | |
|       mAxes[it.key()].removeOne(axis);
 | |
|       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)
 | |
|         parentPlot()->axisRemoved(axis);
 | |
|       delete axis;
 | |
|       return true;
 | |
|     }
 | |
|   }
 | |
|   qDebug() << Q_FUNC_INFO << "Axis isn't in axis rect:" << reinterpret_cast<quintptr>(axis);
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Zooms in (or out) to the passed rectangular region \a pixelRect, given in pixel coordinates.
 | |
| 
 | |
|   All axes of this axis rect will have their range zoomed accordingly. If you only wish to zoom
 | |
|   specific axes, use the overloaded version of this method.
 | |
|   
 | |
|   \see QCustomPlot::setSelectionRectMode
 | |
| */
 | |
| void QCPAxisRect::zoom(const QRectF &pixelRect)
 | |
| {
 | |
|   zoom(pixelRect, axes());
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Zooms in (or out) to the passed rectangular region \a pixelRect, given in pixel coordinates.
 | |
|   
 | |
|   Only the axes passed in \a affectedAxes will have their ranges zoomed accordingly.
 | |
|   
 | |
|   \see QCustomPlot::setSelectionRectMode
 | |
| */
 | |
| void QCPAxisRect::zoom(const QRectF &pixelRect, const QList<QCPAxis*> &affectedAxes)
 | |
| {
 | |
|   foreach (QCPAxis *axis, affectedAxes)
 | |
|   {
 | |
|     if (!axis)
 | |
|     {
 | |
|       qDebug() << Q_FUNC_INFO << "a passed axis was zero";
 | |
|       continue;
 | |
|     }
 | |
|     QCPRange pixelRange;
 | |
|     if (axis->orientation() == Qt::Horizontal)
 | |
|       pixelRange = QCPRange(pixelRect.left(), pixelRect.right());
 | |
|     else
 | |
|       pixelRange = QCPRange(pixelRect.top(), pixelRect.bottom());
 | |
|     axis->setRange(axis->pixelToCoord(pixelRange.lower), axis->pixelToCoord(pixelRange.upper));
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Convenience function to create an axis on each side that doesn't have any axes yet and set their
 | |
|   visibility to true. Further, the top/right axes are assigned the following properties of the
 | |
|   bottom/left axes:
 | |
| 
 | |
|   \li range (\ref QCPAxis::setRange)
 | |
|   \li range reversed (\ref QCPAxis::setRangeReversed)
 | |
|   \li scale type (\ref QCPAxis::setScaleType)
 | |
|   \li tick visibility (\ref QCPAxis::setTicks)
 | |
|   \li number format (\ref QCPAxis::setNumberFormat)
 | |
|   \li number precision (\ref QCPAxis::setNumberPrecision)
 | |
|   \li tick count of ticker (\ref QCPAxisTicker::setTickCount)
 | |
|   \li tick origin of ticker (\ref QCPAxisTicker::setTickOrigin)
 | |
| 
 | |
|   Tick label visibility (\ref QCPAxis::setTickLabels) of the right and top axes are set to false.
 | |
| 
 | |
|   If \a connectRanges is true, the \ref QCPAxis::rangeChanged "rangeChanged" signals of the bottom
 | |
|   and left axes are connected to the \ref QCPAxis::setRange slots of the top and right axes.
 | |
| */
 | |
| void QCPAxisRect::setupFullAxesBox(bool connectRanges)
 | |
| {
 | |
|   QCPAxis *xAxis, *yAxis, *xAxis2, *yAxis2;
 | |
|   if (axisCount(QCPAxis::atBottom) == 0)
 | |
|     xAxis = addAxis(QCPAxis::atBottom);
 | |
|   else
 | |
|     xAxis = axis(QCPAxis::atBottom);
 | |
|   
 | |
|   if (axisCount(QCPAxis::atLeft) == 0)
 | |
|     yAxis = addAxis(QCPAxis::atLeft);
 | |
|   else
 | |
|     yAxis = axis(QCPAxis::atLeft);
 | |
|   
 | |
|   if (axisCount(QCPAxis::atTop) == 0)
 | |
|     xAxis2 = addAxis(QCPAxis::atTop);
 | |
|   else
 | |
|     xAxis2 = axis(QCPAxis::atTop);
 | |
|   
 | |
|   if (axisCount(QCPAxis::atRight) == 0)
 | |
|     yAxis2 = addAxis(QCPAxis::atRight);
 | |
|   else
 | |
|     yAxis2 = axis(QCPAxis::atRight);
 | |
|   
 | |
|   xAxis->setVisible(true);
 | |
|   yAxis->setVisible(true);
 | |
|   xAxis2->setVisible(true);
 | |
|   yAxis2->setVisible(true);
 | |
|   xAxis2->setTickLabels(false);
 | |
|   yAxis2->setTickLabels(false);
 | |
|   
 | |
|   xAxis2->setRange(xAxis->range());
 | |
|   xAxis2->setRangeReversed(xAxis->rangeReversed());
 | |
|   xAxis2->setScaleType(xAxis->scaleType());
 | |
|   xAxis2->setTicks(xAxis->ticks());
 | |
|   xAxis2->setNumberFormat(xAxis->numberFormat());
 | |
|   xAxis2->setNumberPrecision(xAxis->numberPrecision());
 | |
|   xAxis2->ticker()->setTickCount(xAxis->ticker()->tickCount());
 | |
|   xAxis2->ticker()->setTickOrigin(xAxis->ticker()->tickOrigin());
 | |
|   
 | |
|   yAxis2->setRange(yAxis->range());
 | |
|   yAxis2->setRangeReversed(yAxis->rangeReversed());
 | |
|   yAxis2->setScaleType(yAxis->scaleType());
 | |
|   yAxis2->setTicks(yAxis->ticks());
 | |
|   yAxis2->setNumberFormat(yAxis->numberFormat());
 | |
|   yAxis2->setNumberPrecision(yAxis->numberPrecision());
 | |
|   yAxis2->ticker()->setTickCount(yAxis->ticker()->tickCount());
 | |
|   yAxis2->ticker()->setTickOrigin(yAxis->ticker()->tickOrigin());
 | |
|   
 | |
|   if (connectRanges)
 | |
|   {
 | |
|     connect(xAxis, SIGNAL(rangeChanged(QCPRange)), xAxis2, SLOT(setRange(QCPRange)));
 | |
|     connect(yAxis, SIGNAL(rangeChanged(QCPRange)), yAxis2, SLOT(setRange(QCPRange)));
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns a list of all the plottables that are associated with this axis rect.
 | |
|   
 | |
|   A plottable is considered associated with an axis rect if its key or value axis (or both) is in
 | |
|   this axis rect.
 | |
|   
 | |
|   \see graphs, items
 | |
| */
 | |
| QList<QCPAbstractPlottable*> QCPAxisRect::plottables() const
 | |
| {
 | |
|   // Note: don't append all QCPAxis::plottables() into a list, because we might get duplicate entries
 | |
|   QList<QCPAbstractPlottable*> result;
 | |
|   for (int i=0; i<mParentPlot->mPlottables.size(); ++i)
 | |
|   {
 | |
|     if (mParentPlot->mPlottables.at(i)->keyAxis()->axisRect() == this || mParentPlot->mPlottables.at(i)->valueAxis()->axisRect() == this)
 | |
|       result.append(mParentPlot->mPlottables.at(i));
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns a list of all the graphs that are associated with this axis rect.
 | |
|   
 | |
|   A graph is considered associated with an axis rect if its key or value axis (or both) is in
 | |
|   this axis rect.
 | |
|   
 | |
|   \see plottables, items
 | |
| */
 | |
| QList<QCPGraph*> QCPAxisRect::graphs() const
 | |
| {
 | |
|   // Note: don't append all QCPAxis::graphs() into a list, because we might get duplicate entries
 | |
|   QList<QCPGraph*> result;
 | |
|   for (int i=0; i<mParentPlot->mGraphs.size(); ++i)
 | |
|   {
 | |
|     if (mParentPlot->mGraphs.at(i)->keyAxis()->axisRect() == this || mParentPlot->mGraphs.at(i)->valueAxis()->axisRect() == this)
 | |
|       result.append(mParentPlot->mGraphs.at(i));
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns a list of all the items that are associated with this axis rect.
 | |
|   
 | |
|   An item is considered associated with an axis rect if any of its positions has key or value axis
 | |
|   set to an axis that is in this axis rect, or if any of its positions has \ref
 | |
|   QCPItemPosition::setAxisRect set to the axis rect, or if the clip axis rect (\ref
 | |
|   QCPAbstractItem::setClipAxisRect) is set to this axis rect.
 | |
|   
 | |
|   \see plottables, graphs
 | |
| */
 | |
| QList<QCPAbstractItem *> QCPAxisRect::items() const
 | |
| {
 | |
|   // Note: don't just append all QCPAxis::items() into a list, because we might get duplicate entries
 | |
|   //       and miss those items that have this axis rect as clipAxisRect.
 | |
|   QList<QCPAbstractItem*> result;
 | |
|   for (int itemId=0; itemId<mParentPlot->mItems.size(); ++itemId)
 | |
|   {
 | |
|     if (mParentPlot->mItems.at(itemId)->clipAxisRect() == this)
 | |
|     {
 | |
|       result.append(mParentPlot->mItems.at(itemId));
 | |
|       continue;
 | |
|     }
 | |
|     QList<QCPItemPosition*> positions = mParentPlot->mItems.at(itemId)->positions();
 | |
|     for (int posId=0; posId<positions.size(); ++posId)
 | |
|     {
 | |
|       if (positions.at(posId)->axisRect() == this ||
 | |
|           positions.at(posId)->keyAxis()->axisRect() == this ||
 | |
|           positions.at(posId)->valueAxis()->axisRect() == this)
 | |
|       {
 | |
|         result.append(mParentPlot->mItems.at(itemId));
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   This method is called automatically upon replot and doesn't need to be called by users of
 | |
|   QCPAxisRect.
 | |
|   
 | |
|   Calls the base class implementation to update the margins (see \ref QCPLayoutElement::update),
 | |
|   and finally passes the \ref rect to the inset layout (\ref insetLayout) and calls its
 | |
|   QCPInsetLayout::update function.
 | |
|   
 | |
|   \seebaseclassmethod
 | |
| */
 | |
| void QCPAxisRect::update(UpdatePhase phase)
 | |
| {
 | |
|   QCPLayoutElement::update(phase);
 | |
|   
 | |
|   switch (phase)
 | |
|   {
 | |
|     case upPreparation:
 | |
|     {
 | |
|       QList<QCPAxis*> allAxes = axes();
 | |
|       for (int i=0; i<allAxes.size(); ++i)
 | |
|         allAxes.at(i)->setupTickVectors();
 | |
|       break;
 | |
|     }
 | |
|     case upLayout:
 | |
|     {
 | |
|       mInsetLayout->setOuterRect(rect());
 | |
|       break;
 | |
|     }
 | |
|     default: break;
 | |
|   }
 | |
|   
 | |
|   // pass update call on to inset layout (doesn't happen automatically, because QCPAxisRect doesn't derive from QCPLayout):
 | |
|   mInsetLayout->update(phase);
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| QList<QCPLayoutElement*> QCPAxisRect::elements(bool recursive) const
 | |
| {
 | |
|   QList<QCPLayoutElement*> result;
 | |
|   if (mInsetLayout)
 | |
|   {
 | |
|     result << mInsetLayout;
 | |
|     if (recursive)
 | |
|       result << mInsetLayout->elements(recursive);
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPAxisRect::applyDefaultAntialiasingHint(QCPPainter *painter) const
 | |
| {
 | |
|   painter->setAntialiasing(false);
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPAxisRect::draw(QCPPainter *painter)
 | |
| {
 | |
|   drawBackground(painter);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets \a pm as the axis background pixmap. The axis background pixmap will be drawn inside the
 | |
|   axis rect. Since axis rects place themselves on the "background" layer by default, the axis rect
 | |
|   backgrounds are usually drawn below everything else.
 | |
| 
 | |
|   For cases where the provided pixmap doesn't have the same size as the axis rect, scaling can be
 | |
|   enabled with \ref setBackgroundScaled and the scaling mode (i.e. whether and how the aspect ratio
 | |
|   is preserved) can be set with \ref setBackgroundScaledMode. To set all these options in one call,
 | |
|   consider using the overloaded version of this function.
 | |
| 
 | |
|   Below the pixmap, the axis rect may be optionally filled with a brush, if specified with \ref
 | |
|   setBackground(const QBrush &brush).
 | |
|   
 | |
|   \see setBackgroundScaled, setBackgroundScaledMode, setBackground(const QBrush &brush)
 | |
| */
 | |
| void QCPAxisRect::setBackground(const QPixmap &pm)
 | |
| {
 | |
|   mBackgroundPixmap = pm;
 | |
|   mScaledBackgroundPixmap = QPixmap();
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Sets \a brush as the background brush. The axis rect background will be filled with this brush.
 | |
|   Since axis rects place themselves on the "background" layer by default, the axis rect backgrounds
 | |
|   are usually drawn below everything else.
 | |
| 
 | |
|   The brush will be drawn before (under) any background pixmap, which may be specified with \ref
 | |
|   setBackground(const QPixmap &pm).
 | |
| 
 | |
|   To disable drawing of a background brush, set \a brush to Qt::NoBrush.
 | |
|   
 | |
|   \see setBackground(const QPixmap &pm)
 | |
| */
 | |
| void QCPAxisRect::setBackground(const QBrush &brush)
 | |
| {
 | |
|   mBackgroundBrush = brush;
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Allows setting the background pixmap of the axis rect, whether it shall be scaled and how it
 | |
|   shall be scaled in one call.
 | |
| 
 | |
|   \see setBackground(const QPixmap &pm), setBackgroundScaled, setBackgroundScaledMode
 | |
| */
 | |
| void QCPAxisRect::setBackground(const QPixmap &pm, bool scaled, Qt::AspectRatioMode mode)
 | |
| {
 | |
|   mBackgroundPixmap = pm;
 | |
|   mScaledBackgroundPixmap = QPixmap();
 | |
|   mBackgroundScaled = scaled;
 | |
|   mBackgroundScaledMode = mode;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets whether the axis background pixmap shall be scaled to fit the axis rect or not. If \a scaled
 | |
|   is set to true, you may control whether and how the aspect ratio of the original pixmap is
 | |
|   preserved with \ref setBackgroundScaledMode.
 | |
|   
 | |
|   Note that the scaled version of the original pixmap is buffered, so there is no performance
 | |
|   penalty on replots. (Except when the axis rect dimensions are changed continuously.)
 | |
|   
 | |
|   \see setBackground, setBackgroundScaledMode
 | |
| */
 | |
| void QCPAxisRect::setBackgroundScaled(bool scaled)
 | |
| {
 | |
|   mBackgroundScaled = scaled;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   If scaling of the axis background pixmap is enabled (\ref setBackgroundScaled), use this function to
 | |
|   define whether and how the aspect ratio of the original pixmap passed to \ref setBackground is preserved.
 | |
|   \see setBackground, setBackgroundScaled
 | |
| */
 | |
| void QCPAxisRect::setBackgroundScaledMode(Qt::AspectRatioMode mode)
 | |
| {
 | |
|   mBackgroundScaledMode = mode;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the range drag axis of the \a orientation provided. If multiple axes were set, returns
 | |
|   the first one (use \ref rangeDragAxes to retrieve a list with all set axes).
 | |
| 
 | |
|   \see setRangeDragAxes
 | |
| */
 | |
| QCPAxis *QCPAxisRect::rangeDragAxis(Qt::Orientation orientation)
 | |
| {
 | |
|   if (orientation == Qt::Horizontal)
 | |
|     return mRangeDragHorzAxis.isEmpty() ? 0 : mRangeDragHorzAxis.first().data();
 | |
|   else
 | |
|     return mRangeDragVertAxis.isEmpty() ? 0 : mRangeDragVertAxis.first().data();
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the range zoom axis of the \a orientation provided. If multiple axes were set, returns
 | |
|   the first one (use \ref rangeZoomAxes to retrieve a list with all set axes).
 | |
| 
 | |
|   \see setRangeZoomAxes
 | |
| */
 | |
| QCPAxis *QCPAxisRect::rangeZoomAxis(Qt::Orientation orientation)
 | |
| {
 | |
|   if (orientation == Qt::Horizontal)
 | |
|     return mRangeZoomHorzAxis.isEmpty() ? 0 : mRangeZoomHorzAxis.first().data();
 | |
|   else
 | |
|     return mRangeZoomVertAxis.isEmpty() ? 0 : mRangeZoomVertAxis.first().data();
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns all range drag axes of the \a orientation provided.
 | |
| 
 | |
|   \see rangeZoomAxis, setRangeZoomAxes
 | |
| */
 | |
| QList<QCPAxis*> QCPAxisRect::rangeDragAxes(Qt::Orientation orientation)
 | |
| {
 | |
|   QList<QCPAxis*> result;
 | |
|   if (orientation == Qt::Horizontal)
 | |
|   {
 | |
|     for (int i=0; i<mRangeDragHorzAxis.size(); ++i)
 | |
|     {
 | |
|       if (!mRangeDragHorzAxis.at(i).isNull())
 | |
|         result.append(mRangeDragHorzAxis.at(i).data());
 | |
|     }
 | |
|   } else
 | |
|   {
 | |
|     for (int i=0; i<mRangeDragVertAxis.size(); ++i)
 | |
|     {
 | |
|       if (!mRangeDragVertAxis.at(i).isNull())
 | |
|         result.append(mRangeDragVertAxis.at(i).data());
 | |
|     }
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns all range zoom axes of the \a orientation provided.
 | |
| 
 | |
|   \see rangeDragAxis, setRangeDragAxes
 | |
| */
 | |
| QList<QCPAxis*> QCPAxisRect::rangeZoomAxes(Qt::Orientation orientation)
 | |
| {
 | |
|   QList<QCPAxis*> result;
 | |
|   if (orientation == Qt::Horizontal)
 | |
|   {
 | |
|     for (int i=0; i<mRangeZoomHorzAxis.size(); ++i)
 | |
|     {
 | |
|       if (!mRangeZoomHorzAxis.at(i).isNull())
 | |
|         result.append(mRangeZoomHorzAxis.at(i).data());
 | |
|     }
 | |
|   } else
 | |
|   {
 | |
|     for (int i=0; i<mRangeZoomVertAxis.size(); ++i)
 | |
|     {
 | |
|       if (!mRangeZoomVertAxis.at(i).isNull())
 | |
|         result.append(mRangeZoomVertAxis.at(i).data());
 | |
|     }
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the range zoom factor of the \a orientation provided.
 | |
|   
 | |
|   \see setRangeZoomFactor
 | |
| */
 | |
| double QCPAxisRect::rangeZoomFactor(Qt::Orientation orientation)
 | |
| {
 | |
|   return (orientation == Qt::Horizontal ? mRangeZoomFactorHorz : mRangeZoomFactorVert);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets which axis orientation may be range dragged by the user with mouse interaction.
 | |
|   What orientation corresponds to which specific axis can be set with
 | |
|   \ref setRangeDragAxes(QCPAxis *horizontal, QCPAxis *vertical). By
 | |
|   default, the horizontal axis is the bottom axis (xAxis) and the vertical axis
 | |
|   is the left axis (yAxis).
 | |
|   
 | |
|   To disable range dragging entirely, pass 0 as \a orientations or remove \ref QCP::iRangeDrag from \ref
 | |
|   QCustomPlot::setInteractions. To enable range dragging for both directions, pass <tt>Qt::Horizontal |
 | |
|   Qt::Vertical</tt> as \a orientations.
 | |
|   
 | |
|   In addition to setting \a orientations to a non-zero value, make sure \ref QCustomPlot::setInteractions
 | |
|   contains \ref QCP::iRangeDrag to enable the range dragging interaction.
 | |
|   
 | |
|   \see setRangeZoom, setRangeDragAxes, QCustomPlot::setNoAntialiasingOnDrag
 | |
| */
 | |
| void QCPAxisRect::setRangeDrag(Qt::Orientations orientations)
 | |
| {
 | |
|   mRangeDrag = orientations;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets which axis orientation may be zoomed by the user with the mouse wheel. What orientation
 | |
|   corresponds to which specific axis can be set with \ref setRangeZoomAxes(QCPAxis *horizontal,
 | |
|   QCPAxis *vertical). By default, the horizontal axis is the bottom axis (xAxis) and the vertical
 | |
|   axis is the left axis (yAxis).
 | |
| 
 | |
|   To disable range zooming entirely, pass 0 as \a orientations or remove \ref QCP::iRangeZoom from \ref
 | |
|   QCustomPlot::setInteractions. To enable range zooming for both directions, pass <tt>Qt::Horizontal |
 | |
|   Qt::Vertical</tt> as \a orientations.
 | |
|   
 | |
|   In addition to setting \a orientations to a non-zero value, make sure \ref QCustomPlot::setInteractions
 | |
|   contains \ref QCP::iRangeZoom to enable the range zooming interaction.
 | |
|   
 | |
|   \see setRangeZoomFactor, setRangeZoomAxes, setRangeDrag
 | |
| */
 | |
| void QCPAxisRect::setRangeZoom(Qt::Orientations orientations)
 | |
| {
 | |
|   mRangeZoom = orientations;
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Sets the axes whose range will be dragged when \ref setRangeDrag enables mouse range dragging on
 | |
|   the QCustomPlot widget. Pass 0 if no axis shall be dragged in the respective orientation.
 | |
| 
 | |
|   Use the overload taking a list of axes, if multiple axes (more than one per orientation) shall
 | |
|   react to dragging interactions.
 | |
| 
 | |
|   \see setRangeZoomAxes
 | |
| */
 | |
| void QCPAxisRect::setRangeDragAxes(QCPAxis *horizontal, QCPAxis *vertical)
 | |
| {
 | |
|   QList<QCPAxis*> horz, vert;
 | |
|   if (horizontal)
 | |
|     horz.append(horizontal);
 | |
|   if (vertical)
 | |
|     vert.append(vertical);
 | |
|   setRangeDragAxes(horz, vert);
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
| 
 | |
|   This method allows to set up multiple axes to react to horizontal and vertical dragging. The drag
 | |
|   orientation that the respective axis will react to is deduced from its orientation (\ref
 | |
|   QCPAxis::orientation).
 | |
| 
 | |
|   In the unusual case that you wish to e.g. drag a vertically oriented axis with a horizontal drag
 | |
|   motion, use the overload taking two separate lists for horizontal and vertical dragging.
 | |
| */
 | |
| void QCPAxisRect::setRangeDragAxes(QList<QCPAxis*> axes)
 | |
| {
 | |
|   QList<QCPAxis*> horz, vert;
 | |
|   foreach (QCPAxis *ax, axes)
 | |
|   {
 | |
|     if (ax->orientation() == Qt::Horizontal)
 | |
|       horz.append(ax);
 | |
|     else
 | |
|       vert.append(ax);
 | |
|   }
 | |
|   setRangeDragAxes(horz, vert);
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
| 
 | |
|   This method allows to set multiple axes up to react to horizontal and vertical dragging, and
 | |
|   define specifically which axis reacts to which drag orientation (irrespective of the axis
 | |
|   orientation).
 | |
| */
 | |
| void QCPAxisRect::setRangeDragAxes(QList<QCPAxis*> horizontal, QList<QCPAxis*> vertical)
 | |
| {
 | |
|   mRangeDragHorzAxis.clear();
 | |
|   foreach (QCPAxis *ax, horizontal)
 | |
|   {
 | |
|     QPointer<QCPAxis> axPointer(ax);
 | |
|     if (!axPointer.isNull())
 | |
|       mRangeDragHorzAxis.append(axPointer);
 | |
|     else
 | |
|       qDebug() << Q_FUNC_INFO << "invalid axis passed in horizontal list:" << reinterpret_cast<quintptr>(ax);
 | |
|   }
 | |
|   mRangeDragVertAxis.clear();
 | |
|   foreach (QCPAxis *ax, vertical)
 | |
|   {
 | |
|     QPointer<QCPAxis> axPointer(ax);
 | |
|     if (!axPointer.isNull())
 | |
|       mRangeDragVertAxis.append(axPointer);
 | |
|     else
 | |
|       qDebug() << Q_FUNC_INFO << "invalid axis passed in vertical list:" << reinterpret_cast<quintptr>(ax);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the axes whose range will be zoomed when \ref setRangeZoom enables mouse wheel zooming on
 | |
|   the QCustomPlot widget. Pass 0 if no axis shall be zoomed in the respective orientation.
 | |
| 
 | |
|   The two axes can be zoomed with different strengths, when different factors are passed to \ref
 | |
|   setRangeZoomFactor(double horizontalFactor, double verticalFactor).
 | |
| 
 | |
|   Use the overload taking a list of axes, if multiple axes (more than one per orientation) shall
 | |
|   react to zooming interactions.
 | |
| 
 | |
|   \see setRangeDragAxes
 | |
| */
 | |
| void QCPAxisRect::setRangeZoomAxes(QCPAxis *horizontal, QCPAxis *vertical)
 | |
| {
 | |
|   QList<QCPAxis*> horz, vert;
 | |
|   if (horizontal)
 | |
|     horz.append(horizontal);
 | |
|   if (vertical)
 | |
|     vert.append(vertical);
 | |
|   setRangeZoomAxes(horz, vert);
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
| 
 | |
|   This method allows to set up multiple axes to react to horizontal and vertical range zooming. The
 | |
|   zoom orientation that the respective axis will react to is deduced from its orientation (\ref
 | |
|   QCPAxis::orientation).
 | |
| 
 | |
|   In the unusual case that you wish to e.g. zoom a vertically oriented axis with a horizontal zoom
 | |
|   interaction, use the overload taking two separate lists for horizontal and vertical zooming.
 | |
| */
 | |
| void QCPAxisRect::setRangeZoomAxes(QList<QCPAxis*> axes)
 | |
| {
 | |
|   QList<QCPAxis*> horz, vert;
 | |
|   foreach (QCPAxis *ax, axes)
 | |
|   {
 | |
|     if (ax->orientation() == Qt::Horizontal)
 | |
|       horz.append(ax);
 | |
|     else
 | |
|       vert.append(ax);
 | |
|   }
 | |
|   setRangeZoomAxes(horz, vert);
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
| 
 | |
|   This method allows to set multiple axes up to react to horizontal and vertical zooming, and
 | |
|   define specifically which axis reacts to which zoom orientation (irrespective of the axis
 | |
|   orientation).
 | |
| */
 | |
| void QCPAxisRect::setRangeZoomAxes(QList<QCPAxis*> horizontal, QList<QCPAxis*> vertical)
 | |
| {
 | |
|   mRangeZoomHorzAxis.clear();
 | |
|   foreach (QCPAxis *ax, horizontal)
 | |
|   {
 | |
|     QPointer<QCPAxis> axPointer(ax);
 | |
|     if (!axPointer.isNull())
 | |
|       mRangeZoomHorzAxis.append(axPointer);
 | |
|     else
 | |
|       qDebug() << Q_FUNC_INFO << "invalid axis passed in horizontal list:" << reinterpret_cast<quintptr>(ax);
 | |
|   }
 | |
|   mRangeZoomVertAxis.clear();
 | |
|   foreach (QCPAxis *ax, vertical)
 | |
|   {
 | |
|     QPointer<QCPAxis> axPointer(ax);
 | |
|     if (!axPointer.isNull())
 | |
|       mRangeZoomVertAxis.append(axPointer);
 | |
|     else
 | |
|       qDebug() << Q_FUNC_INFO << "invalid axis passed in vertical list:" << reinterpret_cast<quintptr>(ax);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets how strong one rotation step of the mouse wheel zooms, when range zoom was activated with
 | |
|   \ref setRangeZoom. The two parameters \a horizontalFactor and \a verticalFactor provide a way to
 | |
|   let the horizontal axis zoom at different rates than the vertical axis. Which axis is horizontal
 | |
|   and which is vertical, can be set with \ref setRangeZoomAxes.
 | |
| 
 | |
|   When the zoom factor is greater than one, scrolling the mouse wheel backwards (towards the user)
 | |
|   will zoom in (make the currently visible range smaller). For zoom factors smaller than one, the
 | |
|   same scrolling direction will zoom out.
 | |
| */
 | |
| void QCPAxisRect::setRangeZoomFactor(double horizontalFactor, double verticalFactor)
 | |
| {
 | |
|   mRangeZoomFactorHorz = horizontalFactor;
 | |
|   mRangeZoomFactorVert = verticalFactor;
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Sets both the horizontal and vertical zoom \a factor.
 | |
| */
 | |
| void QCPAxisRect::setRangeZoomFactor(double factor)
 | |
| {
 | |
|   mRangeZoomFactorHorz = factor;
 | |
|   mRangeZoomFactorVert = factor;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Draws the background of this axis rect. It may consist of a background fill (a QBrush) and a
 | |
|   pixmap.
 | |
|   
 | |
|   If a brush was given via \ref setBackground(const QBrush &brush), this function first draws an
 | |
|   according filling inside the axis rect with the provided \a painter.
 | |
|   
 | |
|   Then, if a pixmap was provided via \ref setBackground, this function buffers the scaled version
 | |
|   depending on \ref setBackgroundScaled and \ref setBackgroundScaledMode and then draws it inside
 | |
|   the axis rect with the provided \a painter. The scaled version is buffered in
 | |
|   mScaledBackgroundPixmap to prevent expensive rescaling at every redraw. It is only updated, when
 | |
|   the axis rect has changed in a way that requires a rescale of the background pixmap (this is
 | |
|   dependent on the \ref setBackgroundScaledMode), or when a differend axis background pixmap was
 | |
|   set.
 | |
|   
 | |
|   \see setBackground, setBackgroundScaled, setBackgroundScaledMode
 | |
| */
 | |
| void QCPAxisRect::drawBackground(QCPPainter *painter)
 | |
| {
 | |
|   // draw background fill:
 | |
|   if (mBackgroundBrush != Qt::NoBrush)
 | |
|     painter->fillRect(mRect, mBackgroundBrush);
 | |
|   
 | |
|   // draw background pixmap (on top of fill, if brush specified):
 | |
|   if (!mBackgroundPixmap.isNull())
 | |
|   {
 | |
|     if (mBackgroundScaled)
 | |
|     {
 | |
|       // check whether mScaledBackground needs to be updated:
 | |
|       QSize scaledSize(mBackgroundPixmap.size());
 | |
|       scaledSize.scale(mRect.size(), mBackgroundScaledMode);
 | |
|       if (mScaledBackgroundPixmap.size() != scaledSize)
 | |
|         mScaledBackgroundPixmap = mBackgroundPixmap.scaled(mRect.size(), mBackgroundScaledMode, Qt::SmoothTransformation);
 | |
|       painter->drawPixmap(mRect.topLeft()+QPoint(0, -1), mScaledBackgroundPixmap, QRect(0, 0, mRect.width(), mRect.height()) & mScaledBackgroundPixmap.rect());
 | |
|     } else
 | |
|     {
 | |
|       painter->drawPixmap(mRect.topLeft()+QPoint(0, -1), mBackgroundPixmap, QRect(0, 0, mRect.width(), mRect.height()));
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   This function makes sure multiple axes on the side specified with \a type don't collide, but are
 | |
|   distributed according to their respective space requirement (QCPAxis::calculateMargin).
 | |
|   
 | |
|   It does this by setting an appropriate offset (\ref QCPAxis::setOffset) on all axes except the
 | |
|   one with index zero.
 | |
|   
 | |
|   This function is called by \ref calculateAutoMargin.
 | |
| */
 | |
| void QCPAxisRect::updateAxesOffset(QCPAxis::AxisType type)
 | |
| {
 | |
|   const QList<QCPAxis*> axesList = mAxes.value(type);
 | |
|   if (axesList.isEmpty())
 | |
|     return;
 | |
|   
 | |
|   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
 | |
|   for (int i=1; i<axesList.size(); ++i)
 | |
|   {
 | |
|     int offset = axesList.at(i-1)->offset() + axesList.at(i-1)->calculateMargin();
 | |
|     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)
 | |
|     {
 | |
|       if (!isFirstVisible)
 | |
|         offset += axesList.at(i)->tickLengthIn();
 | |
|       isFirstVisible = false;
 | |
|     }
 | |
|     axesList.at(i)->setOffset(offset);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| int QCPAxisRect::calculateAutoMargin(QCP::MarginSide side)
 | |
| {
 | |
|   if (!mAutoMargins.testFlag(side))
 | |
|     qDebug() << Q_FUNC_INFO << "Called with side that isn't specified as auto margin";
 | |
|   
 | |
|   updateAxesOffset(QCPAxis::marginSideToAxisType(side));
 | |
|   
 | |
|   // note: only need to look at the last (outer most) axis to determine the total margin, due to updateAxisOffset call
 | |
|   const QList<QCPAxis*> axesList = mAxes.value(QCPAxis::marginSideToAxisType(side));
 | |
|   if (axesList.size() > 0)
 | |
|     return axesList.last()->offset() + axesList.last()->calculateMargin();
 | |
|   else
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Reacts to a change in layout to potentially set the convenience axis pointers \ref
 | |
|   QCustomPlot::xAxis, \ref QCustomPlot::yAxis, etc. of the parent QCustomPlot to the respective
 | |
|   axes of this axis rect. This is only done if the respective convenience pointer is currently zero
 | |
|   and if there is no QCPAxisRect at position (0, 0) of the plot layout.
 | |
|   
 | |
|   This automation makes it simpler to replace the main axis rect with a newly created one, without
 | |
|   the need to manually reset the convenience pointers.
 | |
| */
 | |
| void QCPAxisRect::layoutChanged()
 | |
| {
 | |
|   if (mParentPlot && mParentPlot->axisRectCount() > 0 && mParentPlot->axisRect(0) == this)
 | |
|   {
 | |
|     if (axisCount(QCPAxis::atBottom) > 0 && !mParentPlot->xAxis)
 | |
|       mParentPlot->xAxis = axis(QCPAxis::atBottom);
 | |
|     if (axisCount(QCPAxis::atLeft) > 0 && !mParentPlot->yAxis)
 | |
|       mParentPlot->yAxis = axis(QCPAxis::atLeft);
 | |
|     if (axisCount(QCPAxis::atTop) > 0 && !mParentPlot->xAxis2)
 | |
|       mParentPlot->xAxis2 = axis(QCPAxis::atTop);
 | |
|     if (axisCount(QCPAxis::atRight) > 0 && !mParentPlot->yAxis2)
 | |
|       mParentPlot->yAxis2 = axis(QCPAxis::atRight);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Event handler for when a mouse button is pressed on the axis rect. If the left mouse button is
 | |
|   pressed, the range dragging interaction is initialized (the actual range manipulation happens in
 | |
|   the \ref mouseMoveEvent).
 | |
| 
 | |
|   The mDragging flag is set to true and some anchor points are set that are needed to determine the
 | |
|   distance the mouse was dragged in the mouse move/release events later.
 | |
|   
 | |
|   \see mouseMoveEvent, mouseReleaseEvent
 | |
| */
 | |
| void QCPAxisRect::mousePressEvent(QMouseEvent *event, const QVariant &details)
 | |
| {
 | |
|   Q_UNUSED(details)
 | |
|   mDragStart = event->pos(); // need this even when not LeftButton is pressed, to determine in releaseEvent whether it was a full click (no position change between press and release)
 | |
|   if (event->buttons() & Qt::LeftButton)
 | |
|   {
 | |
|     mDragging = true;
 | |
|     // initialize antialiasing backup in case we start dragging:
 | |
|     if (mParentPlot->noAntialiasingOnDrag())
 | |
|     {
 | |
|       mAADragBackup = mParentPlot->antialiasedElements();
 | |
|       mNotAADragBackup = mParentPlot->notAntialiasedElements();
 | |
|     }
 | |
|     // Mouse range dragging interaction:
 | |
|     if (mParentPlot->interactions().testFlag(QCP::iRangeDrag))
 | |
|     {
 | |
|       mDragStartHorzRange.clear();
 | |
|       for (int i=0; i<mRangeDragHorzAxis.size(); ++i)
 | |
|         mDragStartHorzRange.append(mRangeDragHorzAxis.at(i).isNull() ? QCPRange() : mRangeDragHorzAxis.at(i)->range());
 | |
|       mDragStartVertRange.clear();
 | |
|       for (int i=0; i<mRangeDragVertAxis.size(); ++i)
 | |
|         mDragStartVertRange.append(mRangeDragVertAxis.at(i).isNull() ? QCPRange() : mRangeDragVertAxis.at(i)->range());
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Event handler for when the mouse is moved on the axis rect. If range dragging was activated in a
 | |
|   preceding \ref mousePressEvent, the range is moved accordingly.
 | |
|   
 | |
|   \see mousePressEvent, mouseReleaseEvent
 | |
| */
 | |
| void QCPAxisRect::mouseMoveEvent(QMouseEvent *event, const QPointF &startPos)
 | |
| {
 | |
|   Q_UNUSED(startPos)
 | |
|   // Mouse range dragging interaction:
 | |
|   if (mDragging && mParentPlot->interactions().testFlag(QCP::iRangeDrag))
 | |
|   {
 | |
|     
 | |
|     if (mRangeDrag.testFlag(Qt::Horizontal))
 | |
|     {
 | |
|       for (int i=0; i<mRangeDragHorzAxis.size(); ++i)
 | |
|       {
 | |
|         QCPAxis *ax = mRangeDragHorzAxis.at(i).data();
 | |
|         if (!ax)
 | |
|           continue;
 | |
|         if (i >= mDragStartHorzRange.size())
 | |
|           break;
 | |
|         if (ax->mScaleType == QCPAxis::stLinear)
 | |
|         {
 | |
|           double diff = ax->pixelToCoord(mDragStart.x()) - ax->pixelToCoord(event->pos().x());
 | |
|           ax->setRange(mDragStartHorzRange.at(i).lower+diff, mDragStartHorzRange.at(i).upper+diff);
 | |
|         } else if (ax->mScaleType == QCPAxis::stLogarithmic)
 | |
|         {
 | |
|           double diff = ax->pixelToCoord(mDragStart.x()) / ax->pixelToCoord(event->pos().x());
 | |
|           ax->setRange(mDragStartHorzRange.at(i).lower*diff, mDragStartHorzRange.at(i).upper*diff);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     
 | |
|     if (mRangeDrag.testFlag(Qt::Vertical))
 | |
|     {
 | |
|       for (int i=0; i<mRangeDragVertAxis.size(); ++i)
 | |
|       {
 | |
|         QCPAxis *ax = mRangeDragVertAxis.at(i).data();
 | |
|         if (!ax)
 | |
|           continue;
 | |
|         if (i >= mDragStartVertRange.size())
 | |
|           break;
 | |
|         if (ax->mScaleType == QCPAxis::stLinear)
 | |
|         {
 | |
|           double diff = ax->pixelToCoord(mDragStart.y()) - ax->pixelToCoord(event->pos().y());
 | |
|           ax->setRange(mDragStartVertRange.at(i).lower+diff, mDragStartVertRange.at(i).upper+diff);
 | |
|         } else if (ax->mScaleType == QCPAxis::stLogarithmic)
 | |
|         {
 | |
|           double diff = ax->pixelToCoord(mDragStart.y()) / ax->pixelToCoord(event->pos().y());
 | |
|           ax->setRange(mDragStartVertRange.at(i).lower*diff, mDragStartVertRange.at(i).upper*diff);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     
 | |
|     if (mRangeDrag != 0) // if either vertical or horizontal drag was enabled, do a replot
 | |
|     {
 | |
|       if (mParentPlot->noAntialiasingOnDrag())
 | |
|         mParentPlot->setNotAntialiasedElements(QCP::aeAll);
 | |
|       mParentPlot->replot();
 | |
|     }
 | |
|     
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPAxisRect::mouseReleaseEvent(QMouseEvent *event, const QPointF &startPos)
 | |
| {
 | |
|   Q_UNUSED(event)
 | |
|   Q_UNUSED(startPos)
 | |
|   mDragging = false;
 | |
|   if (mParentPlot->noAntialiasingOnDrag())
 | |
|   {
 | |
|     mParentPlot->setAntialiasedElements(mAADragBackup);
 | |
|     mParentPlot->setNotAntialiasedElements(mNotAADragBackup);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Event handler for mouse wheel events. If rangeZoom is Qt::Horizontal, Qt::Vertical or both, the
 | |
|   ranges of the axes defined as rangeZoomHorzAxis and rangeZoomVertAxis are scaled. The center of
 | |
|   the scaling operation is the current cursor position inside the axis rect. The scaling factor is
 | |
|   dependent on the mouse wheel delta (which direction the wheel was rotated) to provide a natural
 | |
|   zooming feel. The Strength of the zoom can be controlled via \ref setRangeZoomFactor.
 | |
|   
 | |
|   Note, that event->delta() is usually +/-120 for single rotation steps. However, if the mouse
 | |
|   wheel is turned rapidly, many steps may bunch up to one event, so the event->delta() may then be
 | |
|   multiples of 120. This is taken into account here, by calculating \a wheelSteps and using it as
 | |
|   exponent of the range zoom factor. This takes care of the wheel direction automatically, by
 | |
|   inverting the factor, when the wheel step is negative (f^-1 = 1/f).
 | |
| */
 | |
| void QCPAxisRect::wheelEvent(QWheelEvent *event)
 | |
| {
 | |
|   // Mouse range zooming interaction:
 | |
|   if (mParentPlot->interactions().testFlag(QCP::iRangeZoom))
 | |
|   {
 | |
|     if (mRangeZoom != 0)
 | |
|     {
 | |
|       double factor;
 | |
|       double wheelSteps = event->delta()/120.0; // a single step delta is +/-120 usually
 | |
|       if (mRangeZoom.testFlag(Qt::Horizontal))
 | |
|       {
 | |
|         factor = qPow(mRangeZoomFactorHorz, wheelSteps);
 | |
|         for (int i=0; i<mRangeZoomHorzAxis.size(); ++i)
 | |
|         {
 | |
|           if (!mRangeZoomHorzAxis.at(i).isNull())
 | |
|             mRangeZoomHorzAxis.at(i)->scaleRange(factor, mRangeZoomHorzAxis.at(i)->pixelToCoord(event->pos().x()));
 | |
|         }
 | |
|       }
 | |
|       if (mRangeZoom.testFlag(Qt::Vertical))
 | |
|       {
 | |
|         factor = qPow(mRangeZoomFactorVert, wheelSteps);
 | |
|         for (int i=0; i<mRangeZoomVertAxis.size(); ++i)
 | |
|         {
 | |
|           if (!mRangeZoomVertAxis.at(i).isNull())
 | |
|             mRangeZoomVertAxis.at(i)->scaleRange(factor, mRangeZoomVertAxis.at(i)->pixelToCoord(event->pos().y()));
 | |
|         }
 | |
|       }
 | |
|       mParentPlot->replot();
 | |
|     }
 | |
|   }
 | |
| }
 | |
| /* end of 'src/layoutelements/layoutelement-axisrect.cpp' */
 | |
| 
 | |
| 
 | |
| /* including file 'src/layoutelements/layoutelement-legend.cpp', size 30933  */
 | |
| /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPAbstractLegendItem
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPAbstractLegendItem
 | |
|   \brief The abstract base class for all entries in a QCPLegend.
 | |
|   
 | |
|   It defines a very basic interface for entries in a QCPLegend. For representing plottables in the
 | |
|   legend, the subclass \ref QCPPlottableLegendItem is more suitable.
 | |
|   
 | |
|   Only derive directly from this class when you need absolute freedom (e.g. a custom legend entry
 | |
|   that's not even associated with a plottable).
 | |
| 
 | |
|   You must implement the following pure virtual functions:
 | |
|   \li \ref draw (from QCPLayerable)
 | |
|   
 | |
|   You inherit the following members you may use:
 | |
|   <table>
 | |
|     <tr>
 | |
|       <td>QCPLegend *\b mParentLegend</td>
 | |
|       <td>A pointer to the parent QCPLegend.</td>
 | |
|     </tr><tr>
 | |
|       <td>QFont \b mFont</td>
 | |
|       <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>
 | |
|     </tr>
 | |
|   </table>
 | |
| */
 | |
| 
 | |
| /* start of documentation of signals */
 | |
| 
 | |
| /*! \fn void QCPAbstractLegendItem::selectionChanged(bool selected)
 | |
|   
 | |
|   This signal is emitted when the selection state of this legend item has changed, either by user
 | |
|   interaction or by a direct call to \ref setSelected.
 | |
| */
 | |
| 
 | |
| /* end of documentation of signals */
 | |
| 
 | |
| /*!
 | |
|   Constructs a QCPAbstractLegendItem and associates it with the QCPLegend \a parent. This does not
 | |
|   cause the item to be added to \a parent, so \ref QCPLegend::addItem must be called separately.
 | |
| */
 | |
| QCPAbstractLegendItem::QCPAbstractLegendItem(QCPLegend *parent) :
 | |
|   QCPLayoutElement(parent->parentPlot()),
 | |
|   mParentLegend(parent),
 | |
|   mFont(parent->font()),
 | |
|   mTextColor(parent->textColor()),
 | |
|   mSelectedFont(parent->selectedFont()),
 | |
|   mSelectedTextColor(parent->selectedTextColor()),
 | |
|   mSelectable(true),
 | |
|   mSelected(false)
 | |
| {
 | |
|   setLayer(QLatin1String("legend"));
 | |
|   setMargins(QMargins(0, 0, 0, 0));
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the default font of this specific legend item to \a font.
 | |
|   
 | |
|   \see setTextColor, QCPLegend::setFont
 | |
| */
 | |
| void QCPAbstractLegendItem::setFont(const QFont &font)
 | |
| {
 | |
|   mFont = font;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the default text color of this specific legend item to \a color.
 | |
|   
 | |
|   \see setFont, QCPLegend::setTextColor
 | |
| */
 | |
| void QCPAbstractLegendItem::setTextColor(const QColor &color)
 | |
| {
 | |
|   mTextColor = color;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   When this legend item is selected, \a font is used to draw generic text, instead of the normal
 | |
|   font set with \ref setFont.
 | |
|   
 | |
|   \see setFont, QCPLegend::setSelectedFont
 | |
| */
 | |
| void QCPAbstractLegendItem::setSelectedFont(const QFont &font)
 | |
| {
 | |
|   mSelectedFont = font;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   When this legend item is selected, \a color is used to draw generic text, instead of the normal
 | |
|   color set with \ref setTextColor.
 | |
|   
 | |
|   \see setTextColor, QCPLegend::setSelectedTextColor
 | |
| */
 | |
| void QCPAbstractLegendItem::setSelectedTextColor(const QColor &color)
 | |
| {
 | |
|   mSelectedTextColor = color;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets whether this specific legend item is selectable.
 | |
|   
 | |
|   \see setSelectedParts, QCustomPlot::setInteractions
 | |
| */
 | |
| void QCPAbstractLegendItem::setSelectable(bool selectable)
 | |
| {
 | |
|   if (mSelectable != selectable)
 | |
|   {
 | |
|     mSelectable = selectable;
 | |
|     emit selectableChanged(mSelectable);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets whether this specific legend item is selected.
 | |
|   
 | |
|   It is possible to set the selection state of this item by calling this function directly, even if
 | |
|   setSelectable is set to false.
 | |
|   
 | |
|   \see setSelectableParts, QCustomPlot::setInteractions
 | |
| */
 | |
| void QCPAbstractLegendItem::setSelected(bool selected)
 | |
| {
 | |
|   if (mSelected != selected)
 | |
|   {
 | |
|     mSelected = selected;
 | |
|     emit selectionChanged(mSelected);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| double QCPAbstractLegendItem::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
 | |
| {
 | |
|   Q_UNUSED(details)
 | |
|   if (!mParentPlot) return -1;
 | |
|   if (onlySelectable && (!mSelectable || !mParentLegend->selectableParts().testFlag(QCPLegend::spItems)))
 | |
|     return -1;
 | |
|   
 | |
|   if (mRect.contains(pos.toPoint()))
 | |
|     return mParentPlot->selectionTolerance()*0.99;
 | |
|   else
 | |
|     return -1;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPAbstractLegendItem::applyDefaultAntialiasingHint(QCPPainter *painter) const
 | |
| {
 | |
|   applyAntialiasingHint(painter, mAntialiased, QCP::aeLegendItems);
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| QRect QCPAbstractLegendItem::clipRect() const
 | |
| {
 | |
|   return mOuterRect;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPAbstractLegendItem::selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged)
 | |
| {
 | |
|   Q_UNUSED(event)
 | |
|   Q_UNUSED(details)
 | |
|   if (mSelectable && mParentLegend->selectableParts().testFlag(QCPLegend::spItems))
 | |
|   {
 | |
|     bool selBefore = mSelected;
 | |
|     setSelected(additive ? !mSelected : true);
 | |
|     if (selectionStateChanged)
 | |
|       *selectionStateChanged = mSelected != selBefore;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPAbstractLegendItem::deselectEvent(bool *selectionStateChanged)
 | |
| {
 | |
|   if (mSelectable && mParentLegend->selectableParts().testFlag(QCPLegend::spItems))
 | |
|   {
 | |
|     bool selBefore = mSelected;
 | |
|     setSelected(false);
 | |
|     if (selectionStateChanged)
 | |
|       *selectionStateChanged = mSelected != selBefore;
 | |
|   }
 | |
| }
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPPlottableLegendItem
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPPlottableLegendItem
 | |
|   \brief A legend item representing a plottable with an icon and the plottable name.
 | |
|   
 | |
|   This is the standard legend item for plottables. It displays an icon of the plottable next to the
 | |
|   plottable name. The icon is drawn by the respective plottable itself (\ref
 | |
|   QCPAbstractPlottable::drawLegendIcon), and tries to give an intuitive symbol for the plottable.
 | |
|   For example, the QCPGraph draws a centered horizontal line and/or a single scatter point in the
 | |
|   middle.
 | |
|   
 | |
|   Legend items of this type are always associated with one plottable (retrievable via the
 | |
|   plottable() function and settable with the constructor). You may change the font of the plottable
 | |
|   name with \ref setFont. Icon padding and border pen is taken from the parent QCPLegend, see \ref
 | |
|   QCPLegend::setIconBorderPen and \ref QCPLegend::setIconTextPadding.
 | |
| 
 | |
|   The function \ref QCPAbstractPlottable::addToLegend/\ref QCPAbstractPlottable::removeFromLegend
 | |
|   creates/removes legend items of this type in the default implementation. However, these functions
 | |
|   may be reimplemented such that a different kind of legend item (e.g a direct subclass of
 | |
|   QCPAbstractLegendItem) is used for that plottable.
 | |
|   
 | |
|   Since QCPLegend is based on QCPLayoutGrid, a legend item itself is just a subclass of
 | |
|   QCPLayoutElement. While it could be added to a legend (or any other layout) via the normal layout
 | |
|   interface, QCPLegend has specialized functions for handling legend items conveniently, see the
 | |
|   documentation of \ref QCPLegend.
 | |
| */
 | |
| 
 | |
| /*!
 | |
|   Creates a new legend item associated with \a plottable.
 | |
|   
 | |
|   Once it's created, it can be added to the legend via \ref QCPLegend::addItem.
 | |
|   
 | |
|   A more convenient way of adding/removing a plottable to/from the legend is via the functions \ref
 | |
|   QCPAbstractPlottable::addToLegend and \ref QCPAbstractPlottable::removeFromLegend.
 | |
| */
 | |
| QCPPlottableLegendItem::QCPPlottableLegendItem(QCPLegend *parent, QCPAbstractPlottable *plottable) :
 | |
|   QCPAbstractLegendItem(parent),
 | |
|   mPlottable(plottable)
 | |
| {
 | |
|   setAntialiased(false);
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns the pen that shall be used to draw the icon border, taking into account the selection
 | |
|   state of this item.
 | |
| */
 | |
| QPen QCPPlottableLegendItem::getIconBorderPen() const
 | |
| {
 | |
|   return mSelected ? mParentLegend->selectedIconBorderPen() : mParentLegend->iconBorderPen();
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns the text color that shall be used to draw text, taking into account the selection state
 | |
|   of this item.
 | |
| */
 | |
| QColor QCPPlottableLegendItem::getTextColor() const
 | |
| {
 | |
|   return mSelected ? mSelectedTextColor : mTextColor;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns the font that shall be used to draw text, taking into account the selection state of this
 | |
|   item.
 | |
| */
 | |
| QFont QCPPlottableLegendItem::getFont() const
 | |
| {
 | |
|   return mSelected ? mSelectedFont : mFont;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Draws the item with \a painter. The size and position of the drawn legend item is defined by the
 | |
|   parent layout (typically a \ref QCPLegend) and the \ref minimumSizeHint and \ref maximumSizeHint
 | |
|   of this legend item.
 | |
| */
 | |
| void QCPPlottableLegendItem::draw(QCPPainter *painter)
 | |
| {
 | |
|   if (!mPlottable) return;
 | |
|   painter->setFont(getFont());
 | |
|   painter->setPen(QPen(getTextColor()));
 | |
|   QSizeF iconSize = mParentLegend->iconSize();
 | |
|   QRectF textRect = painter->fontMetrics().boundingRect(0, 0, 0, iconSize.height(), Qt::TextDontClip, mPlottable->name());
 | |
|   QRectF iconRect(mRect.topLeft(), iconSize);
 | |
|   int textHeight = qMax(textRect.height(), iconSize.height());  // if text has smaller height than icon, center text vertically in icon height, else align tops
 | |
|   painter->drawText(mRect.x()+iconSize.width()+mParentLegend->iconTextPadding(), mRect.y(), textRect.width(), textHeight, Qt::TextDontClip, mPlottable->name());
 | |
|   // draw icon:
 | |
|   painter->save();
 | |
|   painter->setClipRect(iconRect, Qt::IntersectClip);
 | |
|   mPlottable->drawLegendIcon(painter, iconRect);
 | |
|   painter->restore();
 | |
|   // draw icon border:
 | |
|   if (getIconBorderPen().style() != Qt::NoPen)
 | |
|   {
 | |
|     painter->setPen(getIconBorderPen());
 | |
|     painter->setBrush(Qt::NoBrush);
 | |
|     int halfPen = qCeil(painter->pen().widthF()*0.5)+1;
 | |
|     painter->setClipRect(mOuterRect.adjusted(-halfPen, -halfPen, halfPen, halfPen)); // extend default clip rect so thicker pens (especially during selection) are not clipped
 | |
|     painter->drawRect(iconRect);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Calculates and returns the size of this item. This includes the icon, the text and the padding in
 | |
|   between.
 | |
|   
 | |
|   \seebaseclassmethod
 | |
| */
 | |
| QSize QCPPlottableLegendItem::minimumSizeHint() const
 | |
| {
 | |
|   if (!mPlottable) return QSize();
 | |
|   QSize result(0, 0);
 | |
|   QRect textRect;
 | |
|   QFontMetrics fontMetrics(getFont());
 | |
|   QSize iconSize = mParentLegend->iconSize();
 | |
|   textRect = fontMetrics.boundingRect(0, 0, 0, iconSize.height(), Qt::TextDontClip, mPlottable->name());
 | |
|   result.setWidth(iconSize.width() + mParentLegend->iconTextPadding() + textRect.width() + mMargins.left() + mMargins.right());
 | |
|   result.setHeight(qMax(textRect.height(), iconSize.height()) + mMargins.top() + mMargins.bottom());
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPLegend
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPLegend
 | |
|   \brief Manages a legend inside a QCustomPlot.
 | |
| 
 | |
|   A legend is a small box somewhere in the plot which lists plottables with their name and icon.
 | |
| 
 | |
|   Normally, the legend is populated by calling \ref QCPAbstractPlottable::addToLegend. The
 | |
|   respective legend item can be removed with \ref QCPAbstractPlottable::removeFromLegend. However,
 | |
|   QCPLegend also offers an interface to add and manipulate legend items directly: \ref item, \ref
 | |
|   itemWithPlottable, \ref itemCount, \ref addItem, \ref removeItem, etc.
 | |
| 
 | |
|   Since \ref QCPLegend derives from \ref QCPLayoutGrid, it can be placed in any position a \ref
 | |
|   QCPLayoutElement may be positioned. The legend items are themselves \ref QCPLayoutElement
 | |
|   "QCPLayoutElements" which are placed in the grid layout of the legend. \ref QCPLegend only adds
 | |
|   an interface specialized for handling child elements of type \ref QCPAbstractLegendItem, as
 | |
|   mentioned above. In principle, any other layout elements may also be added to a legend via the
 | |
|   normal \ref QCPLayoutGrid interface. See the special page about \link thelayoutsystem The Layout
 | |
|   System\endlink for examples on how to add other elements to the legend and move it outside the axis
 | |
|   rect.
 | |
| 
 | |
|   Use the methods \ref setFillOrder and \ref setWrap inherited from \ref QCPLayoutGrid to control
 | |
|   in which order (column first or row first) the legend is filled up when calling \ref addItem, and
 | |
|   at which column or row wrapping occurs.
 | |
| 
 | |
|   By default, every QCustomPlot has one legend (\ref QCustomPlot::legend) which is placed in the
 | |
|   inset layout of the main axis rect (\ref QCPAxisRect::insetLayout). To move the legend to another
 | |
|   position inside the axis rect, use the methods of the \ref QCPLayoutInset. To move the legend
 | |
|   outside of the axis rect, place it anywhere else with the \ref QCPLayout/\ref QCPLayoutElement
 | |
|   interface.
 | |
| */
 | |
| 
 | |
| /* start of documentation of signals */
 | |
| 
 | |
| /*! \fn void QCPLegend::selectionChanged(QCPLegend::SelectableParts selection);
 | |
| 
 | |
|   This signal is emitted when the selection state of this legend has changed.
 | |
|   
 | |
|   \see setSelectedParts, setSelectableParts
 | |
| */
 | |
| 
 | |
| /* end of documentation of signals */
 | |
| 
 | |
| /*!
 | |
|   Constructs a new QCPLegend instance with default values.
 | |
|   
 | |
|   Note that by default, QCustomPlot already contains a legend ready to be used as \ref
 | |
|   QCustomPlot::legend
 | |
| */
 | |
| QCPLegend::QCPLegend()
 | |
| {
 | |
|   setFillOrder(QCPLayoutGrid::foRowsFirst);
 | |
|   setWrap(0);
 | |
|   
 | |
|   setRowSpacing(3);
 | |
|   setColumnSpacing(8);
 | |
|   setMargins(QMargins(7, 5, 7, 4));
 | |
|   setAntialiased(false);
 | |
|   setIconSize(32, 18);
 | |
|   
 | |
|   setIconTextPadding(7);
 | |
|   
 | |
|   setSelectableParts(spLegendBox | spItems);
 | |
|   setSelectedParts(spNone);
 | |
|   
 | |
|   setBorderPen(QPen(Qt::black, 0));
 | |
|   setSelectedBorderPen(QPen(Qt::blue, 2));
 | |
|   setIconBorderPen(Qt::NoPen);
 | |
|   setSelectedIconBorderPen(QPen(Qt::blue, 2));
 | |
|   setBrush(Qt::white);
 | |
|   setSelectedBrush(Qt::white);
 | |
|   setTextColor(Qt::black);
 | |
|   setSelectedTextColor(Qt::blue);
 | |
| }
 | |
| 
 | |
| QCPLegend::~QCPLegend()
 | |
| {
 | |
|   clearItems();
 | |
|   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)
 | |
|     mParentPlot->legendRemoved(this);
 | |
| }
 | |
| 
 | |
| /* no doc for getter, see setSelectedParts */
 | |
| QCPLegend::SelectableParts QCPLegend::selectedParts() const
 | |
| {
 | |
|   // check whether any legend elements selected, if yes, add spItems to return value
 | |
|   bool hasSelectedItems = false;
 | |
|   for (int i=0; i<itemCount(); ++i)
 | |
|   {
 | |
|     if (item(i) && item(i)->selected())
 | |
|     {
 | |
|       hasSelectedItems = true;
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
|   if (hasSelectedItems)
 | |
|     return mSelectedParts | spItems;
 | |
|   else
 | |
|     return mSelectedParts & ~spItems;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the pen, the border of the entire legend is drawn with.
 | |
| */
 | |
| void QCPLegend::setBorderPen(const QPen &pen)
 | |
| {
 | |
|   mBorderPen = pen;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the brush of the legend background.
 | |
| */
 | |
| void QCPLegend::setBrush(const QBrush &brush)
 | |
| {
 | |
|   mBrush = brush;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the default font of legend text. Legend items that draw text (e.g. the name of a graph) will
 | |
|   use this font by default. However, a different font can be specified on a per-item-basis by
 | |
|   accessing the specific legend item.
 | |
|   
 | |
|   This function will also set \a font on all already existing legend items.
 | |
|   
 | |
|   \see QCPAbstractLegendItem::setFont
 | |
| */
 | |
| void QCPLegend::setFont(const QFont &font)
 | |
| {
 | |
|   mFont = font;
 | |
|   for (int i=0; i<itemCount(); ++i)
 | |
|   {
 | |
|     if (item(i))
 | |
|       item(i)->setFont(mFont);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the default color of legend text. Legend items that draw text (e.g. the name of a graph)
 | |
|   will use this color by default. However, a different colors can be specified on a per-item-basis
 | |
|   by accessing the specific legend item.
 | |
|   
 | |
|   This function will also set \a color on all already existing legend items.
 | |
|   
 | |
|   \see QCPAbstractLegendItem::setTextColor
 | |
| */
 | |
| void QCPLegend::setTextColor(const QColor &color)
 | |
| {
 | |
|   mTextColor = color;
 | |
|   for (int i=0; i<itemCount(); ++i)
 | |
|   {
 | |
|     if (item(i))
 | |
|       item(i)->setTextColor(color);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the size of legend icons. Legend items that draw an icon (e.g. a visual
 | |
|   representation of the graph) will use this size by default.
 | |
| */
 | |
| void QCPLegend::setIconSize(const QSize &size)
 | |
| {
 | |
|   mIconSize = size;
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
| */
 | |
| void QCPLegend::setIconSize(int width, int height)
 | |
| {
 | |
|   mIconSize.setWidth(width);
 | |
|   mIconSize.setHeight(height);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the horizontal space in pixels between the legend icon and the text next to it.
 | |
|   Legend items that draw an icon (e.g. a visual representation of the graph) and text (e.g. the
 | |
|   name of the graph) will use this space by default.
 | |
| */
 | |
| void QCPLegend::setIconTextPadding(int padding)
 | |
| {
 | |
|   mIconTextPadding = padding;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the pen used to draw a border around each legend icon. Legend items that draw an
 | |
|   icon (e.g. a visual representation of the graph) will use this pen by default.
 | |
|   
 | |
|   If no border is wanted, set this to \a Qt::NoPen.
 | |
| */
 | |
| void QCPLegend::setIconBorderPen(const QPen &pen)
 | |
| {
 | |
|   mIconBorderPen = pen;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets whether the user can (de-)select the parts in \a selectable by clicking on the QCustomPlot surface.
 | |
|   (When \ref QCustomPlot::setInteractions contains \ref QCP::iSelectLegend.)
 | |
|   
 | |
|   However, even when \a selectable is set to a value not allowing the selection of a specific part,
 | |
|   it is still possible to set the selection of this part manually, by calling \ref setSelectedParts
 | |
|   directly.
 | |
|   
 | |
|   \see SelectablePart, setSelectedParts
 | |
| */
 | |
| void QCPLegend::setSelectableParts(const SelectableParts &selectable)
 | |
| {
 | |
|   if (mSelectableParts != selectable)
 | |
|   {
 | |
|     mSelectableParts = selectable;
 | |
|     emit selectableChanged(mSelectableParts);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the selected state of the respective legend parts described by \ref SelectablePart. When a part
 | |
|   is selected, it uses a different pen/font and brush. If some legend items are selected and \a selected
 | |
|   doesn't contain \ref spItems, those items become deselected.
 | |
|   
 | |
|   The entire selection mechanism is handled automatically when \ref QCustomPlot::setInteractions
 | |
|   contains iSelectLegend. You only need to call this function when you wish to change the selection
 | |
|   state manually.
 | |
|   
 | |
|   This function can change the selection state of a part even when \ref setSelectableParts was set to a
 | |
|   value that actually excludes the part.
 | |
|   
 | |
|   emits the \ref selectionChanged signal when \a selected is different from the previous selection state.
 | |
|   
 | |
|   Note that it doesn't make sense to set the selected state \ref spItems here when it wasn't set
 | |
|   before, because there's no way to specify which exact items to newly select. Do this by calling
 | |
|   \ref QCPAbstractLegendItem::setSelected directly on the legend item you wish to select.
 | |
|   
 | |
|   \see SelectablePart, setSelectableParts, selectTest, setSelectedBorderPen, setSelectedIconBorderPen, setSelectedBrush,
 | |
|   setSelectedFont
 | |
| */
 | |
| void QCPLegend::setSelectedParts(const SelectableParts &selected)
 | |
| {
 | |
|   SelectableParts newSelected = selected;
 | |
|   mSelectedParts = this->selectedParts(); // update mSelectedParts in case item selection changed
 | |
| 
 | |
|   if (mSelectedParts != newSelected)
 | |
|   {
 | |
|     if (!mSelectedParts.testFlag(spItems) && newSelected.testFlag(spItems)) // attempt to set spItems flag (can't do that)
 | |
|     {
 | |
|       qDebug() << Q_FUNC_INFO << "spItems flag can not be set, it can only be unset with this function";
 | |
|       newSelected &= ~spItems;
 | |
|     }
 | |
|     if (mSelectedParts.testFlag(spItems) && !newSelected.testFlag(spItems)) // spItems flag was unset, so clear item selection
 | |
|     {
 | |
|       for (int i=0; i<itemCount(); ++i)
 | |
|       {
 | |
|         if (item(i))
 | |
|           item(i)->setSelected(false);
 | |
|       }
 | |
|     }
 | |
|     mSelectedParts = newSelected;
 | |
|     emit selectionChanged(mSelectedParts);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   When the legend box is selected, this pen is used to draw the border instead of the normal pen
 | |
|   set via \ref setBorderPen.
 | |
| 
 | |
|   \see setSelectedParts, setSelectableParts, setSelectedBrush
 | |
| */
 | |
| void QCPLegend::setSelectedBorderPen(const QPen &pen)
 | |
| {
 | |
|   mSelectedBorderPen = pen;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the pen legend items will use to draw their icon borders, when they are selected.
 | |
| 
 | |
|   \see setSelectedParts, setSelectableParts, setSelectedFont
 | |
| */
 | |
| void QCPLegend::setSelectedIconBorderPen(const QPen &pen)
 | |
| {
 | |
|   mSelectedIconBorderPen = pen;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   When the legend box is selected, this brush is used to draw the legend background instead of the normal brush
 | |
|   set via \ref setBrush.
 | |
| 
 | |
|   \see setSelectedParts, setSelectableParts, setSelectedBorderPen
 | |
| */
 | |
| void QCPLegend::setSelectedBrush(const QBrush &brush)
 | |
| {
 | |
|   mSelectedBrush = brush;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the default font that is used by legend items when they are selected.
 | |
|   
 | |
|   This function will also set \a font on all already existing legend items.
 | |
| 
 | |
|   \see setFont, QCPAbstractLegendItem::setSelectedFont
 | |
| */
 | |
| void QCPLegend::setSelectedFont(const QFont &font)
 | |
| {
 | |
|   mSelectedFont = font;
 | |
|   for (int i=0; i<itemCount(); ++i)
 | |
|   {
 | |
|     if (item(i))
 | |
|       item(i)->setSelectedFont(font);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the default text color that is used by legend items when they are selected.
 | |
|   
 | |
|   This function will also set \a color on all already existing legend items.
 | |
| 
 | |
|   \see setTextColor, QCPAbstractLegendItem::setSelectedTextColor
 | |
| */
 | |
| void QCPLegend::setSelectedTextColor(const QColor &color)
 | |
| {
 | |
|   mSelectedTextColor = color;
 | |
|   for (int i=0; i<itemCount(); ++i)
 | |
|   {
 | |
|     if (item(i))
 | |
|       item(i)->setSelectedTextColor(color);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the item with index \a i.
 | |
| 
 | |
|   Note that the linear index depends on the current fill order (\ref setFillOrder).
 | |
| 
 | |
|   \see itemCount, addItem, itemWithPlottable
 | |
| */
 | |
| QCPAbstractLegendItem *QCPLegend::item(int index) const
 | |
| {
 | |
|   return qobject_cast<QCPAbstractLegendItem*>(elementAt(index));
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the QCPPlottableLegendItem which is associated with \a plottable (e.g. a \ref QCPGraph*).
 | |
|   If such an item isn't in the legend, returns 0.
 | |
|   
 | |
|   \see hasItemWithPlottable
 | |
| */
 | |
| QCPPlottableLegendItem *QCPLegend::itemWithPlottable(const QCPAbstractPlottable *plottable) const
 | |
| {
 | |
|   for (int i=0; i<itemCount(); ++i)
 | |
|   {
 | |
|     if (QCPPlottableLegendItem *pli = qobject_cast<QCPPlottableLegendItem*>(item(i)))
 | |
|     {
 | |
|       if (pli->plottable() == plottable)
 | |
|         return pli;
 | |
|     }
 | |
|   }
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the number of items currently in the legend.
 | |
| 
 | |
|   Note that if empty cells are in the legend (e.g. by calling methods of the \ref QCPLayoutGrid
 | |
|   base class which allows creating empty cells), they are included in the returned count.
 | |
| 
 | |
|   \see item
 | |
| */
 | |
| int QCPLegend::itemCount() const
 | |
| {
 | |
|   return elementCount();
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns whether the legend contains \a item.
 | |
|   
 | |
|   \see hasItemWithPlottable
 | |
| */
 | |
| bool QCPLegend::hasItem(QCPAbstractLegendItem *item) const
 | |
| {
 | |
|   for (int i=0; i<itemCount(); ++i)
 | |
|   {
 | |
|     if (item == this->item(i))
 | |
|         return true;
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns whether the legend contains a QCPPlottableLegendItem which is associated with \a plottable (e.g. a \ref QCPGraph*).
 | |
|   If such an item isn't in the legend, returns false.
 | |
|   
 | |
|   \see itemWithPlottable
 | |
| */
 | |
| bool QCPLegend::hasItemWithPlottable(const QCPAbstractPlottable *plottable) const
 | |
| {
 | |
|   return itemWithPlottable(plottable);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Adds \a item to the legend, if it's not present already. The element is arranged according to the
 | |
|   current fill order (\ref setFillOrder) and wrapping (\ref setWrap).
 | |
| 
 | |
|   Returns true on sucess, i.e. if the item wasn't in the list already and has been successfuly added.
 | |
| 
 | |
|   The legend takes ownership of the item.
 | |
| 
 | |
|   \see removeItem, item, hasItem
 | |
| */
 | |
| bool QCPLegend::addItem(QCPAbstractLegendItem *item)
 | |
| {
 | |
|   return addElement(item);
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
| 
 | |
|   Removes the item with the specified \a index from the legend and deletes it.
 | |
| 
 | |
|   After successful removal, the legend is reordered according to the current fill order (\ref
 | |
|   setFillOrder) and wrapping (\ref setWrap), so no empty cell remains where the removed \a item
 | |
|   was. If you don't want this, rather use the raw element interface of \ref QCPLayoutGrid.
 | |
| 
 | |
|   Returns true, if successful. Unlike \ref QCPLayoutGrid::removeAt, this method only removes
 | |
|   elements derived from \ref QCPAbstractLegendItem.
 | |
| 
 | |
|   \see itemCount, clearItems
 | |
| */
 | |
| bool QCPLegend::removeItem(int index)
 | |
| {
 | |
|   if (QCPAbstractLegendItem *ali = item(index))
 | |
|   {
 | |
|     bool success = remove(ali);
 | |
|     if (success)
 | |
|       setFillOrder(fillOrder(), true); // gets rid of empty cell by reordering
 | |
|     return success;
 | |
|   } else
 | |
|     return false;
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
| 
 | |
|   Removes \a item from the legend and deletes it.
 | |
| 
 | |
|   After successful removal, the legend is reordered according to the current fill order (\ref
 | |
|   setFillOrder) and wrapping (\ref setWrap), so no empty cell remains where the removed \a item
 | |
|   was. If you don't want this, rather use the raw element interface of \ref QCPLayoutGrid.
 | |
| 
 | |
|   Returns true, if successful.
 | |
| 
 | |
|   \see clearItems
 | |
| */
 | |
| bool QCPLegend::removeItem(QCPAbstractLegendItem *item)
 | |
| {
 | |
|   bool success = remove(item);
 | |
|   if (success)
 | |
|     setFillOrder(fillOrder(), true); // gets rid of empty cell by reordering
 | |
|   return success;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Removes all items from the legend.
 | |
| */
 | |
| void QCPLegend::clearItems()
 | |
| {
 | |
|   for (int i=itemCount()-1; i>=0; --i)
 | |
|     removeItem(i);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the legend items that are currently selected. If no items are selected,
 | |
|   the list is empty.
 | |
|   
 | |
|   \see QCPAbstractLegendItem::setSelected, setSelectable
 | |
| */
 | |
| QList<QCPAbstractLegendItem *> QCPLegend::selectedItems() const
 | |
| {
 | |
|   QList<QCPAbstractLegendItem*> result;
 | |
|   for (int i=0; i<itemCount(); ++i)
 | |
|   {
 | |
|     if (QCPAbstractLegendItem *ali = item(i))
 | |
|     {
 | |
|       if (ali->selected())
 | |
|         result.append(ali);
 | |
|     }
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   A convenience function to easily set the QPainter::Antialiased hint on the provided \a painter
 | |
|   before drawing main legend elements.
 | |
| 
 | |
|   This is the antialiasing state the painter passed to the \ref draw method is in by default.
 | |
|   
 | |
|   This function takes into account the local setting of the antialiasing flag as well as the
 | |
|   overrides set with \ref QCustomPlot::setAntialiasedElements and \ref
 | |
|   QCustomPlot::setNotAntialiasedElements.
 | |
|   
 | |
|   \seebaseclassmethod
 | |
|   
 | |
|   \see setAntialiased
 | |
| */
 | |
| void QCPLegend::applyDefaultAntialiasingHint(QCPPainter *painter) const
 | |
| {
 | |
|   applyAntialiasingHint(painter, mAntialiased, QCP::aeLegend);
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns the pen used to paint the border of the legend, taking into account the selection state
 | |
|   of the legend box.
 | |
| */
 | |
| QPen QCPLegend::getBorderPen() const
 | |
| {
 | |
|   return mSelectedParts.testFlag(spLegendBox) ? mSelectedBorderPen : mBorderPen;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns the brush used to paint the background of the legend, taking into account the selection
 | |
|   state of the legend box.
 | |
| */
 | |
| QBrush QCPLegend::getBrush() const
 | |
| {
 | |
|   return mSelectedParts.testFlag(spLegendBox) ? mSelectedBrush : mBrush;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Draws the legend box with the provided \a painter. The individual legend items are layerables
 | |
|   themselves, thus are drawn independently.
 | |
| */
 | |
| void QCPLegend::draw(QCPPainter *painter)
 | |
| {
 | |
|   // draw background rect:
 | |
|   painter->setBrush(getBrush());
 | |
|   painter->setPen(getBorderPen());
 | |
|   painter->drawRect(mOuterRect);
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| double QCPLegend::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
 | |
| {
 | |
|   if (!mParentPlot) return -1;
 | |
|   if (onlySelectable && !mSelectableParts.testFlag(spLegendBox))
 | |
|     return -1;
 | |
|   
 | |
|   if (mOuterRect.contains(pos.toPoint()))
 | |
|   {
 | |
|     if (details) details->setValue(spLegendBox);
 | |
|     return mParentPlot->selectionTolerance()*0.99;
 | |
|   }
 | |
|   return -1;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPLegend::selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged)
 | |
| {
 | |
|   Q_UNUSED(event)
 | |
|   mSelectedParts = selectedParts(); // in case item selection has changed
 | |
|   if (details.value<SelectablePart>() == spLegendBox && mSelectableParts.testFlag(spLegendBox))
 | |
|   {
 | |
|     SelectableParts selBefore = mSelectedParts;
 | |
|     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)
 | |
|     if (selectionStateChanged)
 | |
|       *selectionStateChanged = mSelectedParts != selBefore;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPLegend::deselectEvent(bool *selectionStateChanged)
 | |
| {
 | |
|   mSelectedParts = selectedParts(); // in case item selection has changed
 | |
|   if (mSelectableParts.testFlag(spLegendBox))
 | |
|   {
 | |
|     SelectableParts selBefore = mSelectedParts;
 | |
|     setSelectedParts(selectedParts() & ~spLegendBox);
 | |
|     if (selectionStateChanged)
 | |
|       *selectionStateChanged = mSelectedParts != selBefore;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| QCP::Interaction QCPLegend::selectionCategory() const
 | |
| {
 | |
|   return QCP::iSelectLegend;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| QCP::Interaction QCPAbstractLegendItem::selectionCategory() const
 | |
| {
 | |
|   return QCP::iSelectLegend;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPLegend::parentPlotInitialized(QCustomPlot *parentPlot)
 | |
| {
 | |
|   if (parentPlot && !parentPlot->legend)
 | |
|     parentPlot->legend = this;
 | |
| }
 | |
| /* end of 'src/layoutelements/layoutelement-legend.cpp' */
 | |
| 
 | |
| 
 | |
| /* including file 'src/layoutelements/layoutelement-textelement.cpp', size 12759 */
 | |
| /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200     */
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPTextElement
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPTextElement
 | |
|   \brief A layout element displaying a text
 | |
| 
 | |
|   The text may be specified with \ref setText, the formatting can be controlled with \ref setFont,
 | |
|   \ref setTextColor, and \ref setTextFlags.
 | |
| 
 | |
|   A text element can be added as follows:
 | |
|   \snippet documentation/doc-code-snippets/mainwindow.cpp qcptextelement-creation
 | |
| */
 | |
| 
 | |
| /* start documentation of signals */
 | |
| 
 | |
| /*! \fn void QCPTextElement::selectionChanged(bool selected)
 | |
|   
 | |
|   This signal is emitted when the selection state has changed to \a selected, either by user
 | |
|   interaction or by a direct call to \ref setSelected.
 | |
|   
 | |
|   \see setSelected, setSelectable
 | |
| */
 | |
| 
 | |
| /*! \fn void QCPTextElement::clicked(QMouseEvent *event)
 | |
| 
 | |
|   This signal is emitted when the text element is clicked.
 | |
| 
 | |
|   \see doubleClicked, selectTest
 | |
| */
 | |
| 
 | |
| /*! \fn void QCPTextElement::doubleClicked(QMouseEvent *event)
 | |
| 
 | |
|   This signal is emitted when the text element is double clicked.
 | |
| 
 | |
|   \see clicked, selectTest
 | |
| */
 | |
| 
 | |
| /* end documentation of signals */
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Creates a new QCPTextElement instance and sets default values. The initial text is empty (\ref
 | |
|   setText).
 | |
| */
 | |
| QCPTextElement::QCPTextElement(QCustomPlot *parentPlot) :
 | |
|   QCPLayoutElement(parentPlot),
 | |
|   mText(),
 | |
|   mTextFlags(Qt::AlignCenter|Qt::TextWordWrap),
 | |
|   mFont(QFont(QLatin1String("sans serif"), 12)), // will be taken from parentPlot if available, see below
 | |
|   mTextColor(Qt::black),
 | |
|   mSelectedFont(QFont(QLatin1String("sans serif"), 12)), // will be taken from parentPlot if available, see below
 | |
|   mSelectedTextColor(Qt::blue),
 | |
|   mSelectable(false),
 | |
|   mSelected(false)
 | |
| {
 | |
|   if (parentPlot)
 | |
|   {
 | |
|     mFont = parentPlot->font();
 | |
|     mSelectedFont = parentPlot->font();
 | |
|   }
 | |
|   setMargins(QMargins(2, 2, 2, 2));
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Creates a new QCPTextElement instance and sets default values.
 | |
| 
 | |
|   The initial text is set to \a text.
 | |
| */
 | |
| QCPTextElement::QCPTextElement(QCustomPlot *parentPlot, const QString &text) :
 | |
|   QCPLayoutElement(parentPlot),
 | |
|   mText(text),
 | |
|   mTextFlags(Qt::AlignCenter|Qt::TextWordWrap),
 | |
|   mFont(QFont(QLatin1String("sans serif"), 12)), // will be taken from parentPlot if available, see below
 | |
|   mTextColor(Qt::black),
 | |
|   mSelectedFont(QFont(QLatin1String("sans serif"), 12)), // will be taken from parentPlot if available, see below
 | |
|   mSelectedTextColor(Qt::blue),
 | |
|   mSelectable(false),
 | |
|   mSelected(false)
 | |
| {
 | |
|   if (parentPlot)
 | |
|   {
 | |
|     mFont = parentPlot->font();
 | |
|     mSelectedFont = parentPlot->font();
 | |
|   }
 | |
|   setMargins(QMargins(2, 2, 2, 2));
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Creates a new QCPTextElement instance and sets default values.
 | |
| 
 | |
|   The initial text is set to \a text with \a pointSize.
 | |
| */
 | |
| QCPTextElement::QCPTextElement(QCustomPlot *parentPlot, const QString &text, double pointSize) :
 | |
|   QCPLayoutElement(parentPlot),
 | |
|   mText(text),
 | |
|   mTextFlags(Qt::AlignCenter|Qt::TextWordWrap),
 | |
|   mFont(QFont(QLatin1String("sans serif"), pointSize)), // will be taken from parentPlot if available, see below
 | |
|   mTextColor(Qt::black),
 | |
|   mSelectedFont(QFont(QLatin1String("sans serif"), pointSize)), // will be taken from parentPlot if available, see below
 | |
|   mSelectedTextColor(Qt::blue),
 | |
|   mSelectable(false),
 | |
|   mSelected(false)
 | |
| {
 | |
|   if (parentPlot)
 | |
|   {
 | |
|     mFont = parentPlot->font();
 | |
|     mFont.setPointSizeF(pointSize);
 | |
|     mSelectedFont = parentPlot->font();
 | |
|     mSelectedFont.setPointSizeF(pointSize);
 | |
|   }
 | |
|   setMargins(QMargins(2, 2, 2, 2));
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Creates a new QCPTextElement instance and sets default values.
 | |
| 
 | |
|   The initial text is set to \a text with \a pointSize and the specified \a fontFamily.
 | |
| */
 | |
| QCPTextElement::QCPTextElement(QCustomPlot *parentPlot, const QString &text, const QString &fontFamily, double pointSize) :
 | |
|   QCPLayoutElement(parentPlot),
 | |
|   mText(text),
 | |
|   mTextFlags(Qt::AlignCenter|Qt::TextWordWrap),
 | |
|   mFont(QFont(fontFamily, pointSize)),
 | |
|   mTextColor(Qt::black),
 | |
|   mSelectedFont(QFont(fontFamily, pointSize)),
 | |
|   mSelectedTextColor(Qt::blue),
 | |
|   mSelectable(false),
 | |
|   mSelected(false)
 | |
| {
 | |
|   setMargins(QMargins(2, 2, 2, 2));
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Creates a new QCPTextElement instance and sets default values.
 | |
| 
 | |
|   The initial text is set to \a text with the specified \a font.
 | |
| */
 | |
| QCPTextElement::QCPTextElement(QCustomPlot *parentPlot, const QString &text, const QFont &font) :
 | |
|   QCPLayoutElement(parentPlot),
 | |
|   mText(text),
 | |
|   mTextFlags(Qt::AlignCenter|Qt::TextWordWrap),
 | |
|   mFont(font),
 | |
|   mTextColor(Qt::black),
 | |
|   mSelectedFont(font),
 | |
|   mSelectedTextColor(Qt::blue),
 | |
|   mSelectable(false),
 | |
|   mSelected(false)
 | |
| {
 | |
|   setMargins(QMargins(2, 2, 2, 2));
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the text that will be displayed to \a text. Multiple lines can be created by insertion of "\n".
 | |
|   
 | |
|   \see setFont, setTextColor, setTextFlags
 | |
| */
 | |
| void QCPTextElement::setText(const QString &text)
 | |
| {
 | |
|   mText = text;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets options for text alignment and wrapping behaviour. \a flags is a bitwise OR-combination of
 | |
|   \c Qt::AlignmentFlag and \c Qt::TextFlag enums.
 | |
|   
 | |
|   Possible enums are:
 | |
|   - Qt::AlignLeft
 | |
|   - Qt::AlignRight
 | |
|   - Qt::AlignHCenter
 | |
|   - Qt::AlignJustify
 | |
|   - Qt::AlignTop
 | |
|   - Qt::AlignBottom
 | |
|   - Qt::AlignVCenter
 | |
|   - Qt::AlignCenter
 | |
|   - Qt::TextDontClip
 | |
|   - Qt::TextSingleLine
 | |
|   - Qt::TextExpandTabs
 | |
|   - Qt::TextShowMnemonic
 | |
|   - Qt::TextWordWrap
 | |
|   - Qt::TextIncludeTrailingSpaces
 | |
| */
 | |
| void QCPTextElement::setTextFlags(int flags)
 | |
| {
 | |
|   mTextFlags = flags;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the \a font of the text.
 | |
|   
 | |
|   \see setTextColor, setSelectedFont
 | |
| */
 | |
| void QCPTextElement::setFont(const QFont &font)
 | |
| {
 | |
|   mFont = font;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the \a color of the text.
 | |
|   
 | |
|   \see setFont, setSelectedTextColor
 | |
| */
 | |
| void QCPTextElement::setTextColor(const QColor &color)
 | |
| {
 | |
|   mTextColor = color;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the \a font of the text that will be used if the text element is selected (\ref setSelected).
 | |
|   
 | |
|   \see setFont
 | |
| */
 | |
| void QCPTextElement::setSelectedFont(const QFont &font)
 | |
| {
 | |
|   mSelectedFont = font;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the \a color of the text that will be used if the text element is selected (\ref setSelected).
 | |
|   
 | |
|   \see setTextColor
 | |
| */
 | |
| void QCPTextElement::setSelectedTextColor(const QColor &color)
 | |
| {
 | |
|   mSelectedTextColor = color;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets whether the user may select this text element.
 | |
| 
 | |
|   Note that even when \a selectable is set to <tt>false</tt>, the selection state may be changed
 | |
|   programmatically via \ref setSelected.
 | |
| */
 | |
| void QCPTextElement::setSelectable(bool selectable)
 | |
| {
 | |
|   if (mSelectable != selectable)
 | |
|   {
 | |
|     mSelectable = selectable;
 | |
|     emit selectableChanged(mSelectable);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the selection state of this text element to \a selected. If the selection has changed, \ref
 | |
|   selectionChanged is emitted.
 | |
|   
 | |
|   Note that this function can change the selection state independently of the current \ref
 | |
|   setSelectable state.
 | |
| */
 | |
| void QCPTextElement::setSelected(bool selected)
 | |
| {
 | |
|   if (mSelected != selected)
 | |
|   {
 | |
|     mSelected = selected;
 | |
|     emit selectionChanged(mSelected);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPTextElement::applyDefaultAntialiasingHint(QCPPainter *painter) const
 | |
| {
 | |
|   applyAntialiasingHint(painter, mAntialiased, QCP::aeOther);
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPTextElement::draw(QCPPainter *painter)
 | |
| {
 | |
|   painter->setFont(mainFont());
 | |
|   painter->setPen(QPen(mainTextColor()));
 | |
|   painter->drawText(mRect, Qt::AlignCenter, mText, &mTextBoundingRect);
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| QSize QCPTextElement::minimumSizeHint() const
 | |
| {
 | |
|   QFontMetrics metrics(mFont);
 | |
|   QSize result = metrics.boundingRect(0, 0, 0, 0, Qt::AlignCenter, mText).size();
 | |
|   result.rwidth() += mMargins.left() + mMargins.right();
 | |
|   result.rheight() += mMargins.top() + mMargins.bottom();
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| QSize QCPTextElement::maximumSizeHint() const
 | |
| {
 | |
|   QFontMetrics metrics(mFont);
 | |
|   QSize result = metrics.boundingRect(0, 0, 0, 0, Qt::AlignCenter, mText).size();
 | |
|   result.rheight() += mMargins.top() + mMargins.bottom();
 | |
|   result.setWidth(QWIDGETSIZE_MAX);
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPTextElement::selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged)
 | |
| {
 | |
|   Q_UNUSED(event)
 | |
|   Q_UNUSED(details)
 | |
|   if (mSelectable)
 | |
|   {
 | |
|     bool selBefore = mSelected;
 | |
|     setSelected(additive ? !mSelected : true);
 | |
|     if (selectionStateChanged)
 | |
|       *selectionStateChanged = mSelected != selBefore;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPTextElement::deselectEvent(bool *selectionStateChanged)
 | |
| {
 | |
|   if (mSelectable)
 | |
|   {
 | |
|     bool selBefore = mSelected;
 | |
|     setSelected(false);
 | |
|     if (selectionStateChanged)
 | |
|       *selectionStateChanged = mSelected != selBefore;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns 0.99*selectionTolerance (see \ref QCustomPlot::setSelectionTolerance) when \a pos is
 | |
|   within the bounding box of the text element's text. Note that this bounding box is updated in the
 | |
|   draw call.
 | |
| 
 | |
|   If \a pos is outside the text's bounding box or if \a onlySelectable is true and this text
 | |
|   element is not selectable (\ref setSelectable), returns -1.
 | |
| 
 | |
|   \seebaseclassmethod
 | |
| */
 | |
| double QCPTextElement::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
 | |
| {
 | |
|   Q_UNUSED(details)
 | |
|   if (onlySelectable && !mSelectable)
 | |
|     return -1;
 | |
|   
 | |
|   if (mTextBoundingRect.contains(pos.toPoint()))
 | |
|     return mParentPlot->selectionTolerance()*0.99;
 | |
|   else
 | |
|     return -1;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Accepts the mouse event in order to emit the according click signal in the \ref
 | |
|   mouseReleaseEvent.
 | |
| 
 | |
|   \seebaseclassmethod
 | |
| */
 | |
| void QCPTextElement::mousePressEvent(QMouseEvent *event, const QVariant &details)
 | |
| {
 | |
|   Q_UNUSED(details)
 | |
|   event->accept();
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Emits the \ref clicked signal if the cursor hasn't moved by more than a few pixels since the \ref
 | |
|   mousePressEvent.
 | |
| 
 | |
|   \seebaseclassmethod
 | |
| */
 | |
| void QCPTextElement::mouseReleaseEvent(QMouseEvent *event, const QPointF &startPos)
 | |
| {
 | |
|   if ((QPointF(event->pos())-startPos).manhattanLength() <= 3)
 | |
|     emit clicked(event);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Emits the \ref doubleClicked signal.
 | |
| 
 | |
|   \seebaseclassmethod
 | |
| */
 | |
| void QCPTextElement::mouseDoubleClickEvent(QMouseEvent *event, const QVariant &details)
 | |
| {
 | |
|   Q_UNUSED(details)
 | |
|   emit doubleClicked(event);
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns the main font to be used. This is mSelectedFont if \ref setSelected is set to
 | |
|   <tt>true</tt>, else mFont is returned.
 | |
| */
 | |
| QFont QCPTextElement::mainFont() const
 | |
| {
 | |
|   return mSelected ? mSelectedFont : mFont;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns the main color to be used. This is mSelectedTextColor if \ref setSelected is set to
 | |
|   <tt>true</tt>, else mTextColor is returned.
 | |
| */
 | |
| QColor QCPTextElement::mainTextColor() const
 | |
| {
 | |
|   return mSelected ? mSelectedTextColor : mTextColor;
 | |
| }
 | |
| /* end of 'src/layoutelements/layoutelement-textelement.cpp' */
 | |
| 
 | |
| 
 | |
| /* including file 'src/layoutelements/layoutelement-colorscale.cpp', size 25910 */
 | |
| /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200    */
 | |
| 
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPColorScale
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPColorScale
 | |
|   \brief A color scale for use with color coding data such as QCPColorMap
 | |
|   
 | |
|   This layout element can be placed on the plot to correlate a color gradient with data values. It
 | |
|   is usually used in combination with one or multiple \ref QCPColorMap "QCPColorMaps".
 | |
| 
 | |
|   \image html QCPColorScale.png
 | |
|   
 | |
|   The color scale can be either horizontal or vertical, as shown in the image above. The
 | |
|   orientation and the side where the numbers appear is controlled with \ref setType.
 | |
|   
 | |
|   Use \ref QCPColorMap::setColorScale to connect a color map with a color scale. Once they are
 | |
|   connected, they share their gradient, data range and data scale type (\ref setGradient, \ref
 | |
|   setDataRange, \ref setDataScaleType). Multiple color maps may be associated with a single color
 | |
|   scale, to make them all synchronize these properties.
 | |
|   
 | |
|   To have finer control over the number display and axis behaviour, you can directly access the
 | |
|   \ref axis. See the documentation of QCPAxis for details about configuring axes. For example, if
 | |
|   you want to change the number of automatically generated ticks, call
 | |
|   \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcolorscale-tickcount
 | |
|   
 | |
|   Placing a color scale next to the main axis rect works like with any other layout element:
 | |
|   \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcolorscale-creation
 | |
|   In this case we have placed it to the right of the default axis rect, so it wasn't necessary to
 | |
|   call \ref setType, since \ref QCPAxis::atRight is already the default. The text next to the color
 | |
|   scale can be set with \ref setLabel.
 | |
|   
 | |
|   For optimum appearance (like in the image above), it may be desirable to line up the axis rect and
 | |
|   the borders of the color scale. Use a \ref QCPMarginGroup to achieve this:
 | |
|   \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcolorscale-margingroup
 | |
|   
 | |
|   Color scales are initialized with a non-zero minimum top and bottom margin (\ref
 | |
|   setMinimumMargins), because vertical color scales are most common and the minimum top/bottom
 | |
|   margin makes sure it keeps some distance to the top/bottom widget border. So if you change to a
 | |
|   horizontal color scale by setting \ref setType to \ref QCPAxis::atBottom or \ref QCPAxis::atTop, you
 | |
|   might want to also change the minimum margins accordingly, e.g. <tt>setMinimumMargins(QMargins(6, 0, 6, 0))</tt>.
 | |
| */
 | |
| 
 | |
| /* start documentation of inline functions */
 | |
| 
 | |
| /*! \fn QCPAxis *QCPColorScale::axis() const
 | |
|   
 | |
|   Returns the internal \ref QCPAxis instance of this color scale. You can access it to alter the
 | |
|   appearance and behaviour of the axis. \ref QCPColorScale duplicates some properties in its
 | |
|   interface for convenience. Those are \ref setDataRange (\ref QCPAxis::setRange), \ref
 | |
|   setDataScaleType (\ref QCPAxis::setScaleType), and the method \ref setLabel (\ref
 | |
|   QCPAxis::setLabel). As they each are connected, it does not matter whether you use the method on
 | |
|   the QCPColorScale or on its QCPAxis.
 | |
|   
 | |
|   If the type of the color scale is changed with \ref setType, the axis returned by this method
 | |
|   will change, too, to either the left, right, bottom or top axis, depending on which type was set.
 | |
| */
 | |
| 
 | |
| /* end documentation of signals */
 | |
| /* start documentation of signals */
 | |
| 
 | |
| /*! \fn void QCPColorScale::dataRangeChanged(const QCPRange &newRange);
 | |
|   
 | |
|   This signal is emitted when the data range changes.
 | |
|   
 | |
|   \see setDataRange
 | |
| */
 | |
| 
 | |
| /*! \fn void QCPColorScale::dataScaleTypeChanged(QCPAxis::ScaleType scaleType);
 | |
|   
 | |
|   This signal is emitted when the data scale type changes.
 | |
|   
 | |
|   \see setDataScaleType
 | |
| */
 | |
| 
 | |
| /*! \fn void QCPColorScale::gradientChanged(const QCPColorGradient &newGradient);
 | |
|   
 | |
|   This signal is emitted when the gradient changes.
 | |
|   
 | |
|   \see setGradient
 | |
| */
 | |
| 
 | |
| /* end documentation of signals */
 | |
| 
 | |
| /*!
 | |
|   Constructs a new QCPColorScale.
 | |
| */
 | |
| QCPColorScale::QCPColorScale(QCustomPlot *parentPlot) :
 | |
|   QCPLayoutElement(parentPlot),
 | |
|   mType(QCPAxis::atTop), // set to atTop such that setType(QCPAxis::atRight) below doesn't skip work because it thinks it's already atRight
 | |
|   mDataScaleType(QCPAxis::stLinear),
 | |
|   mBarWidth(20),
 | |
|   mAxisRect(new QCPColorScaleAxisRectPrivate(this))
 | |
| {
 | |
|   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)
 | |
|   setType(QCPAxis::atRight);
 | |
|   setDataRange(QCPRange(0, 6));
 | |
| }
 | |
| 
 | |
| QCPColorScale::~QCPColorScale()
 | |
| {
 | |
|   delete mAxisRect;
 | |
| }
 | |
| 
 | |
| /* undocumented getter */
 | |
| QString QCPColorScale::label() const
 | |
| {
 | |
|   if (!mColorAxis)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "internal color axis undefined";
 | |
|     return QString();
 | |
|   }
 | |
|   
 | |
|   return mColorAxis.data()->label();
 | |
| }
 | |
| 
 | |
| /* undocumented getter */
 | |
| bool QCPColorScale::rangeDrag() const
 | |
| {
 | |
|   if (!mAxisRect)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "internal axis rect was deleted";
 | |
|     return false;
 | |
|   }
 | |
|   
 | |
|   return mAxisRect.data()->rangeDrag().testFlag(QCPAxis::orientation(mType)) &&
 | |
|       mAxisRect.data()->rangeDragAxis(QCPAxis::orientation(mType)) &&
 | |
|       mAxisRect.data()->rangeDragAxis(QCPAxis::orientation(mType))->orientation() == QCPAxis::orientation(mType);
 | |
| }
 | |
| 
 | |
| /* undocumented getter */
 | |
| bool QCPColorScale::rangeZoom() const
 | |
| {
 | |
|   if (!mAxisRect)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "internal axis rect was deleted";
 | |
|     return false;
 | |
|   }
 | |
|   
 | |
|   return mAxisRect.data()->rangeZoom().testFlag(QCPAxis::orientation(mType)) &&
 | |
|       mAxisRect.data()->rangeZoomAxis(QCPAxis::orientation(mType)) &&
 | |
|       mAxisRect.data()->rangeZoomAxis(QCPAxis::orientation(mType))->orientation() == QCPAxis::orientation(mType);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets at which side of the color scale the axis is placed, and thus also its orientation.
 | |
|   
 | |
|   Note that after setting \a type to a different value, the axis returned by \ref axis() will
 | |
|   be a different one. The new axis will adopt the following properties from the previous axis: The
 | |
|   range, scale type, label and ticker (the latter will be shared and not copied).
 | |
| */
 | |
| void QCPColorScale::setType(QCPAxis::AxisType type)
 | |
| {
 | |
|   if (!mAxisRect)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "internal axis rect was deleted";
 | |
|     return;
 | |
|   }
 | |
|   if (mType != type)
 | |
|   {
 | |
|     mType = type;
 | |
|     QCPRange rangeTransfer(0, 6);
 | |
|     QString labelTransfer;
 | |
|     QSharedPointer<QCPAxisTicker> tickerTransfer;
 | |
|     // transfer/revert some settings on old axis if it exists:
 | |
|     bool doTransfer = (bool)mColorAxis;
 | |
|     if (doTransfer)
 | |
|     {
 | |
|       rangeTransfer = mColorAxis.data()->range();
 | |
|       labelTransfer = mColorAxis.data()->label();
 | |
|       tickerTransfer = mColorAxis.data()->ticker();
 | |
|       mColorAxis.data()->setLabel(QString());
 | |
|       disconnect(mColorAxis.data(), SIGNAL(rangeChanged(QCPRange)), this, SLOT(setDataRange(QCPRange)));
 | |
|       disconnect(mColorAxis.data(), SIGNAL(scaleTypeChanged(QCPAxis::ScaleType)), this, SLOT(setDataScaleType(QCPAxis::ScaleType)));
 | |
|     }
 | |
|     QList<QCPAxis::AxisType> allAxisTypes = QList<QCPAxis::AxisType>() << QCPAxis::atLeft << QCPAxis::atRight << QCPAxis::atBottom << QCPAxis::atTop;
 | |
|     foreach (QCPAxis::AxisType atype, allAxisTypes)
 | |
|     {
 | |
|       mAxisRect.data()->axis(atype)->setTicks(atype == mType);
 | |
|       mAxisRect.data()->axis(atype)->setTickLabels(atype== mType);
 | |
|     }
 | |
|     // set new mColorAxis pointer:
 | |
|     mColorAxis = mAxisRect.data()->axis(mType);
 | |
|     // transfer settings to new axis:
 | |
|     if (doTransfer)
 | |
|     {
 | |
|       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)
 | |
|       mColorAxis.data()->setLabel(labelTransfer);
 | |
|       mColorAxis.data()->setTicker(tickerTransfer);
 | |
|     }
 | |
|     connect(mColorAxis.data(), SIGNAL(rangeChanged(QCPRange)), this, SLOT(setDataRange(QCPRange)));
 | |
|     connect(mColorAxis.data(), SIGNAL(scaleTypeChanged(QCPAxis::ScaleType)), this, SLOT(setDataScaleType(QCPAxis::ScaleType)));
 | |
|     mAxisRect.data()->setRangeDragAxes(QList<QCPAxis*>() << mColorAxis.data());
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the range spanned by the color gradient and that is shown by the axis in the color scale.
 | |
|   
 | |
|   It is equivalent to calling QCPColorMap::setDataRange on any of the connected color maps. It is
 | |
|   also equivalent to directly accessing the \ref axis and setting its range with \ref
 | |
|   QCPAxis::setRange.
 | |
|   
 | |
|   \see setDataScaleType, setGradient, rescaleDataRange
 | |
| */
 | |
| void QCPColorScale::setDataRange(const QCPRange &dataRange)
 | |
| {
 | |
|   if (mDataRange.lower != dataRange.lower || mDataRange.upper != dataRange.upper)
 | |
|   {
 | |
|     mDataRange = dataRange;
 | |
|     if (mColorAxis)
 | |
|       mColorAxis.data()->setRange(mDataRange);
 | |
|     emit dataRangeChanged(mDataRange);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the scale type of the color scale, i.e. whether values are linearly associated with colors
 | |
|   or logarithmically.
 | |
|   
 | |
|   It is equivalent to calling QCPColorMap::setDataScaleType on any of the connected color maps. It is
 | |
|   also equivalent to directly accessing the \ref axis and setting its scale type with \ref
 | |
|   QCPAxis::setScaleType.
 | |
|   
 | |
|   \see setDataRange, setGradient
 | |
| */
 | |
| void QCPColorScale::setDataScaleType(QCPAxis::ScaleType scaleType)
 | |
| {
 | |
|   if (mDataScaleType != scaleType)
 | |
|   {
 | |
|     mDataScaleType = scaleType;
 | |
|     if (mColorAxis)
 | |
|       mColorAxis.data()->setScaleType(mDataScaleType);
 | |
|     if (mDataScaleType == QCPAxis::stLogarithmic)
 | |
|       setDataRange(mDataRange.sanitizedForLogScale());
 | |
|     emit dataScaleTypeChanged(mDataScaleType);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the color gradient that will be used to represent data values.
 | |
|   
 | |
|   It is equivalent to calling QCPColorMap::setGradient on any of the connected color maps.
 | |
|   
 | |
|   \see setDataRange, setDataScaleType
 | |
| */
 | |
| void QCPColorScale::setGradient(const QCPColorGradient &gradient)
 | |
| {
 | |
|   if (mGradient != gradient)
 | |
|   {
 | |
|     mGradient = gradient;
 | |
|     if (mAxisRect)
 | |
|       mAxisRect.data()->mGradientImageInvalidated = true;
 | |
|     emit gradientChanged(mGradient);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the axis label of the color scale. This is equivalent to calling \ref QCPAxis::setLabel on
 | |
|   the internal \ref axis.
 | |
| */
 | |
| void QCPColorScale::setLabel(const QString &str)
 | |
| {
 | |
|   if (!mColorAxis)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "internal color axis undefined";
 | |
|     return;
 | |
|   }
 | |
|   
 | |
|   mColorAxis.data()->setLabel(str);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the width (or height, for horizontal color scales) the bar where the gradient is displayed
 | |
|   will have.
 | |
| */
 | |
| void QCPColorScale::setBarWidth(int width)
 | |
| {
 | |
|   mBarWidth = width;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets whether the user can drag the data range (\ref setDataRange).
 | |
|   
 | |
|   Note that \ref QCP::iRangeDrag must be in the QCustomPlot's interactions (\ref
 | |
|   QCustomPlot::setInteractions) to allow range dragging.
 | |
| */
 | |
| void QCPColorScale::setRangeDrag(bool enabled)
 | |
| {
 | |
|   if (!mAxisRect)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "internal axis rect was deleted";
 | |
|     return;
 | |
|   }
 | |
|   
 | |
|   if (enabled)
 | |
|     mAxisRect.data()->setRangeDrag(QCPAxis::orientation(mType));
 | |
|   else
 | |
|     mAxisRect.data()->setRangeDrag(0);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets whether the user can zoom the data range (\ref setDataRange) by scrolling the mouse wheel.
 | |
|   
 | |
|   Note that \ref QCP::iRangeZoom must be in the QCustomPlot's interactions (\ref
 | |
|   QCustomPlot::setInteractions) to allow range dragging.
 | |
| */
 | |
| void QCPColorScale::setRangeZoom(bool enabled)
 | |
| {
 | |
|   if (!mAxisRect)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "internal axis rect was deleted";
 | |
|     return;
 | |
|   }
 | |
|   
 | |
|   if (enabled)
 | |
|     mAxisRect.data()->setRangeZoom(QCPAxis::orientation(mType));
 | |
|   else
 | |
|     mAxisRect.data()->setRangeZoom(0);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns a list of all the color maps associated with this color scale.
 | |
| */
 | |
| QList<QCPColorMap*> QCPColorScale::colorMaps() const
 | |
| {
 | |
|   QList<QCPColorMap*> result;
 | |
|   for (int i=0; i<mParentPlot->plottableCount(); ++i)
 | |
|   {
 | |
|     if (QCPColorMap *cm = qobject_cast<QCPColorMap*>(mParentPlot->plottable(i)))
 | |
|       if (cm->colorScale() == this)
 | |
|         result.append(cm);
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Changes the data range such that all color maps associated with this color scale are fully mapped
 | |
|   to the gradient in the data dimension.
 | |
|   
 | |
|   \see setDataRange
 | |
| */
 | |
| void QCPColorScale::rescaleDataRange(bool onlyVisibleMaps)
 | |
| {
 | |
|   QList<QCPColorMap*> maps = colorMaps();
 | |
|   QCPRange newRange;
 | |
|   bool haveRange = false;
 | |
|   QCP::SignDomain sign = QCP::sdBoth;
 | |
|   if (mDataScaleType == QCPAxis::stLogarithmic)
 | |
|     sign = (mDataRange.upper < 0 ? QCP::sdNegative : QCP::sdPositive);
 | |
|   for (int i=0; i<maps.size(); ++i)
 | |
|   {
 | |
|     if (!maps.at(i)->realVisibility() && onlyVisibleMaps)
 | |
|       continue;
 | |
|     QCPRange mapRange;
 | |
|     if (maps.at(i)->colorScale() == this)
 | |
|     {
 | |
|       bool currentFoundRange = true;
 | |
|       mapRange = maps.at(i)->data()->dataBounds();
 | |
|       if (sign == QCP::sdPositive)
 | |
|       {
 | |
|         if (mapRange.lower <= 0 && mapRange.upper > 0)
 | |
|           mapRange.lower = mapRange.upper*1e-3;
 | |
|         else if (mapRange.lower <= 0 && mapRange.upper <= 0)
 | |
|           currentFoundRange = false;
 | |
|       } else if (sign == QCP::sdNegative)
 | |
|       {
 | |
|         if (mapRange.upper >= 0 && mapRange.lower < 0)
 | |
|           mapRange.upper = mapRange.lower*1e-3;
 | |
|         else if (mapRange.upper >= 0 && mapRange.lower >= 0)
 | |
|           currentFoundRange = false;
 | |
|       }
 | |
|       if (currentFoundRange)
 | |
|       {
 | |
|         if (!haveRange)
 | |
|           newRange = mapRange;
 | |
|         else
 | |
|           newRange.expand(mapRange);
 | |
|         haveRange = true;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   if (haveRange)
 | |
|   {
 | |
|     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
 | |
|     {
 | |
|       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
 | |
|       if (mDataScaleType == QCPAxis::stLinear)
 | |
|       {
 | |
|         newRange.lower = center-mDataRange.size()/2.0;
 | |
|         newRange.upper = center+mDataRange.size()/2.0;
 | |
|       } else // mScaleType == stLogarithmic
 | |
|       {
 | |
|         newRange.lower = center/qSqrt(mDataRange.upper/mDataRange.lower);
 | |
|         newRange.upper = center*qSqrt(mDataRange.upper/mDataRange.lower);
 | |
|       }
 | |
|     }
 | |
|     setDataRange(newRange);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPColorScale::update(UpdatePhase phase)
 | |
| {
 | |
|   QCPLayoutElement::update(phase);
 | |
|   if (!mAxisRect)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "internal axis rect was deleted";
 | |
|     return;
 | |
|   }
 | |
|   
 | |
|   mAxisRect.data()->update(phase);
 | |
|   
 | |
|   switch (phase)
 | |
|   {
 | |
|     case upMargins:
 | |
|     {
 | |
|       if (mType == QCPAxis::atBottom || mType == QCPAxis::atTop)
 | |
|       {
 | |
|         setMaximumSize(QWIDGETSIZE_MAX, mBarWidth+mAxisRect.data()->margins().top()+mAxisRect.data()->margins().bottom()+margins().top()+margins().bottom());
 | |
|         setMinimumSize(0,               mBarWidth+mAxisRect.data()->margins().top()+mAxisRect.data()->margins().bottom()+margins().top()+margins().bottom());
 | |
|       } else
 | |
|       {
 | |
|         setMaximumSize(mBarWidth+mAxisRect.data()->margins().left()+mAxisRect.data()->margins().right()+margins().left()+margins().right(), QWIDGETSIZE_MAX);
 | |
|         setMinimumSize(mBarWidth+mAxisRect.data()->margins().left()+mAxisRect.data()->margins().right()+margins().left()+margins().right(), 0);
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     case upLayout:
 | |
|     {
 | |
|       mAxisRect.data()->setOuterRect(rect());
 | |
|       break;
 | |
|     }
 | |
|     default: break;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPColorScale::applyDefaultAntialiasingHint(QCPPainter *painter) const
 | |
| {
 | |
|   painter->setAntialiasing(false);
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPColorScale::mousePressEvent(QMouseEvent *event, const QVariant &details)
 | |
| {
 | |
|   if (!mAxisRect)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "internal axis rect was deleted";
 | |
|     return;
 | |
|   }
 | |
|   mAxisRect.data()->mousePressEvent(event, details);
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPColorScale::mouseMoveEvent(QMouseEvent *event, const QPointF &startPos)
 | |
| {
 | |
|   if (!mAxisRect)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "internal axis rect was deleted";
 | |
|     return;
 | |
|   }
 | |
|   mAxisRect.data()->mouseMoveEvent(event, startPos);
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPColorScale::mouseReleaseEvent(QMouseEvent *event, const QPointF &startPos)
 | |
| {
 | |
|   if (!mAxisRect)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "internal axis rect was deleted";
 | |
|     return;
 | |
|   }
 | |
|   mAxisRect.data()->mouseReleaseEvent(event, startPos);
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPColorScale::wheelEvent(QWheelEvent *event)
 | |
| {
 | |
|   if (!mAxisRect)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "internal axis rect was deleted";
 | |
|     return;
 | |
|   }
 | |
|   mAxisRect.data()->wheelEvent(event);
 | |
| }
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPColorScaleAxisRectPrivate
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPColorScaleAxisRectPrivate
 | |
| 
 | |
|   \internal
 | |
|   \brief An axis rect subclass for use in a QCPColorScale
 | |
|   
 | |
|   This is a private class and not part of the public QCustomPlot interface.
 | |
|   
 | |
|   It provides the axis rect functionality for the QCPColorScale class.
 | |
| */
 | |
| 
 | |
| 
 | |
| /*!
 | |
|   Creates a new instance, as a child of \a parentColorScale.
 | |
| */
 | |
| QCPColorScaleAxisRectPrivate::QCPColorScaleAxisRectPrivate(QCPColorScale *parentColorScale) :
 | |
|   QCPAxisRect(parentColorScale->parentPlot(), true),
 | |
|   mParentColorScale(parentColorScale),
 | |
|   mGradientImageInvalidated(true)
 | |
| {
 | |
|   setParentLayerable(parentColorScale);
 | |
|   setMinimumMargins(QMargins(0, 0, 0, 0));
 | |
|   QList<QCPAxis::AxisType> allAxisTypes = QList<QCPAxis::AxisType>() << QCPAxis::atBottom << QCPAxis::atTop << QCPAxis::atLeft << QCPAxis::atRight;
 | |
|   foreach (QCPAxis::AxisType type, allAxisTypes)
 | |
|   {
 | |
|     axis(type)->setVisible(true);
 | |
|     axis(type)->grid()->setVisible(false);
 | |
|     axis(type)->setPadding(0);
 | |
|     connect(axis(type), SIGNAL(selectionChanged(QCPAxis::SelectableParts)), this, SLOT(axisSelectionChanged(QCPAxis::SelectableParts)));
 | |
|     connect(axis(type), SIGNAL(selectableChanged(QCPAxis::SelectableParts)), this, SLOT(axisSelectableChanged(QCPAxis::SelectableParts)));
 | |
|   }
 | |
| 
 | |
|   connect(axis(QCPAxis::atLeft), SIGNAL(rangeChanged(QCPRange)), axis(QCPAxis::atRight), SLOT(setRange(QCPRange)));
 | |
|   connect(axis(QCPAxis::atRight), SIGNAL(rangeChanged(QCPRange)), axis(QCPAxis::atLeft), SLOT(setRange(QCPRange)));
 | |
|   connect(axis(QCPAxis::atBottom), SIGNAL(rangeChanged(QCPRange)), axis(QCPAxis::atTop), SLOT(setRange(QCPRange)));
 | |
|   connect(axis(QCPAxis::atTop), SIGNAL(rangeChanged(QCPRange)), axis(QCPAxis::atBottom), SLOT(setRange(QCPRange)));
 | |
|   connect(axis(QCPAxis::atLeft), SIGNAL(scaleTypeChanged(QCPAxis::ScaleType)), axis(QCPAxis::atRight), SLOT(setScaleType(QCPAxis::ScaleType)));
 | |
|   connect(axis(QCPAxis::atRight), SIGNAL(scaleTypeChanged(QCPAxis::ScaleType)), axis(QCPAxis::atLeft), SLOT(setScaleType(QCPAxis::ScaleType)));
 | |
|   connect(axis(QCPAxis::atBottom), SIGNAL(scaleTypeChanged(QCPAxis::ScaleType)), axis(QCPAxis::atTop), SLOT(setScaleType(QCPAxis::ScaleType)));
 | |
|   connect(axis(QCPAxis::atTop), SIGNAL(scaleTypeChanged(QCPAxis::ScaleType)), axis(QCPAxis::atBottom), SLOT(setScaleType(QCPAxis::ScaleType)));
 | |
|   
 | |
|   // make layer transfers of color scale transfer to axis rect and axes
 | |
|   // the axes must be set after axis rect, such that they appear above color gradient drawn by axis rect:
 | |
|   connect(parentColorScale, SIGNAL(layerChanged(QCPLayer*)), this, SLOT(setLayer(QCPLayer*)));
 | |
|   foreach (QCPAxis::AxisType type, allAxisTypes)
 | |
|     connect(parentColorScale, SIGNAL(layerChanged(QCPLayer*)), axis(type), SLOT(setLayer(QCPLayer*)));
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Updates the color gradient image if necessary, by calling \ref updateGradientImage, then draws
 | |
|   it. Then the axes are drawn by calling the \ref QCPAxisRect::draw base class implementation.
 | |
|   
 | |
|   \seebaseclassmethod
 | |
| */
 | |
| void QCPColorScaleAxisRectPrivate::draw(QCPPainter *painter)
 | |
| {
 | |
|   if (mGradientImageInvalidated)
 | |
|     updateGradientImage();
 | |
|   
 | |
|   bool mirrorHorz = false;
 | |
|   bool mirrorVert = false;
 | |
|   if (mParentColorScale->mColorAxis)
 | |
|   {
 | |
|     mirrorHorz = mParentColorScale->mColorAxis.data()->rangeReversed() && (mParentColorScale->type() == QCPAxis::atBottom || mParentColorScale->type() == QCPAxis::atTop);
 | |
|     mirrorVert = mParentColorScale->mColorAxis.data()->rangeReversed() && (mParentColorScale->type() == QCPAxis::atLeft || mParentColorScale->type() == QCPAxis::atRight);
 | |
|   }
 | |
|   
 | |
|   painter->drawImage(rect().adjusted(0, -1, 0, -1), mGradientImage.mirrored(mirrorHorz, mirrorVert));
 | |
|   QCPAxisRect::draw(painter);
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Uses the current gradient of the parent \ref QCPColorScale (specified in the constructor) to
 | |
|   generate a gradient image. This gradient image will be used in the \ref draw method.
 | |
| */
 | |
| void QCPColorScaleAxisRectPrivate::updateGradientImage()
 | |
| {
 | |
|   if (rect().isEmpty())
 | |
|     return;
 | |
|   
 | |
|   const QImage::Format format = QImage::Format_ARGB32_Premultiplied;
 | |
|   int n = mParentColorScale->mGradient.levelCount();
 | |
|   int w, h;
 | |
|   QVector<double> data(n);
 | |
|   for (int i=0; i<n; ++i)
 | |
|     data[i] = i;
 | |
|   if (mParentColorScale->mType == QCPAxis::atBottom || mParentColorScale->mType == QCPAxis::atTop)
 | |
|   {
 | |
|     w = n;
 | |
|     h = rect().height();
 | |
|     mGradientImage = QImage(w, h, format);
 | |
|     QVector<QRgb*> pixels;
 | |
|     for (int y=0; y<h; ++y)
 | |
|       pixels.append(reinterpret_cast<QRgb*>(mGradientImage.scanLine(y)));
 | |
|     mParentColorScale->mGradient.colorize(data.constData(), QCPRange(0, n-1), pixels.first(), n);
 | |
|     for (int y=1; y<h; ++y)
 | |
|       memcpy(pixels.at(y), pixels.first(), n*sizeof(QRgb));
 | |
|   } else
 | |
|   {
 | |
|     w = rect().width();
 | |
|     h = n;
 | |
|     mGradientImage = QImage(w, h, format);
 | |
|     for (int y=0; y<h; ++y)
 | |
|     {
 | |
|       QRgb *pixels = reinterpret_cast<QRgb*>(mGradientImage.scanLine(y));
 | |
|       const QRgb lineColor = mParentColorScale->mGradient.color(data[h-1-y], QCPRange(0, n-1));
 | |
|       for (int x=0; x<w; ++x)
 | |
|         pixels[x] = lineColor;
 | |
|     }
 | |
|   }
 | |
|   mGradientImageInvalidated = false;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   This slot is connected to the selectionChanged signals of the four axes in the constructor. It
 | |
|   synchronizes the selection state of the axes.
 | |
| */
 | |
| void QCPColorScaleAxisRectPrivate::axisSelectionChanged(QCPAxis::SelectableParts selectedParts)
 | |
| {
 | |
|   // axis bases of four axes shall always (de-)selected synchronously:
 | |
|   QList<QCPAxis::AxisType> allAxisTypes = QList<QCPAxis::AxisType>() << QCPAxis::atBottom << QCPAxis::atTop << QCPAxis::atLeft << QCPAxis::atRight;
 | |
|   foreach (QCPAxis::AxisType type, allAxisTypes)
 | |
|   {
 | |
|     if (QCPAxis *senderAxis = qobject_cast<QCPAxis*>(sender()))
 | |
|       if (senderAxis->axisType() == type)
 | |
|         continue;
 | |
|     
 | |
|     if (axis(type)->selectableParts().testFlag(QCPAxis::spAxis))
 | |
|     {
 | |
|       if (selectedParts.testFlag(QCPAxis::spAxis))
 | |
|         axis(type)->setSelectedParts(axis(type)->selectedParts() | QCPAxis::spAxis);
 | |
|       else
 | |
|         axis(type)->setSelectedParts(axis(type)->selectedParts() & ~QCPAxis::spAxis);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   This slot is connected to the selectableChanged signals of the four axes in the constructor. It
 | |
|   synchronizes the selectability of the axes.
 | |
| */
 | |
| void QCPColorScaleAxisRectPrivate::axisSelectableChanged(QCPAxis::SelectableParts selectableParts)
 | |
| {
 | |
|   // synchronize axis base selectability:
 | |
|   QList<QCPAxis::AxisType> allAxisTypes = QList<QCPAxis::AxisType>() << QCPAxis::atBottom << QCPAxis::atTop << QCPAxis::atLeft << QCPAxis::atRight;
 | |
|   foreach (QCPAxis::AxisType type, allAxisTypes)
 | |
|   {
 | |
|     if (QCPAxis *senderAxis = qobject_cast<QCPAxis*>(sender()))
 | |
|       if (senderAxis->axisType() == type)
 | |
|         continue;
 | |
|     
 | |
|     if (axis(type)->selectableParts().testFlag(QCPAxis::spAxis))
 | |
|     {
 | |
|       if (selectableParts.testFlag(QCPAxis::spAxis))
 | |
|         axis(type)->setSelectableParts(axis(type)->selectableParts() | QCPAxis::spAxis);
 | |
|       else
 | |
|         axis(type)->setSelectableParts(axis(type)->selectableParts() & ~QCPAxis::spAxis);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| /* end of 'src/layoutelements/layoutelement-colorscale.cpp' */
 | |
| 
 | |
| 
 | |
| /* including file 'src/plottables/plottable-graph.cpp', size 72363           */
 | |
| /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPGraphData
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPGraphData
 | |
|   \brief Holds the data of one single data point for QCPGraph.
 | |
|   
 | |
|   The stored data is:
 | |
|   \li \a key: coordinate on the key axis of this data point (this is the \a mainKey and the \a sortKey)
 | |
|   \li \a value: coordinate on the value axis of this data point (this is the \a mainValue)
 | |
|   
 | |
|   The container for storing multiple data points is \ref QCPGraphDataContainer. It is a typedef for
 | |
|   \ref QCPDataContainer with \ref QCPGraphData as the DataType template parameter. See the
 | |
|   documentation there for an explanation regarding the data type's generic methods.
 | |
|   
 | |
|   \see QCPGraphDataContainer
 | |
| */
 | |
| 
 | |
| /* start documentation of inline functions */
 | |
| 
 | |
| /*! \fn double QCPGraphData::sortKey() const
 | |
|   
 | |
|   Returns the \a key member of this data point.
 | |
|   
 | |
|   For a general explanation of what this method is good for in the context of the data container,
 | |
|   see the documentation of \ref QCPDataContainer.
 | |
| */
 | |
| 
 | |
| /*! \fn static QCPGraphData QCPGraphData::fromSortKey(double sortKey)
 | |
|   
 | |
|   Returns a data point with the specified \a sortKey. All other members are set to zero.
 | |
|   
 | |
|   For a general explanation of what this method is good for in the context of the data container,
 | |
|   see the documentation of \ref QCPDataContainer.
 | |
| */
 | |
| 
 | |
| /*! \fn static static bool QCPGraphData::sortKeyIsMainKey()
 | |
|   
 | |
|   Since the member \a key is both the data point key coordinate and the data ordering parameter,
 | |
|   this method returns true.
 | |
|   
 | |
|   For a general explanation of what this method is good for in the context of the data container,
 | |
|   see the documentation of \ref QCPDataContainer.
 | |
| */
 | |
| 
 | |
| /*! \fn double QCPGraphData::mainKey() const
 | |
|   
 | |
|   Returns the \a key member of this data point.
 | |
|   
 | |
|   For a general explanation of what this method is good for in the context of the data container,
 | |
|   see the documentation of \ref QCPDataContainer.
 | |
| */
 | |
| 
 | |
| /*! \fn double QCPGraphData::mainValue() const
 | |
|   
 | |
|   Returns the \a value member of this data point.
 | |
|   
 | |
|   For a general explanation of what this method is good for in the context of the data container,
 | |
|   see the documentation of \ref QCPDataContainer.
 | |
| */
 | |
| 
 | |
| /*! \fn QCPRange QCPGraphData::valueRange() const
 | |
|   
 | |
|   Returns a QCPRange with both lower and upper boundary set to \a value of this data point.
 | |
|   
 | |
|   For a general explanation of what this method is good for in the context of the data container,
 | |
|   see the documentation of \ref QCPDataContainer.
 | |
| */
 | |
| 
 | |
| /* end documentation of inline functions */
 | |
| 
 | |
| /*!
 | |
|   Constructs a data point with key and value set to zero.
 | |
| */
 | |
| QCPGraphData::QCPGraphData() :
 | |
|   key(0),
 | |
|   value(0)
 | |
| {
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Constructs a data point with the specified \a key and \a value.
 | |
| */
 | |
| QCPGraphData::QCPGraphData(double key, double value) :
 | |
|   key(key),
 | |
|   value(value)
 | |
| {
 | |
| }
 | |
| 
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPGraph
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPGraph
 | |
|   \brief A plottable representing a graph in a plot.
 | |
| 
 | |
|   \image html QCPGraph.png
 | |
|   
 | |
|   Usually you create new graphs by calling QCustomPlot::addGraph. The resulting instance can be
 | |
|   accessed via QCustomPlot::graph.
 | |
| 
 | |
|   To plot data, assign it with the \ref setData or \ref addData functions. Alternatively, you can
 | |
|   also access and modify the data via the \ref data method, which returns a pointer to the internal
 | |
|   \ref QCPGraphDataContainer.
 | |
|   
 | |
|   Graphs are used to display single-valued data. Single-valued means that there should only be one
 | |
|   data point per unique key coordinate. In other words, the graph can't have \a loops. If you do
 | |
|   want to plot non-single-valued curves, rather use the QCPCurve plottable.
 | |
|   
 | |
|   Gaps in the graph line can be created by adding data points with NaN as value
 | |
|   (<tt>qQNaN()</tt> or <tt>std::numeric_limits<double>::quiet_NaN()</tt>) in between the two data points that shall be
 | |
|   separated.
 | |
|   
 | |
|   \section qcpgraph-appearance Changing the appearance
 | |
|   
 | |
|   The appearance of the graph is mainly determined by the line style, scatter style, brush and pen
 | |
|   of the graph (\ref setLineStyle, \ref setScatterStyle, \ref setBrush, \ref setPen).
 | |
|   
 | |
|   \subsection filling Filling under or between graphs
 | |
|   
 | |
|   QCPGraph knows two types of fills: Normal graph fills towards the zero-value-line parallel to
 | |
|   the key axis of the graph, and fills between two graphs, called channel fills. To enable a fill,
 | |
|   just set a brush with \ref setBrush which is neither Qt::NoBrush nor fully transparent.
 | |
|   
 | |
|   By default, a normal fill towards the zero-value-line will be drawn. To set up a channel fill
 | |
|   between this graph and another one, call \ref setChannelFillGraph with the other graph as
 | |
|   parameter.
 | |
| 
 | |
|   \see QCustomPlot::addGraph, QCustomPlot::graph
 | |
| */
 | |
| 
 | |
| /* start of documentation of inline functions */
 | |
| 
 | |
| /*! \fn QSharedPointer<QCPGraphDataContainer> QCPGraph::data() const
 | |
|   
 | |
|   Returns a shared pointer to the internal data storage of type \ref QCPGraphDataContainer. You may
 | |
|   use it to directly manipulate the data, which may be more convenient and faster than using the
 | |
|   regular \ref setData or \ref addData methods.
 | |
| */
 | |
| 
 | |
| /* end of documentation of inline functions */
 | |
| 
 | |
| /*!
 | |
|   Constructs a graph which uses \a keyAxis as its key axis ("x") and \a valueAxis as its value
 | |
|   axis ("y"). \a keyAxis and \a valueAxis must reside in the same QCustomPlot instance and not have
 | |
|   the same orientation. If either of these restrictions is violated, a corresponding message is
 | |
|   printed to the debug output (qDebug), the construction is not aborted, though.
 | |
|   
 | |
|   The created QCPGraph is automatically registered with the QCustomPlot instance inferred from \a
 | |
|   keyAxis. This QCustomPlot instance takes ownership of the QCPGraph, so do not delete it manually
 | |
|   but use QCustomPlot::removePlottable() instead.
 | |
|   
 | |
|   To directly create a graph inside a plot, you can also use the simpler QCustomPlot::addGraph function.
 | |
| */
 | |
| QCPGraph::QCPGraph(QCPAxis *keyAxis, QCPAxis *valueAxis) :
 | |
|   QCPAbstractPlottable1D<QCPGraphData>(keyAxis, valueAxis)
 | |
| {
 | |
|   // special handling for QCPGraphs to maintain the simple graph interface:
 | |
|   mParentPlot->registerGraph(this);
 | |
| 
 | |
|   setPen(QPen(Qt::blue, 0));
 | |
|   setBrush(Qt::NoBrush);
 | |
|   
 | |
|   setLineStyle(lsLine);
 | |
|   setScatterSkip(0);
 | |
|   setChannelFillGraph(0);
 | |
|   setAdaptiveSampling(true);
 | |
| }
 | |
| 
 | |
| QCPGraph::~QCPGraph()
 | |
| {
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Replaces the current data container with the provided \a data container.
 | |
|   
 | |
|   Since a QSharedPointer is used, multiple QCPGraphs may share the same data container safely.
 | |
|   Modifying the data in the container will then affect all graphs that share the container. Sharing
 | |
|   can be achieved by simply exchanging the data containers wrapped in shared pointers:
 | |
|   \snippet documentation/doc-code-snippets/mainwindow.cpp qcpgraph-datasharing-1
 | |
|   
 | |
|   If you do not wish to share containers, but create a copy from an existing container, rather use
 | |
|   the \ref QCPDataContainer<DataType>::set method on the graph's data container directly:
 | |
|   \snippet documentation/doc-code-snippets/mainwindow.cpp qcpgraph-datasharing-2
 | |
|   
 | |
|   \see addData
 | |
| */
 | |
| void QCPGraph::setData(QSharedPointer<QCPGraphDataContainer> data)
 | |
| {
 | |
|   mDataContainer = data;
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Replaces the current data with the provided points in \a keys and \a values. The provided
 | |
|   vectors should have equal length. Else, the number of added points will be the size of the
 | |
|   smallest vector.
 | |
|   
 | |
|   If you can guarantee that the passed data points are sorted by \a keys in ascending order, you
 | |
|   can set \a alreadySorted to true, to improve performance by saving a sorting run.
 | |
|   
 | |
|   \see addData
 | |
| */
 | |
| void QCPGraph::setData(const QVector<double> &keys, const QVector<double> &values, bool alreadySorted)
 | |
| {
 | |
|   mDataContainer->clear();
 | |
|   addData(keys, values, alreadySorted);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets how the single data points are connected in the plot. For scatter-only plots, set \a ls to
 | |
|   \ref lsNone and \ref setScatterStyle to the desired scatter style.
 | |
|   
 | |
|   \see setScatterStyle
 | |
| */
 | |
| void QCPGraph::setLineStyle(LineStyle ls)
 | |
| {
 | |
|   mLineStyle = ls;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the visual appearance of single data points in the plot. If set to \ref QCPScatterStyle::ssNone, no scatter points
 | |
|   are drawn (e.g. for line-only-plots with appropriate line style).
 | |
|   
 | |
|   \see QCPScatterStyle, setLineStyle
 | |
| */
 | |
| void QCPGraph::setScatterStyle(const QCPScatterStyle &style)
 | |
| {
 | |
|   mScatterStyle = style;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   If scatters are displayed (scatter style not \ref QCPScatterStyle::ssNone), \a skip number of
 | |
|   scatter points are skipped/not drawn after every drawn scatter point.
 | |
| 
 | |
|   This can be used to make the data appear sparser while for example still having a smooth line,
 | |
|   and to improve performance for very high density plots.
 | |
| 
 | |
|   If \a skip is set to 0 (default), all scatter points are drawn.
 | |
| 
 | |
|   \see setScatterStyle
 | |
| */
 | |
| void QCPGraph::setScatterSkip(int skip)
 | |
| {
 | |
|   mScatterSkip = qMax(0, skip);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the target graph for filling the area between this graph and \a targetGraph with the current
 | |
|   brush (\ref setBrush).
 | |
|   
 | |
|   When \a targetGraph is set to 0, a normal graph fill to the zero-value-line will be shown. To
 | |
|   disable any filling, set the brush to Qt::NoBrush.
 | |
| 
 | |
|   \see setBrush
 | |
| */
 | |
| void QCPGraph::setChannelFillGraph(QCPGraph *targetGraph)
 | |
| {
 | |
|   // prevent setting channel target to this graph itself:
 | |
|   if (targetGraph == this)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "targetGraph is this graph itself";
 | |
|     mChannelFillGraph = 0;
 | |
|     return;
 | |
|   }
 | |
|   // prevent setting channel target to a graph not in the plot:
 | |
|   if (targetGraph && targetGraph->mParentPlot != mParentPlot)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "targetGraph not in same plot";
 | |
|     mChannelFillGraph = 0;
 | |
|     return;
 | |
|   }
 | |
|   
 | |
|   mChannelFillGraph = targetGraph;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets whether adaptive sampling shall be used when plotting this graph. QCustomPlot's adaptive
 | |
|   sampling technique can drastically improve the replot performance for graphs with a larger number
 | |
|   of points (e.g. above 10,000), without notably changing the appearance of the graph.
 | |
|   
 | |
|   By default, adaptive sampling is enabled. Even if enabled, QCustomPlot decides whether adaptive
 | |
|   sampling shall actually be used on a per-graph basis. So leaving adaptive sampling enabled has no
 | |
|   disadvantage in almost all cases.
 | |
|   
 | |
|   \image html adaptive-sampling-line.png "A line plot of 500,000 points without and with adaptive sampling"
 | |
|   
 | |
|   As can be seen, line plots experience no visual degradation from adaptive sampling. Outliers are
 | |
|   reproduced reliably, as well as the overall shape of the data set. The replot time reduces
 | |
|   dramatically though. This allows QCustomPlot to display large amounts of data in realtime.
 | |
|   
 | |
|   \image html adaptive-sampling-scatter.png "A scatter plot of 100,000 points without and with adaptive sampling"
 | |
|   
 | |
|   Care must be taken when using high-density scatter plots in combination with adaptive sampling.
 | |
|   The adaptive sampling algorithm treats scatter plots more carefully than line plots which still
 | |
|   gives a significant reduction of replot times, but not quite as much as for line plots. This is
 | |
|   because scatter plots inherently need more data points to be preserved in order to still resemble
 | |
|   the original, non-adaptive-sampling plot. As shown above, the results still aren't quite
 | |
|   identical, as banding occurs for the outer data points. This is in fact intentional, such that
 | |
|   the boundaries of the data cloud stay visible to the viewer. How strong the banding appears,
 | |
|   depends on the point density, i.e. the number of points in the plot.
 | |
|   
 | |
|   For some situations with scatter plots it might thus be desirable to manually turn adaptive
 | |
|   sampling off. For example, when saving the plot to disk. This can be achieved by setting \a
 | |
|   enabled to false before issuing a command like \ref QCustomPlot::savePng, and setting \a enabled
 | |
|   back to true afterwards.
 | |
| */
 | |
| void QCPGraph::setAdaptiveSampling(bool enabled)
 | |
| {
 | |
|   mAdaptiveSampling = enabled;
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Adds the provided points in \a keys and \a values to the current data. The provided vectors
 | |
|   should have equal length. Else, the number of added points will be the size of the smallest
 | |
|   vector.
 | |
|   
 | |
|   If you can guarantee that the passed data points are sorted by \a keys in ascending order, you
 | |
|   can set \a alreadySorted to true, to improve performance by saving a sorting run.
 | |
|   
 | |
|   Alternatively, you can also access and modify the data directly via the \ref data method, which
 | |
|   returns a pointer to the internal data container.
 | |
| */
 | |
| void QCPGraph::addData(const QVector<double> &keys, const QVector<double> &values, bool alreadySorted)
 | |
| {
 | |
|   if (keys.size() != values.size())
 | |
|     qDebug() << Q_FUNC_INFO << "keys and values have different sizes:" << keys.size() << values.size();
 | |
|   const int n = qMin(keys.size(), values.size());
 | |
|   QVector<QCPGraphData> tempData(n);
 | |
|   QVector<QCPGraphData>::iterator it = tempData.begin();
 | |
|   const QVector<QCPGraphData>::iterator itEnd = tempData.end();
 | |
|   int i = 0;
 | |
|   while (it != itEnd)
 | |
|   {
 | |
|     it->key = keys[i];
 | |
|     it->value = values[i];
 | |
|     ++it;
 | |
|     ++i;
 | |
|   }
 | |
|   mDataContainer->add(tempData, alreadySorted); // don't modify tempData beyond this to prevent copy on write
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Adds the provided data point as \a key and \a value to the current data.
 | |
|   
 | |
|   Alternatively, you can also access and modify the data directly via the \ref data method, which
 | |
|   returns a pointer to the internal data container.
 | |
| */
 | |
| void QCPGraph::addData(double key, double value)
 | |
| {
 | |
|   mDataContainer->add(QCPGraphData(key, value));
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| double QCPGraph::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
 | |
| {
 | |
|   if ((onlySelectable && mSelectable == QCP::stNone) || mDataContainer->isEmpty())
 | |
|     return -1;
 | |
|   if (!mKeyAxis || !mValueAxis)
 | |
|     return -1;
 | |
|   
 | |
|   if (mKeyAxis.data()->axisRect()->rect().contains(pos.toPoint()))
 | |
|   {
 | |
|     QCPGraphDataContainer::const_iterator closestDataPoint = mDataContainer->constEnd();
 | |
|     double result = pointDistance(pos, closestDataPoint);
 | |
|     if (details)
 | |
|     {
 | |
|       int pointIndex = closestDataPoint-mDataContainer->constBegin();
 | |
|       details->setValue(QCPDataSelection(QCPDataRange(pointIndex, pointIndex+1)));
 | |
|     }
 | |
|     return result;
 | |
|   } else
 | |
|     return -1;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| QCPRange QCPGraph::getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain) const
 | |
| {
 | |
|   return mDataContainer->keyRange(foundRange, inSignDomain);
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| QCPRange QCPGraph::getValueRange(bool &foundRange, QCP::SignDomain inSignDomain, const QCPRange &inKeyRange) const
 | |
| {
 | |
|   return mDataContainer->valueRange(foundRange, inSignDomain, inKeyRange);
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPGraph::draw(QCPPainter *painter)
 | |
| {
 | |
|   if (!mKeyAxis || !mValueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
 | |
|   if (mKeyAxis.data()->range().size() <= 0 || mDataContainer->isEmpty()) return;
 | |
|   if (mLineStyle == lsNone && mScatterStyle.isNone()) return;
 | |
|   
 | |
|   QVector<QPointF> lines, scatters; // line and (if necessary) scatter pixel coordinates will be stored here while iterating over segments
 | |
|   
 | |
|   // loop over and draw segments of unselected/selected data:
 | |
|   QList<QCPDataRange> selectedSegments, unselectedSegments, allSegments;
 | |
|   getDataSegments(selectedSegments, unselectedSegments);
 | |
|   allSegments << unselectedSegments << selectedSegments;
 | |
|   for (int i=0; i<allSegments.size(); ++i)
 | |
|   {
 | |
|     bool isSelectedSegment = i >= unselectedSegments.size();
 | |
|     // get line pixel points appropriate to line style:
 | |
|     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)
 | |
|     getLines(&lines, lineDataRange);
 | |
|     
 | |
|     // check data validity if flag set:
 | |
| #ifdef QCUSTOMPLOT_CHECK_DATA
 | |
|     QCPGraphDataContainer::const_iterator it;
 | |
|     for (it = mDataContainer->constBegin(); it != mDataContainer->constEnd(); ++it)
 | |
|     {
 | |
|       if (QCP::isInvalidData(it->key, it->value))
 | |
|         qDebug() << Q_FUNC_INFO << "Data point at" << it->key << "invalid." << "Plottable name:" << name();
 | |
|     }
 | |
| #endif
 | |
|     
 | |
|     // draw fill of graph:
 | |
|     if (isSelectedSegment && mSelectionDecorator)
 | |
|       mSelectionDecorator->applyBrush(painter);
 | |
|     else
 | |
|       painter->setBrush(mBrush);
 | |
|     painter->setPen(Qt::NoPen);
 | |
|     drawFill(painter, &lines);
 | |
|     
 | |
|     // draw line:
 | |
|     if (mLineStyle != lsNone)
 | |
|     {
 | |
|       if (isSelectedSegment && mSelectionDecorator)
 | |
|         mSelectionDecorator->applyPen(painter);
 | |
|       else
 | |
|         painter->setPen(mPen);
 | |
|       painter->setBrush(Qt::NoBrush);
 | |
|       if (mLineStyle == lsImpulse)
 | |
|         drawImpulsePlot(painter, lines);
 | |
|       else
 | |
|         drawLinePlot(painter, lines); // also step plots can be drawn as a line plot
 | |
|     }
 | |
|     
 | |
|     // draw scatters:
 | |
|     QCPScatterStyle finalScatterStyle = mScatterStyle;
 | |
|     if (isSelectedSegment && mSelectionDecorator)
 | |
|       finalScatterStyle = mSelectionDecorator->getFinalScatterStyle(mScatterStyle);
 | |
|     if (!finalScatterStyle.isNone())
 | |
|     {
 | |
|       getScatters(&scatters, allSegments.at(i));
 | |
|       drawScatterPlot(painter, scatters, finalScatterStyle);
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   // draw other selection decoration that isn't just line/scatter pens and brushes:
 | |
|   if (mSelectionDecorator)
 | |
|     mSelectionDecorator->drawDecoration(painter, selection());
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPGraph::drawLegendIcon(QCPPainter *painter, const QRectF &rect) const
 | |
| {
 | |
|   // draw fill:
 | |
|   if (mBrush.style() != Qt::NoBrush)
 | |
|   {
 | |
|     applyFillAntialiasingHint(painter);
 | |
|     painter->fillRect(QRectF(rect.left(), rect.top()+rect.height()/2.0, rect.width(), rect.height()/3.0), mBrush);
 | |
|   }
 | |
|   // draw line vertically centered:
 | |
|   if (mLineStyle != lsNone)
 | |
|   {
 | |
|     applyDefaultAntialiasingHint(painter);
 | |
|     painter->setPen(mPen);
 | |
|     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
 | |
|   }
 | |
|   // draw scatter symbol:
 | |
|   if (!mScatterStyle.isNone())
 | |
|   {
 | |
|     applyScattersAntialiasingHint(painter);
 | |
|     // scale scatter pixmap if it's too large to fit in legend icon rect:
 | |
|     if (mScatterStyle.shape() == QCPScatterStyle::ssPixmap && (mScatterStyle.pixmap().size().width() > rect.width() || mScatterStyle.pixmap().size().height() > rect.height()))
 | |
|     {
 | |
|       QCPScatterStyle scaledStyle(mScatterStyle);
 | |
|       scaledStyle.setPixmap(scaledStyle.pixmap().scaled(rect.size().toSize(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
 | |
|       scaledStyle.applyTo(painter, mPen);
 | |
|       scaledStyle.drawShape(painter, QRectF(rect).center());
 | |
|     } else
 | |
|     {
 | |
|       mScatterStyle.applyTo(painter, mPen);
 | |
|       mScatterStyle.drawShape(painter, QRectF(rect).center());
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   This method retrieves an optimized set of data points via \ref getOptimizedLineData, an branches
 | |
|   out to the line style specific functions such as \ref dataToLines, \ref dataToStepLeftLines, etc.
 | |
|   according to the line style of the graph.
 | |
| 
 | |
|   \a lines will be filled with points in pixel coordinates, that can be drawn with the according
 | |
|   draw functions like \ref drawLinePlot and \ref drawImpulsePlot. The points returned in \a lines
 | |
|   aren't necessarily the original data points. For example, step line styles require additional
 | |
|   points to form the steps when drawn. If the line style of the graph is \ref lsNone, the \a
 | |
|   lines vector will be empty.
 | |
| 
 | |
|   \a dataRange specifies the beginning and ending data indices that will be taken into account for
 | |
|   conversion. In this function, the specified range may exceed the total data bounds without harm:
 | |
|   a correspondingly trimmed data range will be used. This takes the burden off the user of this
 | |
|   function to check for valid indices in \a dataRange, e.g. when extending ranges coming from \ref
 | |
|   getDataSegments.
 | |
| 
 | |
|   \see getScatters
 | |
| */
 | |
| void QCPGraph::getLines(QVector<QPointF> *lines, const QCPDataRange &dataRange) const
 | |
| {
 | |
|   if (!lines) return;
 | |
|   QCPGraphDataContainer::const_iterator begin, end;
 | |
|   getVisibleDataBounds(begin, end, dataRange);
 | |
|   if (begin == end)
 | |
|   {
 | |
|     lines->clear();
 | |
|     return;
 | |
|   }
 | |
|   
 | |
|   QVector<QCPGraphData> lineData;
 | |
|   if (mLineStyle != lsNone)
 | |
|     getOptimizedLineData(&lineData, begin, end);
 | |
| 
 | |
|   switch (mLineStyle)
 | |
|   {
 | |
|     case lsNone: lines->clear(); break;
 | |
|     case lsLine: *lines = dataToLines(lineData); break;
 | |
|     case lsStepLeft: *lines = dataToStepLeftLines(lineData); break;
 | |
|     case lsStepRight: *lines = dataToStepRightLines(lineData); break;
 | |
|     case lsStepCenter: *lines = dataToStepCenterLines(lineData); break;
 | |
|     case lsImpulse: *lines = dataToImpulseLines(lineData); break;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   This method retrieves an optimized set of data points via \ref getOptimizedScatterData and then
 | |
|   converts them to pixel coordinates. The resulting points are returned in \a scatters, and can be
 | |
|   passed to \ref drawScatterPlot.
 | |
| 
 | |
|   \a dataRange specifies the beginning and ending data indices that will be taken into account for
 | |
|   conversion. In this function, the specified range may exceed the total data bounds without harm:
 | |
|   a correspondingly trimmed data range will be used. This takes the burden off the user of this
 | |
|   function to check for valid indices in \a dataRange, e.g. when extending ranges coming from \ref
 | |
|   getDataSegments.
 | |
| */
 | |
| void QCPGraph::getScatters(QVector<QPointF> *scatters, const QCPDataRange &dataRange) const
 | |
| {
 | |
|   if (!scatters) return;
 | |
|   QCPAxis *keyAxis = mKeyAxis.data();
 | |
|   QCPAxis *valueAxis = mValueAxis.data();
 | |
|   if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; scatters->clear(); return; }
 | |
|   
 | |
|   QCPGraphDataContainer::const_iterator begin, end;
 | |
|   getVisibleDataBounds(begin, end, dataRange);
 | |
|   if (begin == end)
 | |
|   {
 | |
|     scatters->clear();
 | |
|     return;
 | |
|   }
 | |
|   
 | |
|   QVector<QCPGraphData> data;
 | |
|   getOptimizedScatterData(&data, begin, end);
 | |
|   scatters->resize(data.size());
 | |
|   if (keyAxis->orientation() == Qt::Vertical)
 | |
|   {
 | |
|     for (int i=0; i<data.size(); ++i)
 | |
|     {
 | |
|       if (!qIsNaN(data.at(i).value))
 | |
|       {
 | |
|         (*scatters)[i].setX(valueAxis->coordToPixel(data.at(i).value));
 | |
|         (*scatters)[i].setY(keyAxis->coordToPixel(data.at(i).key));
 | |
|       }
 | |
|     }
 | |
|   } else
 | |
|   {
 | |
|     for (int i=0; i<data.size(); ++i)
 | |
|     {
 | |
|       if (!qIsNaN(data.at(i).value))
 | |
|       {
 | |
|         (*scatters)[i].setX(keyAxis->coordToPixel(data.at(i).key));
 | |
|         (*scatters)[i].setY(valueAxis->coordToPixel(data.at(i).value));
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Takes raw data points in plot coordinates as \a data, and returns a vector containing pixel
 | |
|   coordinate points which are suitable for drawing the line style \ref lsLine.
 | |
|   
 | |
|   The source of \a data is usually \ref getOptimizedLineData, and this method is called in \a
 | |
|   getLines if the line style is set accordingly.
 | |
| 
 | |
|   \see dataToStepLeftLines, dataToStepRightLines, dataToStepCenterLines, dataToImpulseLines, getLines, drawLinePlot
 | |
| */
 | |
| QVector<QPointF> QCPGraph::dataToLines(const QVector<QCPGraphData> &data) const
 | |
| {
 | |
|   QVector<QPointF> result;
 | |
|   QCPAxis *keyAxis = mKeyAxis.data();
 | |
|   QCPAxis *valueAxis = mValueAxis.data();
 | |
|   if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return result; }
 | |
| 
 | |
|   result.reserve(data.size()+2); // added 2 to reserve memory for lower/upper fill base points that might be needed for fill
 | |
|   result.resize(data.size());
 | |
|   
 | |
|   // transform data points to pixels:
 | |
|   if (keyAxis->orientation() == Qt::Vertical)
 | |
|   {
 | |
|     for (int i=0; i<data.size(); ++i)
 | |
|     {
 | |
|       result[i].setX(valueAxis->coordToPixel(data.at(i).value));
 | |
|       result[i].setY(keyAxis->coordToPixel(data.at(i).key));
 | |
|     }
 | |
|   } else // key axis is horizontal
 | |
|   {
 | |
|     for (int i=0; i<data.size(); ++i)
 | |
|     {
 | |
|       result[i].setX(keyAxis->coordToPixel(data.at(i).key));
 | |
|       result[i].setY(valueAxis->coordToPixel(data.at(i).value));
 | |
|     }
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Takes raw data points in plot coordinates as \a data, and returns a vector containing pixel
 | |
|   coordinate points which are suitable for drawing the line style \ref lsStepLeft.
 | |
|   
 | |
|   The source of \a data is usually \ref getOptimizedLineData, and this method is called in \a
 | |
|   getLines if the line style is set accordingly.
 | |
| 
 | |
|   \see dataToLines, dataToStepRightLines, dataToStepCenterLines, dataToImpulseLines, getLines, drawLinePlot
 | |
| */
 | |
| QVector<QPointF> QCPGraph::dataToStepLeftLines(const QVector<QCPGraphData> &data) const
 | |
| {
 | |
|   QVector<QPointF> result;
 | |
|   QCPAxis *keyAxis = mKeyAxis.data();
 | |
|   QCPAxis *valueAxis = mValueAxis.data();
 | |
|   if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return result; }
 | |
|   
 | |
|   result.reserve(data.size()*2+2); // added 2 to reserve memory for lower/upper fill base points that might be needed for fill
 | |
|   result.resize(data.size()*2);
 | |
|   
 | |
|   // calculate steps from data and transform to pixel coordinates:
 | |
|   if (keyAxis->orientation() == Qt::Vertical)
 | |
|   {
 | |
|     double lastValue = valueAxis->coordToPixel(data.first().value);
 | |
|     for (int i=0; i<data.size(); ++i)
 | |
|     {
 | |
|       const double key = keyAxis->coordToPixel(data.at(i).key);
 | |
|       result[i*2+0].setX(lastValue);
 | |
|       result[i*2+0].setY(key);
 | |
|       lastValue = valueAxis->coordToPixel(data.at(i).value);
 | |
|       result[i*2+1].setX(lastValue);
 | |
|       result[i*2+1].setY(key);
 | |
|     }
 | |
|   } else // key axis is horizontal
 | |
|   {
 | |
|     double lastValue = valueAxis->coordToPixel(data.first().value);
 | |
|     for (int i=0; i<data.size(); ++i)
 | |
|     {
 | |
|       const double key = keyAxis->coordToPixel(data.at(i).key);
 | |
|       result[i*2+0].setX(key);
 | |
|       result[i*2+0].setY(lastValue);
 | |
|       lastValue = valueAxis->coordToPixel(data.at(i).value);
 | |
|       result[i*2+1].setX(key);
 | |
|       result[i*2+1].setY(lastValue);
 | |
|     }
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Takes raw data points in plot coordinates as \a data, and returns a vector containing pixel
 | |
|   coordinate points which are suitable for drawing the line style \ref lsStepRight.
 | |
|   
 | |
|   The source of \a data is usually \ref getOptimizedLineData, and this method is called in \a
 | |
|   getLines if the line style is set accordingly.
 | |
| 
 | |
|   \see dataToLines, dataToStepLeftLines, dataToStepCenterLines, dataToImpulseLines, getLines, drawLinePlot
 | |
| */
 | |
| QVector<QPointF> QCPGraph::dataToStepRightLines(const QVector<QCPGraphData> &data) const
 | |
| {
 | |
|   QVector<QPointF> result;
 | |
|   QCPAxis *keyAxis = mKeyAxis.data();
 | |
|   QCPAxis *valueAxis = mValueAxis.data();
 | |
|   if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return result; }
 | |
|   
 | |
|   result.reserve(data.size()*2+2); // added 2 to reserve memory for lower/upper fill base points that might be needed for fill
 | |
|   result.resize(data.size()*2);
 | |
|   
 | |
|   // calculate steps from data and transform to pixel coordinates:
 | |
|   if (keyAxis->orientation() == Qt::Vertical)
 | |
|   {
 | |
|     double lastKey = keyAxis->coordToPixel(data.first().key);
 | |
|     for (int i=0; i<data.size(); ++i)
 | |
|     {
 | |
|       const double value = valueAxis->coordToPixel(data.at(i).value);
 | |
|       result[i*2+0].setX(value);
 | |
|       result[i*2+0].setY(lastKey);
 | |
|       lastKey = keyAxis->coordToPixel(data.at(i).key);
 | |
|       result[i*2+1].setX(value);
 | |
|       result[i*2+1].setY(lastKey);
 | |
|     }
 | |
|   } else // key axis is horizontal
 | |
|   {
 | |
|     double lastKey = keyAxis->coordToPixel(data.first().key);
 | |
|     for (int i=0; i<data.size(); ++i)
 | |
|     {
 | |
|       const double value = valueAxis->coordToPixel(data.at(i).value);
 | |
|       result[i*2+0].setX(lastKey);
 | |
|       result[i*2+0].setY(value);
 | |
|       lastKey = keyAxis->coordToPixel(data.at(i).key);
 | |
|       result[i*2+1].setX(lastKey);
 | |
|       result[i*2+1].setY(value);
 | |
|     }
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Takes raw data points in plot coordinates as \a data, and returns a vector containing pixel
 | |
|   coordinate points which are suitable for drawing the line style \ref lsStepCenter.
 | |
|   
 | |
|   The source of \a data is usually \ref getOptimizedLineData, and this method is called in \a
 | |
|   getLines if the line style is set accordingly.
 | |
| 
 | |
|   \see dataToLines, dataToStepLeftLines, dataToStepRightLines, dataToImpulseLines, getLines, drawLinePlot
 | |
| */
 | |
| QVector<QPointF> QCPGraph::dataToStepCenterLines(const QVector<QCPGraphData> &data) const
 | |
| {
 | |
|   QVector<QPointF> result;
 | |
|   QCPAxis *keyAxis = mKeyAxis.data();
 | |
|   QCPAxis *valueAxis = mValueAxis.data();
 | |
|   if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return result; }
 | |
|   
 | |
|   result.reserve(data.size()*2+2); // added 2 to reserve memory for lower/upper fill base points that might be needed for fill
 | |
|   result.resize(data.size()*2);
 | |
|   
 | |
|   // calculate steps from data and transform to pixel coordinates:
 | |
|   if (keyAxis->orientation() == Qt::Vertical)
 | |
|   {
 | |
|     double lastKey = keyAxis->coordToPixel(data.first().key);
 | |
|     double lastValue = valueAxis->coordToPixel(data.first().value);
 | |
|     result[0].setX(lastValue);
 | |
|     result[0].setY(lastKey);
 | |
|     for (int i=1; i<data.size(); ++i)
 | |
|     {
 | |
|       const double key = (keyAxis->coordToPixel(data.at(i).key)+lastKey)*0.5;
 | |
|       result[i*2-1].setX(lastValue);
 | |
|       result[i*2-1].setY(key);
 | |
|       lastValue = valueAxis->coordToPixel(data.at(i).value);
 | |
|       lastKey = keyAxis->coordToPixel(data.at(i).key);
 | |
|       result[i*2+0].setX(lastValue);
 | |
|       result[i*2+0].setY(key);
 | |
|     }
 | |
|     result[data.size()*2-1].setX(lastValue);
 | |
|     result[data.size()*2-1].setY(lastKey);
 | |
|   } else // key axis is horizontal
 | |
|   {
 | |
|     double lastKey = keyAxis->coordToPixel(data.first().key);
 | |
|     double lastValue = valueAxis->coordToPixel(data.first().value);
 | |
|     result[0].setX(lastKey);
 | |
|     result[0].setY(lastValue);
 | |
|     for (int i=1; i<data.size(); ++i)
 | |
|     {
 | |
|       const double key = (keyAxis->coordToPixel(data.at(i).key)+lastKey)*0.5;
 | |
|       result[i*2-1].setX(key);
 | |
|       result[i*2-1].setY(lastValue);
 | |
|       lastValue = valueAxis->coordToPixel(data.at(i).value);
 | |
|       lastKey = keyAxis->coordToPixel(data.at(i).key);
 | |
|       result[i*2+0].setX(key);
 | |
|       result[i*2+0].setY(lastValue);
 | |
|     }
 | |
|     result[data.size()*2-1].setX(lastKey);
 | |
|     result[data.size()*2-1].setY(lastValue);
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Takes raw data points in plot coordinates as \a data, and returns a vector containing pixel
 | |
|   coordinate points which are suitable for drawing the line style \ref lsImpulse.
 | |
|   
 | |
|   The source of \a data is usually \ref getOptimizedLineData, and this method is called in \a
 | |
|   getLines if the line style is set accordingly.
 | |
| 
 | |
|   \see dataToLines, dataToStepLeftLines, dataToStepRightLines, dataToStepCenterLines, getLines, drawImpulsePlot
 | |
| */
 | |
| QVector<QPointF> QCPGraph::dataToImpulseLines(const QVector<QCPGraphData> &data) const
 | |
| {
 | |
|   QVector<QPointF> result;
 | |
|   QCPAxis *keyAxis = mKeyAxis.data();
 | |
|   QCPAxis *valueAxis = mValueAxis.data();
 | |
|   if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return result; }
 | |
|   
 | |
|   result.resize(data.size()*2); // no need to reserve 2 extra points because impulse plot has no fill
 | |
|   
 | |
|   // transform data points to pixels:
 | |
|   if (keyAxis->orientation() == Qt::Vertical)
 | |
|   {
 | |
|     for (int i=0; i<data.size(); ++i)
 | |
|     {
 | |
|       const double key = keyAxis->coordToPixel(data.at(i).key);
 | |
|       result[i*2+0].setX(valueAxis->coordToPixel(0));
 | |
|       result[i*2+0].setY(key);
 | |
|       result[i*2+1].setX(valueAxis->coordToPixel(data.at(i).value));
 | |
|       result[i*2+1].setY(key);
 | |
|     }
 | |
|   } else // key axis is horizontal
 | |
|   {
 | |
|     for (int i=0; i<data.size(); ++i)
 | |
|     {
 | |
|       const double key = keyAxis->coordToPixel(data.at(i).key);
 | |
|       result[i*2+0].setX(key);
 | |
|       result[i*2+0].setY(valueAxis->coordToPixel(0));
 | |
|       result[i*2+1].setX(key);
 | |
|       result[i*2+1].setY(valueAxis->coordToPixel(data.at(i).value));
 | |
|     }
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Draws the fill of the graph using the specified \a painter, with the currently set brush.
 | |
|   
 | |
|   \a lines contains the points of the graph line, in pixel coordinates.
 | |
| 
 | |
|   If the fill is a normal fill towards the zero-value-line, only the points in \a lines are
 | |
|   required and two extra points at the zero-value-line, which are added by \ref addFillBasePoints
 | |
|   and removed by \ref removeFillBasePoints after the fill drawing is done.
 | |
| 
 | |
|   On the other hand if the fill is a channel fill between this QCPGraph and another QCPGraph (\a
 | |
|   mChannelFillGraph), the more complex polygon is calculated with the \ref getChannelFillPolygon
 | |
|   function, and then drawn.
 | |
| 
 | |
|   \see drawLinePlot, drawImpulsePlot, drawScatterPlot
 | |
| */
 | |
| void QCPGraph::drawFill(QCPPainter *painter, QVector<QPointF> *lines) const
 | |
| {
 | |
|   if (mLineStyle == lsImpulse) return; // fill doesn't make sense for impulse plot
 | |
|   if (painter->brush().style() == Qt::NoBrush || painter->brush().color().alpha() == 0) return;
 | |
|   
 | |
|   applyFillAntialiasingHint(painter);
 | |
|   if (!mChannelFillGraph)
 | |
|   {
 | |
|     // draw base fill under graph, fill goes all the way to the zero-value-line:
 | |
|     addFillBasePoints(lines);
 | |
|     painter->drawPolygon(QPolygonF(*lines));
 | |
|     removeFillBasePoints(lines);
 | |
|   } else
 | |
|   {
 | |
|     // draw channel fill between this graph and mChannelFillGraph:
 | |
|     painter->drawPolygon(getChannelFillPolygon(lines));
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Draws scatter symbols at every point passed in \a scatters, given in pixel coordinates. The
 | |
|   scatters will be drawn with \a painter and have the appearance as specified in \a style.
 | |
| 
 | |
|   \see drawLinePlot, drawImpulsePlot
 | |
| */
 | |
| void QCPGraph::drawScatterPlot(QCPPainter *painter, const QVector<QPointF> &scatters, const QCPScatterStyle &style) const
 | |
| {
 | |
|   applyScattersAntialiasingHint(painter);
 | |
|   style.applyTo(painter, mPen);
 | |
|   for (int i=0; i<scatters.size(); ++i)
 | |
|     style.drawShape(painter, scatters.at(i).x(), scatters.at(i).y());
 | |
| }
 | |
| 
 | |
| /*!  \internal
 | |
|   
 | |
|   Draws lines between the points in \a lines, given in pixel coordinates.
 | |
|   
 | |
|   \see drawScatterPlot, drawImpulsePlot, QCPAbstractPlottable1D::drawPolyline
 | |
| */
 | |
| void QCPGraph::drawLinePlot(QCPPainter *painter, const QVector<QPointF> &lines) const
 | |
| {
 | |
|   if (painter->pen().style() != Qt::NoPen && painter->pen().color().alpha() != 0)
 | |
|   {
 | |
|     applyDefaultAntialiasingHint(painter);
 | |
|     drawPolyline(painter, lines);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Draws impulses from the provided data, i.e. it connects all line pairs in \a lines, given in
 | |
|   pixel coordinates. The \a lines necessary for impulses are generated by \ref dataToImpulseLines
 | |
|   from the regular graph data points.
 | |
| 
 | |
|   \see drawLinePlot, drawScatterPlot
 | |
| */
 | |
| void QCPGraph::drawImpulsePlot(QCPPainter *painter, const QVector<QPointF> &lines) const
 | |
| {
 | |
|   if (painter->pen().style() != Qt::NoPen && painter->pen().color().alpha() != 0)
 | |
|   {
 | |
|     applyDefaultAntialiasingHint(painter);
 | |
|     QPen oldPen = painter->pen();
 | |
|     QPen newPen = painter->pen();
 | |
|     newPen.setCapStyle(Qt::FlatCap); // so impulse line doesn't reach beyond zero-line
 | |
|     painter->setPen(newPen);
 | |
|     painter->drawLines(lines);
 | |
|     painter->setPen(oldPen);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Returns via \a lineData the data points that need to be visualized for this graph when plotting
 | |
|   graph lines, taking into consideration the currently visible axis ranges and, if \ref
 | |
|   setAdaptiveSampling is enabled, local point densities. The considered data can be restricted
 | |
|   further by \a begin and \a end, e.g. to only plot a certain segment of the data (see \ref
 | |
|   getDataSegments).
 | |
| 
 | |
|   This method is used by \ref getLines to retrieve the basic working set of data.
 | |
| 
 | |
|   \see getOptimizedScatterData
 | |
| */
 | |
| void QCPGraph::getOptimizedLineData(QVector<QCPGraphData> *lineData, const QCPGraphDataContainer::const_iterator &begin, const QCPGraphDataContainer::const_iterator &end) const
 | |
| {
 | |
|   if (!lineData) return;
 | |
|   QCPAxis *keyAxis = mKeyAxis.data();
 | |
|   QCPAxis *valueAxis = mValueAxis.data();
 | |
|   if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
 | |
|   if (begin == end) return;
 | |
|   
 | |
|   int dataCount = end-begin;
 | |
|   int maxCount = std::numeric_limits<int>::max();
 | |
|   if (mAdaptiveSampling)
 | |
|   {
 | |
|     double keyPixelSpan = qAbs(keyAxis->coordToPixel(begin->key)-keyAxis->coordToPixel((end-1)->key));
 | |
|     if (2*keyPixelSpan+2 < (double)std::numeric_limits<int>::max())
 | |
|       maxCount = 2*keyPixelSpan+2;
 | |
|   }
 | |
|   
 | |
|   if (mAdaptiveSampling && dataCount >= maxCount) // use adaptive sampling only if there are at least two points per pixel on average
 | |
|   {
 | |
|     QCPGraphDataContainer::const_iterator it = begin;
 | |
|     double minValue = it->value;
 | |
|     double maxValue = it->value;
 | |
|     QCPGraphDataContainer::const_iterator currentIntervalFirstPoint = it;
 | |
|     int reversedFactor = keyAxis->pixelOrientation(); // is used to calculate keyEpsilon pixel into the correct direction
 | |
|     int reversedRound = reversedFactor==-1 ? 1 : 0; // is used to switch between floor (normal) and ceil (reversed) rounding of currentIntervalStartKey
 | |
|     double currentIntervalStartKey = keyAxis->pixelToCoord((int)(keyAxis->coordToPixel(begin->key)+reversedRound));
 | |
|     double lastIntervalEndKey = currentIntervalStartKey;
 | |
|     double keyEpsilon = qAbs(currentIntervalStartKey-keyAxis->pixelToCoord(keyAxis->coordToPixel(currentIntervalStartKey)+1.0*reversedFactor)); // interval of one pixel on screen when mapped to plot key coordinates
 | |
|     bool keyEpsilonVariable = keyAxis->scaleType() == QCPAxis::stLogarithmic; // indicates whether keyEpsilon needs to be updated after every interval (for log axes)
 | |
|     int intervalDataCount = 1;
 | |
|     ++it; // advance iterator to second data point because adaptive sampling works in 1 point retrospect
 | |
|     while (it != end)
 | |
|     {
 | |
|       if (it->key < currentIntervalStartKey+keyEpsilon) // data point is still within same pixel, so skip it and expand value span of this cluster if necessary
 | |
|       {
 | |
|         if (it->value < minValue)
 | |
|           minValue = it->value;
 | |
|         else if (it->value > maxValue)
 | |
|           maxValue = it->value;
 | |
|         ++intervalDataCount;
 | |
|       } else // new pixel interval started
 | |
|       {
 | |
|         if (intervalDataCount >= 2) // last pixel had multiple data points, consolidate them to a cluster
 | |
|         {
 | |
|           if (lastIntervalEndKey < currentIntervalStartKey-keyEpsilon) // last point is further away, so first point of this cluster must be at a real data point
 | |
|             lineData->append(QCPGraphData(currentIntervalStartKey+keyEpsilon*0.2, currentIntervalFirstPoint->value));
 | |
|           lineData->append(QCPGraphData(currentIntervalStartKey+keyEpsilon*0.25, minValue));
 | |
|           lineData->append(QCPGraphData(currentIntervalStartKey+keyEpsilon*0.75, maxValue));
 | |
|           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
 | |
|             lineData->append(QCPGraphData(currentIntervalStartKey+keyEpsilon*0.8, (it-1)->value));
 | |
|         } else
 | |
|           lineData->append(QCPGraphData(currentIntervalFirstPoint->key, currentIntervalFirstPoint->value));
 | |
|         lastIntervalEndKey = (it-1)->key;
 | |
|         minValue = it->value;
 | |
|         maxValue = it->value;
 | |
|         currentIntervalFirstPoint = it;
 | |
|         currentIntervalStartKey = keyAxis->pixelToCoord((int)(keyAxis->coordToPixel(it->key)+reversedRound));
 | |
|         if (keyEpsilonVariable)
 | |
|           keyEpsilon = qAbs(currentIntervalStartKey-keyAxis->pixelToCoord(keyAxis->coordToPixel(currentIntervalStartKey)+1.0*reversedFactor));
 | |
|         intervalDataCount = 1;
 | |
|       }
 | |
|       ++it;
 | |
|     }
 | |
|     // handle last interval:
 | |
|     if (intervalDataCount >= 2) // last pixel had multiple data points, consolidate them to a cluster
 | |
|     {
 | |
|       if (lastIntervalEndKey < currentIntervalStartKey-keyEpsilon) // last point wasn't a cluster, so first point of this cluster must be at a real data point
 | |
|         lineData->append(QCPGraphData(currentIntervalStartKey+keyEpsilon*0.2, currentIntervalFirstPoint->value));
 | |
|       lineData->append(QCPGraphData(currentIntervalStartKey+keyEpsilon*0.25, minValue));
 | |
|       lineData->append(QCPGraphData(currentIntervalStartKey+keyEpsilon*0.75, maxValue));
 | |
|     } else
 | |
|       lineData->append(QCPGraphData(currentIntervalFirstPoint->key, currentIntervalFirstPoint->value));
 | |
|     
 | |
|   } else // don't use adaptive sampling algorithm, transfer points one-to-one from the data container into the output
 | |
|   {
 | |
|     QCPGraphDataContainer::const_iterator it = begin;
 | |
|     lineData->reserve(dataCount+2); // +2 for possible fill end points
 | |
|     while (it != end)
 | |
|     {
 | |
|       lineData->append(*it);
 | |
|       ++it;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Returns via \a scatterData the data points that need to be visualized for this graph when
 | |
|   plotting scatter points, taking into consideration the currently visible axis ranges and, if \ref
 | |
|   setAdaptiveSampling is enabled, local point densities. The considered data can be restricted
 | |
|   further by \a begin and \a end, e.g. to only plot a certain segment of the data (see \ref
 | |
|   getDataSegments).
 | |
| 
 | |
|   This method is used by \ref getScatters to retrieve the basic working set of data.
 | |
| 
 | |
|   \see getOptimizedLineData
 | |
| */
 | |
| void QCPGraph::getOptimizedScatterData(QVector<QCPGraphData> *scatterData, QCPGraphDataContainer::const_iterator begin, QCPGraphDataContainer::const_iterator end) const
 | |
| {
 | |
|   if (!scatterData) return;
 | |
|   QCPAxis *keyAxis = mKeyAxis.data();
 | |
|   QCPAxis *valueAxis = mValueAxis.data();
 | |
|   if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
 | |
|   
 | |
|   const int scatterModulo = mScatterSkip+1;
 | |
|   const bool doScatterSkip = mScatterSkip > 0;
 | |
|   int beginIndex = begin-mDataContainer->constBegin();
 | |
|   int endIndex = end-mDataContainer->constBegin();
 | |
|   while (doScatterSkip && begin != end && beginIndex % scatterModulo != 0) // advance begin iterator to first non-skipped scatter
 | |
|   {
 | |
|     ++beginIndex;
 | |
|     ++begin;
 | |
|   }
 | |
|   if (begin == end) return;
 | |
|   int dataCount = end-begin;
 | |
|   int maxCount = std::numeric_limits<int>::max();
 | |
|   if (mAdaptiveSampling)
 | |
|   {
 | |
|     int keyPixelSpan = qAbs(keyAxis->coordToPixel(begin->key)-keyAxis->coordToPixel((end-1)->key));
 | |
|     maxCount = 2*keyPixelSpan+2;
 | |
|   }
 | |
|   
 | |
|   if (mAdaptiveSampling && dataCount >= maxCount) // use adaptive sampling only if there are at least two points per pixel on average
 | |
|   {
 | |
|     double valueMaxRange = valueAxis->range().upper;
 | |
|     double valueMinRange = valueAxis->range().lower;
 | |
|     QCPGraphDataContainer::const_iterator it = begin;
 | |
|     int itIndex = beginIndex;
 | |
|     double minValue = it->value;
 | |
|     double maxValue = it->value;
 | |
|     QCPGraphDataContainer::const_iterator minValueIt = it;
 | |
|     QCPGraphDataContainer::const_iterator maxValueIt = it;
 | |
|     QCPGraphDataContainer::const_iterator currentIntervalStart = it;
 | |
|     int reversedFactor = keyAxis->pixelOrientation(); // is used to calculate keyEpsilon pixel into the correct direction
 | |
|     int reversedRound = reversedFactor==-1 ? 1 : 0; // is used to switch between floor (normal) and ceil (reversed) rounding of currentIntervalStartKey
 | |
|     double currentIntervalStartKey = keyAxis->pixelToCoord((int)(keyAxis->coordToPixel(begin->key)+reversedRound));
 | |
|     double keyEpsilon = qAbs(currentIntervalStartKey-keyAxis->pixelToCoord(keyAxis->coordToPixel(currentIntervalStartKey)+1.0*reversedFactor)); // interval of one pixel on screen when mapped to plot key coordinates
 | |
|     bool keyEpsilonVariable = keyAxis->scaleType() == QCPAxis::stLogarithmic; // indicates whether keyEpsilon needs to be updated after every interval (for log axes)
 | |
|     int intervalDataCount = 1;
 | |
|     // advance iterator to second (non-skipped) data point because adaptive sampling works in 1 point retrospect:
 | |
|     if (!doScatterSkip)
 | |
|       ++it;
 | |
|     else
 | |
|     {
 | |
|       itIndex += scatterModulo;
 | |
|       if (itIndex < endIndex) // make sure we didn't jump over end
 | |
|         it += scatterModulo;
 | |
|       else
 | |
|       {
 | |
|         it = end;
 | |
|         itIndex = endIndex;
 | |
|       }
 | |
|     }
 | |
|     // main loop over data points:
 | |
|     while (it != end)
 | |
|     {
 | |
|       if (it->key < currentIntervalStartKey+keyEpsilon) // data point is still within same pixel, so skip it and expand value span of this pixel if necessary
 | |
|       {
 | |
|         if (it->value < minValue && it->value > valueMinRange && it->value < valueMaxRange)
 | |
|         {
 | |
|           minValue = it->value;
 | |
|           minValueIt = it;
 | |
|         } else if (it->value > maxValue && it->value > valueMinRange && it->value < valueMaxRange)
 | |
|         {
 | |
|           maxValue = it->value;
 | |
|           maxValueIt = it;
 | |
|         }
 | |
|         ++intervalDataCount;
 | |
|       } else // new pixel started
 | |
|       {
 | |
|         if (intervalDataCount >= 2) // last pixel had multiple data points, consolidate them
 | |
|         {
 | |
|           // determine value pixel span and add as many points in interval to maintain certain vertical data density (this is specific to scatter plot):
 | |
|           double valuePixelSpan = qAbs(valueAxis->coordToPixel(minValue)-valueAxis->coordToPixel(maxValue));
 | |
|           int dataModulo = qMax(1, qRound(intervalDataCount/(valuePixelSpan/4.0))); // approximately every 4 value pixels one data point on average
 | |
|           QCPGraphDataContainer::const_iterator intervalIt = currentIntervalStart;
 | |
|           int c = 0;
 | |
|           while (intervalIt != it)
 | |
|           {
 | |
|             if ((c % dataModulo == 0 || intervalIt == minValueIt || intervalIt == maxValueIt) && intervalIt->value > valueMinRange && intervalIt->value < valueMaxRange)
 | |
|               scatterData->append(*intervalIt);
 | |
|             ++c;
 | |
|             if (!doScatterSkip)
 | |
|               ++intervalIt;
 | |
|             else
 | |
|               intervalIt += scatterModulo; // since we know indices of "currentIntervalStart", "intervalIt" and "it" are multiples of scatterModulo, we can't accidentally jump over "it" here
 | |
|           }
 | |
|         } else if (currentIntervalStart->value > valueMinRange && currentIntervalStart->value < valueMaxRange)
 | |
|           scatterData->append(*currentIntervalStart);
 | |
|         minValue = it->value;
 | |
|         maxValue = it->value;
 | |
|         currentIntervalStart = it;
 | |
|         currentIntervalStartKey = keyAxis->pixelToCoord((int)(keyAxis->coordToPixel(it->key)+reversedRound));
 | |
|         if (keyEpsilonVariable)
 | |
|           keyEpsilon = qAbs(currentIntervalStartKey-keyAxis->pixelToCoord(keyAxis->coordToPixel(currentIntervalStartKey)+1.0*reversedFactor));
 | |
|         intervalDataCount = 1;
 | |
|       }
 | |
|       // advance to next data point:
 | |
|       if (!doScatterSkip)
 | |
|         ++it;
 | |
|       else
 | |
|       {
 | |
|         itIndex += scatterModulo;
 | |
|         if (itIndex < endIndex) // make sure we didn't jump over end
 | |
|           it += scatterModulo;
 | |
|         else
 | |
|         {
 | |
|           it = end;
 | |
|           itIndex = endIndex;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     // handle last interval:
 | |
|     if (intervalDataCount >= 2) // last pixel had multiple data points, consolidate them
 | |
|     {
 | |
|       // determine value pixel span and add as many points in interval to maintain certain vertical data density (this is specific to scatter plot):
 | |
|       double valuePixelSpan = qAbs(valueAxis->coordToPixel(minValue)-valueAxis->coordToPixel(maxValue));
 | |
|       int dataModulo = qMax(1, qRound(intervalDataCount/(valuePixelSpan/4.0))); // approximately every 4 value pixels one data point on average
 | |
|       QCPGraphDataContainer::const_iterator intervalIt = currentIntervalStart;
 | |
|       int intervalItIndex = intervalIt-mDataContainer->constBegin();
 | |
|       int c = 0;
 | |
|       while (intervalIt != it)
 | |
|       {
 | |
|         if ((c % dataModulo == 0 || intervalIt == minValueIt || intervalIt == maxValueIt) && intervalIt->value > valueMinRange && intervalIt->value < valueMaxRange)
 | |
|           scatterData->append(*intervalIt);
 | |
|         ++c;
 | |
|         if (!doScatterSkip)
 | |
|           ++intervalIt;
 | |
|         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:
 | |
|         {
 | |
|           intervalItIndex += scatterModulo;
 | |
|           if (intervalItIndex < itIndex)
 | |
|             intervalIt += scatterModulo;
 | |
|           else
 | |
|           {
 | |
|             intervalIt = it;
 | |
|             intervalItIndex = itIndex;
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     } else if (currentIntervalStart->value > valueMinRange && currentIntervalStart->value < valueMaxRange)
 | |
|       scatterData->append(*currentIntervalStart);
 | |
|     
 | |
|   } else // don't use adaptive sampling algorithm, transfer points one-to-one from the data container into the output
 | |
|   {
 | |
|     QCPGraphDataContainer::const_iterator it = begin;
 | |
|     int itIndex = beginIndex;
 | |
|     scatterData->reserve(dataCount);
 | |
|     while (it != end)
 | |
|     {
 | |
|       scatterData->append(*it);
 | |
|       // advance to next data point:
 | |
|       if (!doScatterSkip)
 | |
|         ++it;
 | |
|       else
 | |
|       {
 | |
|         itIndex += scatterModulo;
 | |
|         if (itIndex < endIndex)
 | |
|           it += scatterModulo;
 | |
|         else
 | |
|         {
 | |
|           it = end;
 | |
|           itIndex = endIndex;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   This method outputs the currently visible data range via \a begin and \a end. The returned range
 | |
|   will also never exceed \a rangeRestriction.
 | |
| 
 | |
|   This method takes into account that the drawing of data lines at the axis rect border always
 | |
|   requires the points just outside the visible axis range. So \a begin and \a end may actually
 | |
|   indicate a range that contains one additional data point to the left and right of the visible
 | |
|   axis range.
 | |
| */
 | |
| void QCPGraph::getVisibleDataBounds(QCPGraphDataContainer::const_iterator &begin, QCPGraphDataContainer::const_iterator &end, const QCPDataRange &rangeRestriction) const
 | |
| {
 | |
|   if (rangeRestriction.isEmpty())
 | |
|   {
 | |
|     end = mDataContainer->constEnd();
 | |
|     begin = end;
 | |
|   } else
 | |
|   {
 | |
|     QCPAxis *keyAxis = mKeyAxis.data();
 | |
|     QCPAxis *valueAxis = mValueAxis.data();
 | |
|     if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
 | |
|     // get visible data range:
 | |
|     begin = mDataContainer->findBegin(keyAxis->range().lower);
 | |
|     end = mDataContainer->findEnd(keyAxis->range().upper);
 | |
|     // limit lower/upperEnd to rangeRestriction:
 | |
|     mDataContainer->limitIteratorsToDataRange(begin, end, rangeRestriction); // this also ensures rangeRestriction outside data bounds doesn't break anything
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   The line vector generated by e.g. \ref getLines describes only the line that connects the data
 | |
|   points. If the graph needs to be filled, two additional points need to be added at the
 | |
|   value-zero-line in the lower and upper key positions of the graph. This function calculates these
 | |
|   points and adds them to the end of \a lineData. Since the fill is typically drawn before the line
 | |
|   stroke, these added points need to be removed again after the fill is done, with the
 | |
|   removeFillBasePoints function.
 | |
| 
 | |
|   The expanding of \a lines by two points will not cause unnecessary memory reallocations, because
 | |
|   the data vector generation functions (e.g. \ref getLines) reserve two extra points when they
 | |
|   allocate memory for \a lines.
 | |
|   
 | |
|   \see removeFillBasePoints, lowerFillBasePoint, upperFillBasePoint
 | |
| */
 | |
| void QCPGraph::addFillBasePoints(QVector<QPointF> *lines) const
 | |
| {
 | |
|   if (!mKeyAxis) { qDebug() << Q_FUNC_INFO << "invalid key axis"; return; }
 | |
|   if (!lines) { qDebug() << Q_FUNC_INFO << "passed null as lineData"; return; }
 | |
|   if (lines->isEmpty()) return;
 | |
|   
 | |
|   // append points that close the polygon fill at the key axis:
 | |
|   if (mKeyAxis.data()->orientation() == Qt::Vertical)
 | |
|   {
 | |
|     *lines << upperFillBasePoint(lines->last().y());
 | |
|     *lines << lowerFillBasePoint(lines->first().y());
 | |
|   } else
 | |
|   {
 | |
|     *lines << upperFillBasePoint(lines->last().x());
 | |
|     *lines << lowerFillBasePoint(lines->first().x());
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   removes the two points from \a lines that were added by \ref addFillBasePoints.
 | |
|   
 | |
|   \see addFillBasePoints, lowerFillBasePoint, upperFillBasePoint
 | |
| */
 | |
| void QCPGraph::removeFillBasePoints(QVector<QPointF> *lines) const
 | |
| {
 | |
|   if (!lines) { qDebug() << Q_FUNC_INFO << "passed null as lineData"; return; }
 | |
|   if (lines->isEmpty()) return;
 | |
|   
 | |
|   lines->remove(lines->size()-2, 2);
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   called by \ref addFillBasePoints to conveniently assign the point which closes the fill polygon
 | |
|   on the lower side of the zero-value-line parallel to the key axis. The logarithmic axis scale
 | |
|   case is a bit special, since the zero-value-line in pixel coordinates is in positive or negative
 | |
|   infinity. So this case is handled separately by just closing the fill polygon on the axis which
 | |
|   lies in the direction towards the zero value.
 | |
|   
 | |
|   \a lowerKey will be the the key (in pixels) of the returned point. Depending on whether the key
 | |
|   axis is horizontal or vertical, \a lowerKey will end up as the x or y value of the returned
 | |
|   point, respectively.
 | |
|   
 | |
|   \see upperFillBasePoint, addFillBasePoints
 | |
| */
 | |
| QPointF QCPGraph::lowerFillBasePoint(double lowerKey) const
 | |
| {
 | |
|   QCPAxis *keyAxis = mKeyAxis.data();
 | |
|   QCPAxis *valueAxis = mValueAxis.data();
 | |
|   if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return QPointF(); }
 | |
|   
 | |
|   QPointF point;
 | |
|   if (valueAxis->scaleType() == QCPAxis::stLinear)
 | |
|   {
 | |
|     if (keyAxis->axisType() == QCPAxis::atLeft)
 | |
|     {
 | |
|       point.setX(valueAxis->coordToPixel(0));
 | |
|       point.setY(lowerKey);
 | |
|     } else if (keyAxis->axisType() == QCPAxis::atRight)
 | |
|     {
 | |
|       point.setX(valueAxis->coordToPixel(0));
 | |
|       point.setY(lowerKey);
 | |
|     } else if (keyAxis->axisType() == QCPAxis::atTop)
 | |
|     {
 | |
|       point.setX(lowerKey);
 | |
|       point.setY(valueAxis->coordToPixel(0));
 | |
|     } else if (keyAxis->axisType() == QCPAxis::atBottom)
 | |
|     {
 | |
|       point.setX(lowerKey);
 | |
|       point.setY(valueAxis->coordToPixel(0));
 | |
|     }
 | |
|   } else // valueAxis->mScaleType == QCPAxis::stLogarithmic
 | |
|   {
 | |
|     // In logarithmic scaling we can't just draw to value zero so we just fill all the way
 | |
|     // to the axis which is in the direction towards zero
 | |
|     if (keyAxis->orientation() == Qt::Vertical)
 | |
|     {
 | |
|       if ((valueAxis->range().upper < 0 && !valueAxis->rangeReversed()) ||
 | |
|           (valueAxis->range().upper > 0 && valueAxis->rangeReversed())) // if range is negative, zero is on opposite side of key axis
 | |
|         point.setX(keyAxis->axisRect()->right());
 | |
|       else
 | |
|         point.setX(keyAxis->axisRect()->left());
 | |
|       point.setY(lowerKey);
 | |
|     } else if (keyAxis->axisType() == QCPAxis::atTop || keyAxis->axisType() == QCPAxis::atBottom)
 | |
|     {
 | |
|       point.setX(lowerKey);
 | |
|       if ((valueAxis->range().upper < 0 && !valueAxis->rangeReversed()) ||
 | |
|           (valueAxis->range().upper > 0 && valueAxis->rangeReversed())) // if range is negative, zero is on opposite side of key axis
 | |
|         point.setY(keyAxis->axisRect()->top());
 | |
|       else
 | |
|         point.setY(keyAxis->axisRect()->bottom());
 | |
|     }
 | |
|   }
 | |
|   return point;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   called by \ref addFillBasePoints to conveniently assign the point which closes the fill
 | |
|   polygon on the upper side of the zero-value-line parallel to the key axis. The logarithmic axis
 | |
|   scale case is a bit special, since the zero-value-line in pixel coordinates is in positive or
 | |
|   negative infinity. So this case is handled separately by just closing the fill polygon on the
 | |
|   axis which lies in the direction towards the zero value.
 | |
| 
 | |
|   \a upperKey will be the the key (in pixels) of the returned point. Depending on whether the key
 | |
|   axis is horizontal or vertical, \a upperKey will end up as the x or y value of the returned
 | |
|   point, respectively.
 | |
|   
 | |
|   \see lowerFillBasePoint, addFillBasePoints
 | |
| */
 | |
| QPointF QCPGraph::upperFillBasePoint(double upperKey) const
 | |
| {
 | |
|   QCPAxis *keyAxis = mKeyAxis.data();
 | |
|   QCPAxis *valueAxis = mValueAxis.data();
 | |
|   if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return QPointF(); }
 | |
|   
 | |
|   QPointF point;
 | |
|   if (valueAxis->scaleType() == QCPAxis::stLinear)
 | |
|   {
 | |
|     if (keyAxis->axisType() == QCPAxis::atLeft)
 | |
|     {
 | |
|       point.setX(valueAxis->coordToPixel(0));
 | |
|       point.setY(upperKey);
 | |
|     } else if (keyAxis->axisType() == QCPAxis::atRight)
 | |
|     {
 | |
|       point.setX(valueAxis->coordToPixel(0));
 | |
|       point.setY(upperKey);
 | |
|     } else if (keyAxis->axisType() == QCPAxis::atTop)
 | |
|     {
 | |
|       point.setX(upperKey);
 | |
|       point.setY(valueAxis->coordToPixel(0));
 | |
|     } else if (keyAxis->axisType() == QCPAxis::atBottom)
 | |
|     {
 | |
|       point.setX(upperKey);
 | |
|       point.setY(valueAxis->coordToPixel(0));
 | |
|     }
 | |
|   } else // valueAxis->mScaleType == QCPAxis::stLogarithmic
 | |
|   {
 | |
|     // In logarithmic scaling we can't just draw to value 0 so we just fill all the way
 | |
|     // to the axis which is in the direction towards 0
 | |
|     if (keyAxis->orientation() == Qt::Vertical)
 | |
|     {
 | |
|       if ((valueAxis->range().upper < 0 && !valueAxis->rangeReversed()) ||
 | |
|           (valueAxis->range().upper > 0 && valueAxis->rangeReversed())) // if range is negative, zero is on opposite side of key axis
 | |
|         point.setX(keyAxis->axisRect()->right());
 | |
|       else
 | |
|         point.setX(keyAxis->axisRect()->left());
 | |
|       point.setY(upperKey);
 | |
|     } else if (keyAxis->axisType() == QCPAxis::atTop || keyAxis->axisType() == QCPAxis::atBottom)
 | |
|     {
 | |
|       point.setX(upperKey);
 | |
|       if ((valueAxis->range().upper < 0 && !valueAxis->rangeReversed()) ||
 | |
|           (valueAxis->range().upper > 0 && valueAxis->rangeReversed())) // if range is negative, zero is on opposite side of key axis
 | |
|         point.setY(keyAxis->axisRect()->top());
 | |
|       else
 | |
|         point.setY(keyAxis->axisRect()->bottom());
 | |
|     }
 | |
|   }
 | |
|   return point;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Generates the polygon needed for drawing channel fills between this graph and the graph specified
 | |
|   in \a mChannelFillGraph (see \ref setChannelFillGraph). The data points representing the line of
 | |
|   this graph in pixel coordinates must be passed in \a lines, the corresponding points of the other
 | |
|   graph are generated by calling its \ref getLines method.
 | |
| 
 | |
|   This method may return an empty polygon if the key ranges of the two graphs have no overlap of if
 | |
|   they don't have the same orientation (e.g. one key axis vertical, the other horizontal). For
 | |
|   increased performance (due to implicit sharing), it is recommended to keep the returned QPolygonF
 | |
|   const.
 | |
| */
 | |
| const QPolygonF QCPGraph::getChannelFillPolygon(const QVector<QPointF> *lines) const
 | |
| {
 | |
|   if (!mChannelFillGraph)
 | |
|     return QPolygonF();
 | |
|   
 | |
|   QCPAxis *keyAxis = mKeyAxis.data();
 | |
|   QCPAxis *valueAxis = mValueAxis.data();
 | |
|   if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return QPolygonF(); }
 | |
|   if (!mChannelFillGraph.data()->mKeyAxis) { qDebug() << Q_FUNC_INFO << "channel fill target key axis invalid"; return QPolygonF(); }
 | |
|   
 | |
|   if (mChannelFillGraph.data()->mKeyAxis.data()->orientation() != keyAxis->orientation())
 | |
|     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)
 | |
|   
 | |
|   if (lines->isEmpty()) return QPolygonF();
 | |
|   QVector<QPointF> otherData;
 | |
|   mChannelFillGraph.data()->getLines(&otherData, QCPDataRange(0, mChannelFillGraph.data()->dataCount()));
 | |
|   if (otherData.isEmpty()) return QPolygonF();
 | |
|   QVector<QPointF> thisData;
 | |
|   thisData.reserve(lines->size()+otherData.size()); // because we will join both vectors at end of this function
 | |
|   for (int i=0; i<lines->size(); ++i) // don't use the vector<<(vector),  it squeezes internally, which ruins the performance tuning with reserve()
 | |
|     thisData << lines->at(i);
 | |
|   
 | |
|   // pointers to be able to swap them, depending which data range needs cropping:
 | |
|   QVector<QPointF> *staticData = &thisData;
 | |
|   QVector<QPointF> *croppedData = &otherData;
 | |
|   
 | |
|   // crop both vectors to ranges in which the keys overlap (which coord is key, depends on axisType):
 | |
|   if (keyAxis->orientation() == Qt::Horizontal)
 | |
|   {
 | |
|     // x is key
 | |
|     // if an axis range is reversed, the data point keys will be descending. Reverse them, since following algorithm assumes ascending keys:
 | |
|     if (staticData->first().x() > staticData->last().x())
 | |
|     {
 | |
|       int size = staticData->size();
 | |
|       for (int i=0; i<size/2; ++i)
 | |
|         qSwap((*staticData)[i], (*staticData)[size-1-i]);
 | |
|     }
 | |
|     if (croppedData->first().x() > croppedData->last().x())
 | |
|     {
 | |
|       int size = croppedData->size();
 | |
|       for (int i=0; i<size/2; ++i)
 | |
|         qSwap((*croppedData)[i], (*croppedData)[size-1-i]);
 | |
|     }
 | |
|     // crop lower bound:
 | |
|     if (staticData->first().x() < croppedData->first().x()) // other one must be cropped
 | |
|       qSwap(staticData, croppedData);
 | |
|     int lowBound = findIndexBelowX(croppedData, staticData->first().x());
 | |
|     if (lowBound == -1) return QPolygonF(); // key ranges have no overlap
 | |
|     croppedData->remove(0, lowBound);
 | |
|     // set lowest point of cropped data to fit exactly key position of first static data
 | |
|     // point via linear interpolation:
 | |
|     if (croppedData->size() < 2) return QPolygonF(); // need at least two points for interpolation
 | |
|     double slope;
 | |
|     if (croppedData->at(1).x()-croppedData->at(0).x() != 0)
 | |
|       slope = (croppedData->at(1).y()-croppedData->at(0).y())/(croppedData->at(1).x()-croppedData->at(0).x());
 | |
|     else
 | |
|       slope = 0;
 | |
|     (*croppedData)[0].setY(croppedData->at(0).y()+slope*(staticData->first().x()-croppedData->at(0).x()));
 | |
|     (*croppedData)[0].setX(staticData->first().x());
 | |
|     
 | |
|     // crop upper bound:
 | |
|     if (staticData->last().x() > croppedData->last().x()) // other one must be cropped
 | |
|       qSwap(staticData, croppedData);
 | |
|     int highBound = findIndexAboveX(croppedData, staticData->last().x());
 | |
|     if (highBound == -1) return QPolygonF(); // key ranges have no overlap
 | |
|     croppedData->remove(highBound+1, croppedData->size()-(highBound+1));
 | |
|     // set highest point of cropped data to fit exactly key position of last static data
 | |
|     // point via linear interpolation:
 | |
|     if (croppedData->size() < 2) return QPolygonF(); // need at least two points for interpolation
 | |
|     int li = croppedData->size()-1; // last index
 | |
|     if (croppedData->at(li).x()-croppedData->at(li-1).x() != 0)
 | |
|       slope = (croppedData->at(li).y()-croppedData->at(li-1).y())/(croppedData->at(li).x()-croppedData->at(li-1).x());
 | |
|     else
 | |
|       slope = 0;
 | |
|     (*croppedData)[li].setY(croppedData->at(li-1).y()+slope*(staticData->last().x()-croppedData->at(li-1).x()));
 | |
|     (*croppedData)[li].setX(staticData->last().x());
 | |
|   } else // mKeyAxis->orientation() == Qt::Vertical
 | |
|   {
 | |
|     // y is key
 | |
|     // similar to "x is key" but switched x,y. Further, lower/upper meaning is inverted compared to x,
 | |
|     // because in pixel coordinates, y increases from top to bottom, not bottom to top like data coordinate.
 | |
|     // if an axis range is reversed, the data point keys will be descending. Reverse them, since following algorithm assumes ascending keys:
 | |
|     if (staticData->first().y() < staticData->last().y())
 | |
|     {
 | |
|       int size = staticData->size();
 | |
|       for (int i=0; i<size/2; ++i)
 | |
|         qSwap((*staticData)[i], (*staticData)[size-1-i]);
 | |
|     }
 | |
|     if (croppedData->first().y() < croppedData->last().y())
 | |
|     {
 | |
|       int size = croppedData->size();
 | |
|       for (int i=0; i<size/2; ++i)
 | |
|         qSwap((*croppedData)[i], (*croppedData)[size-1-i]);
 | |
|     }
 | |
|     // crop lower bound:
 | |
|     if (staticData->first().y() > croppedData->first().y()) // other one must be cropped
 | |
|       qSwap(staticData, croppedData);
 | |
|     int lowBound = findIndexAboveY(croppedData, staticData->first().y());
 | |
|     if (lowBound == -1) return QPolygonF(); // key ranges have no overlap
 | |
|     croppedData->remove(0, lowBound);
 | |
|     // set lowest point of cropped data to fit exactly key position of first static data
 | |
|     // point via linear interpolation:
 | |
|     if (croppedData->size() < 2) return QPolygonF(); // need at least two points for interpolation
 | |
|     double slope;
 | |
|     if (croppedData->at(1).y()-croppedData->at(0).y() != 0) // avoid division by zero in step plots
 | |
|       slope = (croppedData->at(1).x()-croppedData->at(0).x())/(croppedData->at(1).y()-croppedData->at(0).y());
 | |
|     else
 | |
|       slope = 0;
 | |
|     (*croppedData)[0].setX(croppedData->at(0).x()+slope*(staticData->first().y()-croppedData->at(0).y()));
 | |
|     (*croppedData)[0].setY(staticData->first().y());
 | |
|     
 | |
|     // crop upper bound:
 | |
|     if (staticData->last().y() < croppedData->last().y()) // other one must be cropped
 | |
|       qSwap(staticData, croppedData);
 | |
|     int highBound = findIndexBelowY(croppedData, staticData->last().y());
 | |
|     if (highBound == -1) return QPolygonF(); // key ranges have no overlap
 | |
|     croppedData->remove(highBound+1, croppedData->size()-(highBound+1));
 | |
|     // set highest point of cropped data to fit exactly key position of last static data
 | |
|     // point via linear interpolation:
 | |
|     if (croppedData->size() < 2) return QPolygonF(); // need at least two points for interpolation
 | |
|     int li = croppedData->size()-1; // last index
 | |
|     if (croppedData->at(li).y()-croppedData->at(li-1).y() != 0) // avoid division by zero in step plots
 | |
|       slope = (croppedData->at(li).x()-croppedData->at(li-1).x())/(croppedData->at(li).y()-croppedData->at(li-1).y());
 | |
|     else
 | |
|       slope = 0;
 | |
|     (*croppedData)[li].setX(croppedData->at(li-1).x()+slope*(staticData->last().y()-croppedData->at(li-1).y()));
 | |
|     (*croppedData)[li].setY(staticData->last().y());
 | |
|   }
 | |
|   
 | |
|   // return joined:
 | |
|   for (int i=otherData.size()-1; i>=0; --i) // insert reversed, otherwise the polygon will be twisted
 | |
|     thisData << otherData.at(i);
 | |
|   return QPolygonF(thisData);
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Finds the smallest index of \a data, whose points x value is just above \a x. Assumes x values in
 | |
|   \a data points are ordered ascending, as is the case when plotting with horizontal key axis.
 | |
| 
 | |
|   Used to calculate the channel fill polygon, see \ref getChannelFillPolygon.
 | |
| */
 | |
| int QCPGraph::findIndexAboveX(const QVector<QPointF> *data, double x) const
 | |
| {
 | |
|   for (int i=data->size()-1; i>=0; --i)
 | |
|   {
 | |
|     if (data->at(i).x() < x)
 | |
|     {
 | |
|       if (i<data->size()-1)
 | |
|         return i+1;
 | |
|       else
 | |
|         return data->size()-1;
 | |
|     }
 | |
|   }
 | |
|   return -1;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Finds the highest index of \a data, whose points x value is just below \a x. Assumes x values in
 | |
|   \a data points are ordered ascending, as is the case when plotting with horizontal key axis.
 | |
|   
 | |
|   Used to calculate the channel fill polygon, see \ref getChannelFillPolygon.
 | |
| */
 | |
| int QCPGraph::findIndexBelowX(const QVector<QPointF> *data, double x) const
 | |
| {
 | |
|   for (int i=0; i<data->size(); ++i)
 | |
|   {
 | |
|     if (data->at(i).x() > x)
 | |
|     {
 | |
|       if (i>0)
 | |
|         return i-1;
 | |
|       else
 | |
|         return 0;
 | |
|     }
 | |
|   }
 | |
|   return -1;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Finds the smallest index of \a data, whose points y value is just above \a y. Assumes y values in
 | |
|   \a data points are ordered descending, as is the case when plotting with vertical key axis.
 | |
|   
 | |
|   Used to calculate the channel fill polygon, see \ref getChannelFillPolygon.
 | |
| */
 | |
| int QCPGraph::findIndexAboveY(const QVector<QPointF> *data, double y) const
 | |
| {
 | |
|   for (int i=0; i<data->size(); ++i)
 | |
|   {
 | |
|     if (data->at(i).y() < y)
 | |
|     {
 | |
|       if (i>0)
 | |
|         return i-1;
 | |
|       else
 | |
|         return 0;
 | |
|     }
 | |
|   }
 | |
|   return -1;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Calculates the minimum distance in pixels the graph's representation has from the given \a
 | |
|   pixelPoint. This is used to determine whether the graph was clicked or not, e.g. in \ref
 | |
|   selectTest. The closest data point to \a pixelPoint is returned in \a closestData. Note that if
 | |
|   the graph has a line representation, the returned distance may be smaller than the distance to
 | |
|   the \a closestData point, since the distance to the graph line is also taken into account.
 | |
|   
 | |
|   If either the graph has no data or if the line style is \ref lsNone and the scatter style's shape
 | |
|   is \ref QCPScatterStyle::ssNone (i.e. there is no visual representation of the graph), returns -1.0.
 | |
| */
 | |
| double QCPGraph::pointDistance(const QPointF &pixelPoint, QCPGraphDataContainer::const_iterator &closestData) const
 | |
| {
 | |
|   closestData = mDataContainer->constEnd();
 | |
|   if (mDataContainer->isEmpty())
 | |
|     return -1.0;
 | |
|   if (mLineStyle == lsNone && mScatterStyle.isNone())
 | |
|     return -1.0;
 | |
|   
 | |
|   // calculate minimum distances to graph data points and find closestData iterator:
 | |
|   double minDistSqr = std::numeric_limits<double>::max();
 | |
|   // determine which key range comes into question, taking selection tolerance around pos into account:
 | |
|   double posKeyMin, posKeyMax, dummy;
 | |
|   pixelsToCoords(pixelPoint-QPointF(mParentPlot->selectionTolerance(), mParentPlot->selectionTolerance()), posKeyMin, dummy);
 | |
|   pixelsToCoords(pixelPoint+QPointF(mParentPlot->selectionTolerance(), mParentPlot->selectionTolerance()), posKeyMax, dummy);
 | |
|   if (posKeyMin > posKeyMax)
 | |
|     qSwap(posKeyMin, posKeyMax);
 | |
|   // iterate over found data points and then choose the one with the shortest distance to pos:
 | |
|   QCPGraphDataContainer::const_iterator begin = mDataContainer->findBegin(posKeyMin, true);
 | |
|   QCPGraphDataContainer::const_iterator end = mDataContainer->findEnd(posKeyMax, true);
 | |
|   for (QCPGraphDataContainer::const_iterator it=begin; it!=end; ++it)
 | |
|   {
 | |
|     const double currentDistSqr = QCPVector2D(coordsToPixels(it->key, it->value)-pixelPoint).lengthSquared();
 | |
|     if (currentDistSqr < minDistSqr)
 | |
|     {
 | |
|       minDistSqr = currentDistSqr;
 | |
|       closestData = it;
 | |
|     }
 | |
|   }
 | |
|     
 | |
|   // calculate distance to graph line if there is one (if so, will probably be smaller than distance to closest data point):
 | |
|   if (mLineStyle != lsNone)
 | |
|   {
 | |
|     // line displayed, calculate distance to line segments:
 | |
|     QVector<QPointF> lineData;
 | |
|     getLines(&lineData, QCPDataRange(0, dataCount()));
 | |
|     QCPVector2D p(pixelPoint);
 | |
|     const int step = mLineStyle==lsImpulse ? 2 : 1; // impulse plot differs from other line styles in that the lineData points are only pairwise connected
 | |
|     for (int i=0; i<lineData.size()-1; i+=step)
 | |
|     {
 | |
|       const double currentDistSqr = p.distanceSquaredToLine(lineData.at(i), lineData.at(i+1));
 | |
|       if (currentDistSqr < minDistSqr)
 | |
|         minDistSqr = currentDistSqr;
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   return qSqrt(minDistSqr);
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Finds the highest index of \a data, whose points y value is just below \a y. Assumes y values in
 | |
|   \a data points are ordered descending, as is the case when plotting with vertical key axis (since
 | |
|   keys are ordered ascending).
 | |
| 
 | |
|   Used to calculate the channel fill polygon, see \ref getChannelFillPolygon.
 | |
| */
 | |
| int QCPGraph::findIndexBelowY(const QVector<QPointF> *data, double y) const
 | |
| {
 | |
|   for (int i=data->size()-1; i>=0; --i)
 | |
|   {
 | |
|     if (data->at(i).y() > y)
 | |
|     {
 | |
|       if (i<data->size()-1)
 | |
|         return i+1;
 | |
|       else
 | |
|         return data->size()-1;
 | |
|     }
 | |
|   }
 | |
|   return -1;
 | |
| }
 | |
| /* end of 'src/plottables/plottable-graph.cpp' */
 | |
| 
 | |
| 
 | |
| /* including file 'src/plottables/plottable-curve.cpp', size 60009           */
 | |
| /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPCurveData
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPCurveData
 | |
|   \brief Holds the data of one single data point for QCPCurve.
 | |
|   
 | |
|   The stored data is:
 | |
|   \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)
 | |
|   \li \a key: coordinate on the key axis of this curve point (this is the \a mainKey)
 | |
|   \li \a value: coordinate on the value axis of this curve point (this is the \a mainValue)
 | |
|   
 | |
|   The container for storing multiple data points is \ref QCPCurveDataContainer. It is a typedef for
 | |
|   \ref QCPDataContainer with \ref QCPCurveData as the DataType template parameter. See the
 | |
|   documentation there for an explanation regarding the data type's generic methods.
 | |
|   
 | |
|   \see QCPCurveDataContainer
 | |
| */
 | |
| 
 | |
| /* start documentation of inline functions */
 | |
| 
 | |
| /*! \fn double QCPCurveData::sortKey() const
 | |
|   
 | |
|   Returns the \a t member of this data point.
 | |
|   
 | |
|   For a general explanation of what this method is good for in the context of the data container,
 | |
|   see the documentation of \ref QCPDataContainer.
 | |
| */
 | |
| 
 | |
| /*! \fn static QCPCurveData QCPCurveData::fromSortKey(double sortKey)
 | |
|   
 | |
|   Returns a data point with the specified \a sortKey (assigned to the data point's \a t member).
 | |
|   All other members are set to zero.
 | |
|   
 | |
|   For a general explanation of what this method is good for in the context of the data container,
 | |
|   see the documentation of \ref QCPDataContainer.
 | |
| */
 | |
| 
 | |
| /*! \fn static static bool QCPCurveData::sortKeyIsMainKey()
 | |
|   
 | |
|   Since the member \a key is the data point key coordinate and the member \a t is the data ordering
 | |
|   parameter, this method returns false.
 | |
|   
 | |
|   For a general explanation of what this method is good for in the context of the data container,
 | |
|   see the documentation of \ref QCPDataContainer.
 | |
| */
 | |
| 
 | |
| /*! \fn double QCPCurveData::mainKey() const
 | |
|   
 | |
|   Returns the \a key member of this data point.
 | |
|   
 | |
|   For a general explanation of what this method is good for in the context of the data container,
 | |
|   see the documentation of \ref QCPDataContainer.
 | |
| */
 | |
| 
 | |
| /*! \fn double QCPCurveData::mainValue() const
 | |
|   
 | |
|   Returns the \a value member of this data point.
 | |
|   
 | |
|   For a general explanation of what this method is good for in the context of the data container,
 | |
|   see the documentation of \ref QCPDataContainer.
 | |
| */
 | |
| 
 | |
| /*! \fn QCPRange QCPCurveData::valueRange() const
 | |
|   
 | |
|   Returns a QCPRange with both lower and upper boundary set to \a value of this data point.
 | |
|   
 | |
|   For a general explanation of what this method is good for in the context of the data container,
 | |
|   see the documentation of \ref QCPDataContainer.
 | |
| */
 | |
| 
 | |
| /* end documentation of inline functions */
 | |
| 
 | |
| /*!
 | |
|   Constructs a curve data point with t, key and value set to zero.
 | |
| */
 | |
| QCPCurveData::QCPCurveData() :
 | |
|   t(0),
 | |
|   key(0),
 | |
|   value(0)
 | |
| {
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Constructs a curve data point with the specified \a t, \a key and \a value.
 | |
| */
 | |
| QCPCurveData::QCPCurveData(double t, double key, double value) :
 | |
|   t(t),
 | |
|   key(key),
 | |
|   value(value)
 | |
| {
 | |
| }
 | |
| 
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPCurve
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPCurve
 | |
|   \brief A plottable representing a parametric curve in a plot.
 | |
|   
 | |
|   \image html QCPCurve.png
 | |
|   
 | |
|   Unlike QCPGraph, plottables of this type may have multiple points with the same key coordinate,
 | |
|   so their visual representation can have \a loops. This is realized by introducing a third
 | |
|   coordinate \a t, which defines the order of the points described by the other two coordinates \a
 | |
|   x and \a y.
 | |
| 
 | |
|   To plot data, assign it with the \ref setData or \ref addData functions. Alternatively, you can
 | |
|   also access and modify the curve's data via the \ref data method, which returns a pointer to the
 | |
|   internal \ref QCPCurveDataContainer.
 | |
|   
 | |
|   Gaps in the curve can be created by adding data points with NaN as key and value
 | |
|   (<tt>qQNaN()</tt> or <tt>std::numeric_limits<double>::quiet_NaN()</tt>) in between the two data points that shall be
 | |
|   separated.
 | |
|   
 | |
|   \section qcpcurve-appearance Changing the appearance
 | |
|   
 | |
|   The appearance of the curve is determined by the pen and the brush (\ref setPen, \ref setBrush).
 | |
|   
 | |
|   \section qcpcurve-usage Usage
 | |
|   
 | |
|   Like all data representing objects in QCustomPlot, the QCPCurve is a plottable
 | |
|   (QCPAbstractPlottable). So the plottable-interface of QCustomPlot applies
 | |
|   (QCustomPlot::plottable, QCustomPlot::removePlottable, etc.)
 | |
|   
 | |
|   Usually, you first create an instance:
 | |
|   \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcurve-creation-1
 | |
|   which registers it with the QCustomPlot instance of the passed axes. Note that this QCustomPlot instance takes
 | |
|   ownership of the plottable, so do not delete it manually but use QCustomPlot::removePlottable() instead.
 | |
|   The newly created plottable can be modified, e.g.:
 | |
|   \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcurve-creation-2
 | |
| */
 | |
| 
 | |
| /* start of documentation of inline functions */
 | |
| 
 | |
| /*! \fn QSharedPointer<QCPCurveDataContainer> QCPCurve::data() const
 | |
|   
 | |
|   Returns a shared pointer to the internal data storage of type \ref QCPCurveDataContainer. You may
 | |
|   use it to directly manipulate the data, which may be more convenient and faster than using the
 | |
|   regular \ref setData or \ref addData methods.
 | |
| */
 | |
| 
 | |
| /* end of documentation of inline functions */
 | |
| 
 | |
| /*!
 | |
|   Constructs a curve which uses \a keyAxis as its key axis ("x") and \a valueAxis as its value
 | |
|   axis ("y"). \a keyAxis and \a valueAxis must reside in the same QCustomPlot instance and not have
 | |
|   the same orientation. If either of these restrictions is violated, a corresponding message is
 | |
|   printed to the debug output (qDebug), the construction is not aborted, though.
 | |
|   
 | |
|   The created QCPCurve is automatically registered with the QCustomPlot instance inferred from \a
 | |
|   keyAxis. This QCustomPlot instance takes ownership of the QCPCurve, so do not delete it manually
 | |
|   but use QCustomPlot::removePlottable() instead.
 | |
| */
 | |
| QCPCurve::QCPCurve(QCPAxis *keyAxis, QCPAxis *valueAxis) :
 | |
|   QCPAbstractPlottable1D<QCPCurveData>(keyAxis, valueAxis)
 | |
| {
 | |
|   // modify inherited properties from abstract plottable:
 | |
|   setPen(QPen(Qt::blue, 0));
 | |
|   setBrush(Qt::NoBrush);
 | |
|   
 | |
|   setScatterStyle(QCPScatterStyle());
 | |
|   setLineStyle(lsLine);
 | |
| }
 | |
| 
 | |
| QCPCurve::~QCPCurve()
 | |
| {
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Replaces the current data container with the provided \a data container.
 | |
|   
 | |
|   Since a QSharedPointer is used, multiple QCPCurves may share the same data container safely.
 | |
|   Modifying the data in the container will then affect all curves that share the container. Sharing
 | |
|   can be achieved by simply exchanging the data containers wrapped in shared pointers:
 | |
|   \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcurve-datasharing-1
 | |
|   
 | |
|   If you do not wish to share containers, but create a copy from an existing container, rather use
 | |
|   the \ref QCPDataContainer<DataType>::set method on the curve's data container directly:
 | |
|   \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcurve-datasharing-2
 | |
|   
 | |
|   \see addData
 | |
| */
 | |
| void QCPCurve::setData(QSharedPointer<QCPCurveDataContainer> data)
 | |
| {
 | |
|   mDataContainer = data;
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Replaces the current data with the provided points in \a t, \a keys and \a values. The provided
 | |
|   vectors should have equal length. Else, the number of added points will be the size of the
 | |
|   smallest vector.
 | |
|   
 | |
|   If you can guarantee that the passed data points are sorted by \a t in ascending order, you can
 | |
|   set \a alreadySorted to true, to improve performance by saving a sorting run.
 | |
|   
 | |
|   \see addData
 | |
| */
 | |
| void QCPCurve::setData(const QVector<double> &t, const QVector<double> &keys, const QVector<double> &values, bool alreadySorted)
 | |
| {
 | |
|   mDataContainer->clear();
 | |
|   addData(t, keys, values, alreadySorted);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Replaces the current data with the provided points in \a keys and \a values. The provided vectors
 | |
|   should have equal length. Else, the number of added points will be the size of the smallest
 | |
|   vector.
 | |
|   
 | |
|   The t parameter of each data point will be set to the integer index of the respective key/value
 | |
|   pair.
 | |
|   
 | |
|   \see addData
 | |
| */
 | |
| void QCPCurve::setData(const QVector<double> &keys, const QVector<double> &values)
 | |
| {
 | |
|   mDataContainer->clear();
 | |
|   addData(keys, values);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the visual appearance of single data points in the plot. If set to \ref
 | |
|   QCPScatterStyle::ssNone, no scatter points are drawn (e.g. for line-only plots with appropriate
 | |
|   line style).
 | |
|   
 | |
|   \see QCPScatterStyle, setLineStyle
 | |
| */
 | |
| void QCPCurve::setScatterStyle(const QCPScatterStyle &style)
 | |
| {
 | |
|   mScatterStyle = style;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   If scatters are displayed (scatter style not \ref QCPScatterStyle::ssNone), \a skip number of
 | |
|   scatter points are skipped/not drawn after every drawn scatter point.
 | |
| 
 | |
|   This can be used to make the data appear sparser while for example still having a smooth line,
 | |
|   and to improve performance for very high density plots.
 | |
| 
 | |
|   If \a skip is set to 0 (default), all scatter points are drawn.
 | |
| 
 | |
|   \see setScatterStyle
 | |
| */
 | |
| void QCPCurve::setScatterSkip(int skip)
 | |
| {
 | |
|   mScatterSkip = qMax(0, skip);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets how the single data points are connected in the plot or how they are represented visually
 | |
|   apart from the scatter symbol. For scatter-only plots, set \a style to \ref lsNone and \ref
 | |
|   setScatterStyle to the desired scatter style.
 | |
|   
 | |
|   \see setScatterStyle
 | |
| */
 | |
| void QCPCurve::setLineStyle(QCPCurve::LineStyle style)
 | |
| {
 | |
|   mLineStyle = style;
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Adds the provided points in \a t, \a keys and \a values to the current data. The provided vectors
 | |
|   should have equal length. Else, the number of added points will be the size of the smallest
 | |
|   vector.
 | |
|   
 | |
|   If you can guarantee that the passed data points are sorted by \a keys in ascending order, you
 | |
|   can set \a alreadySorted to true, to improve performance by saving a sorting run.
 | |
|   
 | |
|   Alternatively, you can also access and modify the data directly via the \ref data method, which
 | |
|   returns a pointer to the internal data container.
 | |
| */
 | |
| void QCPCurve::addData(const QVector<double> &t, const QVector<double> &keys, const QVector<double> &values, bool alreadySorted)
 | |
| {
 | |
|   if (t.size() != keys.size() || t.size() != values.size())
 | |
|     qDebug() << Q_FUNC_INFO << "ts, keys and values have different sizes:" << t.size() << keys.size() << values.size();
 | |
|   const int n = qMin(qMin(t.size(), keys.size()), values.size());
 | |
|   QVector<QCPCurveData> tempData(n);
 | |
|   QVector<QCPCurveData>::iterator it = tempData.begin();
 | |
|   const QVector<QCPCurveData>::iterator itEnd = tempData.end();
 | |
|   int i = 0;
 | |
|   while (it != itEnd)
 | |
|   {
 | |
|     it->t = t[i];
 | |
|     it->key = keys[i];
 | |
|     it->value = values[i];
 | |
|     ++it;
 | |
|     ++i;
 | |
|   }
 | |
|   mDataContainer->add(tempData, alreadySorted); // don't modify tempData beyond this to prevent copy on write
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Adds the provided points in \a keys and \a values to the current data. The provided vectors
 | |
|   should have equal length. Else, the number of added points will be the size of the smallest
 | |
|   vector.
 | |
|   
 | |
|   The t parameter of each data point will be set to the integer index of the respective key/value
 | |
|   pair.
 | |
|   
 | |
|   Alternatively, you can also access and modify the data directly via the \ref data method, which
 | |
|   returns a pointer to the internal data container.
 | |
| */
 | |
| void QCPCurve::addData(const QVector<double> &keys, const QVector<double> &values)
 | |
| {
 | |
|   if (keys.size() != values.size())
 | |
|     qDebug() << Q_FUNC_INFO << "keys and values have different sizes:" << keys.size() << values.size();
 | |
|   const int n = qMin(keys.size(), values.size());
 | |
|   double tStart;
 | |
|   if (!mDataContainer->isEmpty())
 | |
|     tStart = (mDataContainer->constEnd()-1)->t + 1.0;
 | |
|   else
 | |
|     tStart = 0;
 | |
|   QVector<QCPCurveData> tempData(n);
 | |
|   QVector<QCPCurveData>::iterator it = tempData.begin();
 | |
|   const QVector<QCPCurveData>::iterator itEnd = tempData.end();
 | |
|   int i = 0;
 | |
|   while (it != itEnd)
 | |
|   {
 | |
|     it->t = tStart + i;
 | |
|     it->key = keys[i];
 | |
|     it->value = values[i];
 | |
|     ++it;
 | |
|     ++i;
 | |
|   }
 | |
|   mDataContainer->add(tempData, true); // don't modify tempData beyond this to prevent copy on write
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   Adds the provided data point as \a t, \a key and \a value to the current data.
 | |
|   
 | |
|   Alternatively, you can also access and modify the data directly via the \ref data method, which
 | |
|   returns a pointer to the internal data container.
 | |
| */
 | |
| void QCPCurve::addData(double t, double key, double value)
 | |
| {
 | |
|   mDataContainer->add(QCPCurveData(t, key, value));
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Adds the provided data point as \a key and \a value to the current data.
 | |
|   
 | |
|   The t parameter is generated automatically by increments of 1 for each point, starting at the
 | |
|   highest t of previously existing data or 0, if the curve data is empty.
 | |
|   
 | |
|   Alternatively, you can also access and modify the data directly via the \ref data method, which
 | |
|   returns a pointer to the internal data container.
 | |
| */
 | |
| void QCPCurve::addData(double key, double value)
 | |
| {
 | |
|   if (!mDataContainer->isEmpty())
 | |
|     mDataContainer->add(QCPCurveData((mDataContainer->constEnd()-1)->t + 1.0, key, value));
 | |
|   else
 | |
|     mDataContainer->add(QCPCurveData(0.0, key, value));
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| double QCPCurve::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
 | |
| {
 | |
|   if ((onlySelectable && mSelectable == QCP::stNone) || mDataContainer->isEmpty())
 | |
|     return -1;
 | |
|   if (!mKeyAxis || !mValueAxis)
 | |
|     return -1;
 | |
|   
 | |
|   if (mKeyAxis.data()->axisRect()->rect().contains(pos.toPoint()))
 | |
|   {
 | |
|     QCPCurveDataContainer::const_iterator closestDataPoint = mDataContainer->constEnd();
 | |
|     double result = pointDistance(pos, closestDataPoint);
 | |
|     if (details)
 | |
|     {
 | |
|       int pointIndex = closestDataPoint-mDataContainer->constBegin();
 | |
|       details->setValue(QCPDataSelection(QCPDataRange(pointIndex, pointIndex+1)));
 | |
|     }
 | |
|     return result;
 | |
|   } else
 | |
|     return -1;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| QCPRange QCPCurve::getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain) const
 | |
| {
 | |
|   return mDataContainer->keyRange(foundRange, inSignDomain);
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| QCPRange QCPCurve::getValueRange(bool &foundRange, QCP::SignDomain inSignDomain, const QCPRange &inKeyRange) const
 | |
| {
 | |
|   return mDataContainer->valueRange(foundRange, inSignDomain, inKeyRange);
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPCurve::draw(QCPPainter *painter)
 | |
| {
 | |
|   if (mDataContainer->isEmpty()) return;
 | |
|   
 | |
|   // allocate line vector:
 | |
|   QVector<QPointF> lines, scatters;
 | |
|   
 | |
|   // loop over and draw segments of unselected/selected data:
 | |
|   QList<QCPDataRange> selectedSegments, unselectedSegments, allSegments;
 | |
|   getDataSegments(selectedSegments, unselectedSegments);
 | |
|   allSegments << unselectedSegments << selectedSegments;
 | |
|   for (int i=0; i<allSegments.size(); ++i)
 | |
|   {
 | |
|     bool isSelectedSegment = i >= unselectedSegments.size();
 | |
|     
 | |
|     // fill with curve data:
 | |
|     QPen finalCurvePen = mPen; // determine the final pen already here, because the line optimization depends on its stroke width
 | |
|     if (isSelectedSegment && mSelectionDecorator)
 | |
|       finalCurvePen = mSelectionDecorator->pen();
 | |
|     
 | |
|     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)
 | |
|     getCurveLines(&lines, lineDataRange, finalCurvePen.widthF());
 | |
|     
 | |
|     // check data validity if flag set:
 | |
|   #ifdef QCUSTOMPLOT_CHECK_DATA
 | |
|     for (QCPCurveDataContainer::const_iterator it = mDataContainer->constBegin(); it != mDataContainer->constEnd(); ++it)
 | |
|     {
 | |
|       if (QCP::isInvalidData(it->t) ||
 | |
|           QCP::isInvalidData(it->key, it->value))
 | |
|         qDebug() << Q_FUNC_INFO << "Data point at" << it->key << "invalid." << "Plottable name:" << name();
 | |
|     }
 | |
|   #endif
 | |
|     
 | |
|     // draw curve fill:
 | |
|     applyFillAntialiasingHint(painter);
 | |
|     if (isSelectedSegment && mSelectionDecorator)
 | |
|       mSelectionDecorator->applyBrush(painter);
 | |
|     else
 | |
|       painter->setBrush(mBrush);
 | |
|     painter->setPen(Qt::NoPen);
 | |
|     if (painter->brush().style() != Qt::NoBrush && painter->brush().color().alpha() != 0)
 | |
|       painter->drawPolygon(QPolygonF(lines));
 | |
|     
 | |
|     // draw curve line:
 | |
|     if (mLineStyle != lsNone)
 | |
|     {
 | |
|       painter->setPen(finalCurvePen);
 | |
|       painter->setBrush(Qt::NoBrush);
 | |
|       drawCurveLine(painter, lines);
 | |
|     }
 | |
|     
 | |
|     // draw scatters:
 | |
|     QCPScatterStyle finalScatterStyle = mScatterStyle;
 | |
|     if (isSelectedSegment && mSelectionDecorator)
 | |
|       finalScatterStyle = mSelectionDecorator->getFinalScatterStyle(mScatterStyle);
 | |
|     if (!finalScatterStyle.isNone())
 | |
|     {
 | |
|       getScatters(&scatters, allSegments.at(i), finalScatterStyle.size());
 | |
|       drawScatterPlot(painter, scatters, finalScatterStyle);
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   // draw other selection decoration that isn't just line/scatter pens and brushes:
 | |
|   if (mSelectionDecorator)
 | |
|     mSelectionDecorator->drawDecoration(painter, selection());
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPCurve::drawLegendIcon(QCPPainter *painter, const QRectF &rect) const
 | |
| {
 | |
|   // draw fill:
 | |
|   if (mBrush.style() != Qt::NoBrush)
 | |
|   {
 | |
|     applyFillAntialiasingHint(painter);
 | |
|     painter->fillRect(QRectF(rect.left(), rect.top()+rect.height()/2.0, rect.width(), rect.height()/3.0), mBrush);
 | |
|   }
 | |
|   // draw line vertically centered:
 | |
|   if (mLineStyle != lsNone)
 | |
|   {
 | |
|     applyDefaultAntialiasingHint(painter);
 | |
|     painter->setPen(mPen);
 | |
|     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
 | |
|   }
 | |
|   // draw scatter symbol:
 | |
|   if (!mScatterStyle.isNone())
 | |
|   {
 | |
|     applyScattersAntialiasingHint(painter);
 | |
|     // scale scatter pixmap if it's too large to fit in legend icon rect:
 | |
|     if (mScatterStyle.shape() == QCPScatterStyle::ssPixmap && (mScatterStyle.pixmap().size().width() > rect.width() || mScatterStyle.pixmap().size().height() > rect.height()))
 | |
|     {
 | |
|       QCPScatterStyle scaledStyle(mScatterStyle);
 | |
|       scaledStyle.setPixmap(scaledStyle.pixmap().scaled(rect.size().toSize(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
 | |
|       scaledStyle.applyTo(painter, mPen);
 | |
|       scaledStyle.drawShape(painter, QRectF(rect).center());
 | |
|     } else
 | |
|     {
 | |
|       mScatterStyle.applyTo(painter, mPen);
 | |
|       mScatterStyle.drawShape(painter, QRectF(rect).center());
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!  \internal
 | |
| 
 | |
|   Draws lines between the points in \a lines, given in pixel coordinates.
 | |
| 
 | |
|   \see drawScatterPlot, getCurveLines
 | |
| */
 | |
| void QCPCurve::drawCurveLine(QCPPainter *painter, const QVector<QPointF> &lines) const
 | |
| {
 | |
|   if (painter->pen().style() != Qt::NoPen && painter->pen().color().alpha() != 0)
 | |
|   {
 | |
|     applyDefaultAntialiasingHint(painter);
 | |
|     drawPolyline(painter, lines);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Draws scatter symbols at every point passed in \a points, given in pixel coordinates. The
 | |
|   scatters will be drawn with \a painter and have the appearance as specified in \a style.
 | |
| 
 | |
|   \see drawCurveLine, getCurveLines
 | |
| */
 | |
| void QCPCurve::drawScatterPlot(QCPPainter *painter, const QVector<QPointF> &points, const QCPScatterStyle &style) const
 | |
| {
 | |
|   // draw scatter point symbols:
 | |
|   applyScattersAntialiasingHint(painter);
 | |
|   style.applyTo(painter, mPen);
 | |
|   for (int i=0; i<points.size(); ++i)
 | |
|     if (!qIsNaN(points.at(i).x()) && !qIsNaN(points.at(i).y()))
 | |
|       style.drawShape(painter,  points.at(i));
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Called by \ref draw to generate points in pixel coordinates which represent the line of the
 | |
|   curve.
 | |
| 
 | |
|   Line segments that aren't visible in the current axis rect are handled in an optimized way. They
 | |
|   are projected onto a rectangle slightly larger than the visible axis rect and simplified
 | |
|   regarding point count. The algorithm makes sure to preserve appearance of lines and fills inside
 | |
|   the visible axis rect by generating new temporary points on the outer rect if necessary.
 | |
| 
 | |
|   \a lines will be filled with points in pixel coordinates, that can be drawn with \ref
 | |
|   drawCurveLine.
 | |
| 
 | |
|   \a dataRange specifies the beginning and ending data indices that will be taken into account for
 | |
|   conversion. In this function, the specified range may exceed the total data bounds without harm:
 | |
|   a correspondingly trimmed data range will be used. This takes the burden off the user of this
 | |
|   function to check for valid indices in \a dataRange, e.g. when extending ranges coming from \ref
 | |
|   getDataSegments.
 | |
| 
 | |
|   \a penWidth specifies the pen width that will be used to later draw the lines generated by this
 | |
|   function. This is needed here to calculate an accordingly wider margin around the axis rect when
 | |
|   performing the line optimization.
 | |
| 
 | |
|   Methods that are also involved in the algorithm are: \ref getRegion, \ref getOptimizedPoint, \ref
 | |
|   getOptimizedCornerPoints \ref mayTraverse, \ref getTraverse, \ref getTraverseCornerPoints.
 | |
| 
 | |
|   \see drawCurveLine, drawScatterPlot
 | |
| */
 | |
| void QCPCurve::getCurveLines(QVector<QPointF> *lines, const QCPDataRange &dataRange, double penWidth) const
 | |
| {
 | |
|   if (!lines) return;
 | |
|   lines->clear();
 | |
|   QCPAxis *keyAxis = mKeyAxis.data();
 | |
|   QCPAxis *valueAxis = mValueAxis.data();
 | |
|   if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
 | |
|   
 | |
|   // add margins to rect to compensate for stroke width
 | |
|   const double strokeMargin = qMax(qreal(1.0), qreal(penWidth*0.75)); // stroke radius + 50% safety
 | |
|   const double keyMin = keyAxis->pixelToCoord(keyAxis->coordToPixel(keyAxis->range().lower)-strokeMargin*keyAxis->pixelOrientation());
 | |
|   const double keyMax = keyAxis->pixelToCoord(keyAxis->coordToPixel(keyAxis->range().upper)+strokeMargin*keyAxis->pixelOrientation());
 | |
|   const double valueMin = valueAxis->pixelToCoord(valueAxis->coordToPixel(valueAxis->range().lower)-strokeMargin*valueAxis->pixelOrientation());
 | |
|   const double valueMax = valueAxis->pixelToCoord(valueAxis->coordToPixel(valueAxis->range().upper)+strokeMargin*valueAxis->pixelOrientation());
 | |
|   QCPCurveDataContainer::const_iterator itBegin = mDataContainer->constBegin();
 | |
|   QCPCurveDataContainer::const_iterator itEnd = mDataContainer->constEnd();
 | |
|   mDataContainer->limitIteratorsToDataRange(itBegin, itEnd, dataRange);
 | |
|   if (itBegin == itEnd)
 | |
|     return;
 | |
|   QCPCurveDataContainer::const_iterator it = itBegin;
 | |
|   QCPCurveDataContainer::const_iterator prevIt = itEnd-1;
 | |
|   int prevRegion = getRegion(prevIt->key, prevIt->value, keyMin, valueMax, keyMax, valueMin);
 | |
|   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)
 | |
|   while (it != itEnd)
 | |
|   {
 | |
|     const int currentRegion = getRegion(it->key, it->value, keyMin, valueMax, keyMax, valueMin);
 | |
|     if (currentRegion != prevRegion) // changed region, possibly need to add some optimized edge points or original points if entering R
 | |
|     {
 | |
|       if (currentRegion != 5) // segment doesn't end in R, so it's a candidate for removal
 | |
|       {
 | |
|         QPointF crossA, crossB;
 | |
|         if (prevRegion == 5) // we're coming from R, so add this point optimized
 | |
|         {
 | |
|           lines->append(getOptimizedPoint(currentRegion, it->key, it->value, prevIt->key, prevIt->value, keyMin, valueMax, keyMax, valueMin));
 | |
|           // 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
 | |
|           *lines << getOptimizedCornerPoints(prevRegion, currentRegion, prevIt->key, prevIt->value, it->key, it->value, keyMin, valueMax, keyMax, valueMin);
 | |
|         } else if (mayTraverse(prevRegion, currentRegion) &&
 | |
|                    getTraverse(prevIt->key, prevIt->value, it->key, it->value, keyMin, valueMax, keyMax, valueMin, crossA, crossB))
 | |
|         {
 | |
|           // add the two cross points optimized if segment crosses R and if segment isn't virtual zeroth segment between last and first curve point:
 | |
|           QVector<QPointF> beforeTraverseCornerPoints, afterTraverseCornerPoints;
 | |
|           getTraverseCornerPoints(prevRegion, currentRegion, keyMin, valueMax, keyMax, valueMin, beforeTraverseCornerPoints, afterTraverseCornerPoints);
 | |
|           if (it != itBegin)
 | |
|           {
 | |
|             *lines << beforeTraverseCornerPoints;
 | |
|             lines->append(crossA);
 | |
|             lines->append(crossB);
 | |
|             *lines << afterTraverseCornerPoints;
 | |
|           } else
 | |
|           {
 | |
|             lines->append(crossB);
 | |
|             *lines << afterTraverseCornerPoints;
 | |
|             trailingPoints << beforeTraverseCornerPoints << crossA ;
 | |
|           }
 | |
|         } 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)
 | |
|         {
 | |
|           *lines << getOptimizedCornerPoints(prevRegion, currentRegion, prevIt->key, prevIt->value, it->key, it->value, keyMin, valueMax, keyMax, valueMin);
 | |
|         }
 | |
|       } else // segment does end in R, so we add previous point optimized and this point at original position
 | |
|       {
 | |
|         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
 | |
|           trailingPoints << getOptimizedPoint(prevRegion, prevIt->key, prevIt->value, it->key, it->value, keyMin, valueMax, keyMax, valueMin);
 | |
|         else
 | |
|           lines->append(getOptimizedPoint(prevRegion, prevIt->key, prevIt->value, it->key, it->value, keyMin, valueMax, keyMax, valueMin));
 | |
|         lines->append(coordsToPixels(it->key, it->value));
 | |
|       }
 | |
|     } else // region didn't change
 | |
|     {
 | |
|       if (currentRegion == 5) // still in R, keep adding original points
 | |
|       {
 | |
|         lines->append(coordsToPixels(it->key, it->value));
 | |
|       } else // still outside R, no need to add anything
 | |
|       {
 | |
|         // see how this is not doing anything? That's the main optimization...
 | |
|       }
 | |
|     }
 | |
|     prevIt = it;
 | |
|     prevRegion = currentRegion;
 | |
|     ++it;
 | |
|   }
 | |
|   *lines << trailingPoints;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Called by \ref draw to generate points in pixel coordinates which represent the scatters of the
 | |
|   curve. If a scatter skip is configured (\ref setScatterSkip), the returned points are accordingly
 | |
|   sparser.
 | |
| 
 | |
|   Scatters that aren't visible in the current axis rect are optimized away.
 | |
| 
 | |
|   \a scatters will be filled with points in pixel coordinates, that can be drawn with \ref
 | |
|   drawScatterPlot.
 | |
| 
 | |
|   \a dataRange specifies the beginning and ending data indices that will be taken into account for
 | |
|   conversion.
 | |
| 
 | |
|   \a scatterWidth specifies the scatter width that will be used to later draw the scatters at pixel
 | |
|   coordinates generated by this function. This is needed here to calculate an accordingly wider
 | |
|   margin around the axis rect when performing the data point reduction.
 | |
| 
 | |
|   \see draw, drawScatterPlot
 | |
| */
 | |
| void QCPCurve::getScatters(QVector<QPointF> *scatters, const QCPDataRange &dataRange, double scatterWidth) const
 | |
| {
 | |
|   if (!scatters) return;
 | |
|   scatters->clear();
 | |
|   QCPAxis *keyAxis = mKeyAxis.data();
 | |
|   QCPAxis *valueAxis = mValueAxis.data();
 | |
|   if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
 | |
|   
 | |
|   QCPCurveDataContainer::const_iterator begin = mDataContainer->constBegin();
 | |
|   QCPCurveDataContainer::const_iterator end = mDataContainer->constEnd();
 | |
|   mDataContainer->limitIteratorsToDataRange(begin, end, dataRange);
 | |
|   if (begin == end)
 | |
|     return;
 | |
|   const int scatterModulo = mScatterSkip+1;
 | |
|   const bool doScatterSkip = mScatterSkip > 0;
 | |
|   int endIndex = end-mDataContainer->constBegin();
 | |
|   
 | |
|   QCPRange keyRange = keyAxis->range();
 | |
|   QCPRange valueRange = valueAxis->range();
 | |
|   // extend range to include width of scatter symbols:
 | |
|   keyRange.lower = keyAxis->pixelToCoord(keyAxis->coordToPixel(keyRange.lower)-scatterWidth*keyAxis->pixelOrientation());
 | |
|   keyRange.upper = keyAxis->pixelToCoord(keyAxis->coordToPixel(keyRange.upper)+scatterWidth*keyAxis->pixelOrientation());
 | |
|   valueRange.lower = valueAxis->pixelToCoord(valueAxis->coordToPixel(valueRange.lower)-scatterWidth*valueAxis->pixelOrientation());
 | |
|   valueRange.upper = valueAxis->pixelToCoord(valueAxis->coordToPixel(valueRange.upper)+scatterWidth*valueAxis->pixelOrientation());
 | |
|   
 | |
|   QCPCurveDataContainer::const_iterator it = begin;
 | |
|   int itIndex = begin-mDataContainer->constBegin();
 | |
|   while (doScatterSkip && it != end && itIndex % scatterModulo != 0) // advance begin iterator to first non-skipped scatter
 | |
|   {
 | |
|     ++itIndex;
 | |
|     ++it;
 | |
|   }
 | |
|   if (keyAxis->orientation() == Qt::Vertical)
 | |
|   {
 | |
|     while (it != end)
 | |
|     {
 | |
|       if (!qIsNaN(it->value) && keyRange.contains(it->key) && valueRange.contains(it->value))
 | |
|         scatters->append(QPointF(valueAxis->coordToPixel(it->value), keyAxis->coordToPixel(it->key)));
 | |
|       
 | |
|       // advance iterator to next (non-skipped) data point:
 | |
|       if (!doScatterSkip)
 | |
|         ++it;
 | |
|       else
 | |
|       {
 | |
|         itIndex += scatterModulo;
 | |
|         if (itIndex < endIndex) // make sure we didn't jump over end
 | |
|           it += scatterModulo;
 | |
|         else
 | |
|         {
 | |
|           it = end;
 | |
|           itIndex = endIndex;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   } else
 | |
|   {
 | |
|     while (it != end)
 | |
|     {
 | |
|       if (!qIsNaN(it->value) && keyRange.contains(it->key) && valueRange.contains(it->value))
 | |
|         scatters->append(QPointF(keyAxis->coordToPixel(it->key), valueAxis->coordToPixel(it->value)));
 | |
|       
 | |
|       // advance iterator to next (non-skipped) data point:
 | |
|       if (!doScatterSkip)
 | |
|         ++it;
 | |
|       else
 | |
|       {
 | |
|         itIndex += scatterModulo;
 | |
|         if (itIndex < endIndex) // make sure we didn't jump over end
 | |
|           it += scatterModulo;
 | |
|         else
 | |
|         {
 | |
|           it = end;
 | |
|           itIndex = endIndex;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   This function is part of the curve optimization algorithm of \ref getCurveLines.
 | |
| 
 | |
|   It returns the region of the given point (\a key, \a value) with respect to a rectangle defined
 | |
|   by \a keyMin, \a keyMax, \a valueMin, and \a valueMax.
 | |
| 
 | |
|   The regions are enumerated from top to bottom (\a valueMin to \a valueMax) and left to right (\a
 | |
|   keyMin to \a keyMax):
 | |
| 
 | |
|   <table style="width:10em; text-align:center">
 | |
|     <tr><td>1</td><td>4</td><td>7</td></tr>
 | |
|     <tr><td>2</td><td style="border:1px solid black">5</td><td>8</td></tr>
 | |
|     <tr><td>3</td><td>6</td><td>9</td></tr>
 | |
|   </table>
 | |
| 
 | |
|   With the rectangle being region 5, and the outer regions extending infinitely outwards. In the
 | |
|   curve optimization algorithm, region 5 is considered to be the visible portion of the plot.
 | |
| */
 | |
| int QCPCurve::getRegion(double key, double value, double keyMin, double valueMax, double keyMax, double valueMin) const
 | |
| {
 | |
|   if (key < keyMin) // region 123
 | |
|   {
 | |
|     if (value > valueMax)
 | |
|       return 1;
 | |
|     else if (value < valueMin)
 | |
|       return 3;
 | |
|     else
 | |
|       return 2;
 | |
|   } else if (key > keyMax) // region 789
 | |
|   {
 | |
|     if (value > valueMax)
 | |
|       return 7;
 | |
|     else if (value < valueMin)
 | |
|       return 9;
 | |
|     else
 | |
|       return 8;
 | |
|   } else // region 456
 | |
|   {
 | |
|     if (value > valueMax)
 | |
|       return 4;
 | |
|     else if (value < valueMin)
 | |
|       return 6;
 | |
|     else
 | |
|       return 5;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   This function is part of the curve optimization algorithm of \ref getCurveLines.
 | |
|   
 | |
|   This method is used in case the current segment passes from inside the visible rect (region 5,
 | |
|   see \ref getRegion) to any of the outer regions (\a otherRegion). The current segment is given by
 | |
|   the line connecting (\a key, \a value) with (\a otherKey, \a otherValue).
 | |
|   
 | |
|   It returns the intersection point of the segment with the border of region 5.
 | |
|   
 | |
|   For this function it doesn't matter whether (\a key, \a value) is the point inside region 5 or
 | |
|   whether it's (\a otherKey, \a otherValue), i.e. whether the segment is coming from region 5 or
 | |
|   leaving it. It is important though that \a otherRegion correctly identifies the other region not
 | |
|   equal to 5.
 | |
| */
 | |
| QPointF QCPCurve::getOptimizedPoint(int otherRegion, double otherKey, double otherValue, double key, double value, double keyMin, double valueMax, double keyMax, double valueMin) const
 | |
| {
 | |
|   double intersectKey = keyMin; // initial value is just fail-safe
 | |
|   double intersectValue = valueMax; // initial value is just fail-safe
 | |
|   switch (otherRegion)
 | |
|   {
 | |
|     case 1: // top and left edge
 | |
|     {
 | |
|       intersectValue = valueMax;
 | |
|       intersectKey = otherKey + (key-otherKey)/(value-otherValue)*(intersectValue-otherValue);
 | |
|       if (intersectKey < keyMin || intersectKey > keyMax) // doesn't intersect, so must intersect other:
 | |
|       {
 | |
|         intersectKey = keyMin;
 | |
|         intersectValue = otherValue + (value-otherValue)/(key-otherKey)*(intersectKey-otherKey);
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     case 2: // left edge
 | |
|     {
 | |
|       intersectKey = keyMin;
 | |
|       intersectValue = otherValue + (value-otherValue)/(key-otherKey)*(intersectKey-otherKey);
 | |
|       break;
 | |
|     }
 | |
|     case 3: // bottom and left edge
 | |
|     {
 | |
|       intersectValue = valueMin;
 | |
|       intersectKey = otherKey + (key-otherKey)/(value-otherValue)*(intersectValue-otherValue);
 | |
|       if (intersectKey < keyMin || intersectKey > keyMax) // doesn't intersect, so must intersect other:
 | |
|       {
 | |
|         intersectKey = keyMin;
 | |
|         intersectValue = otherValue + (value-otherValue)/(key-otherKey)*(intersectKey-otherKey);
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     case 4: // top edge
 | |
|     {
 | |
|       intersectValue = valueMax;
 | |
|       intersectKey = otherKey + (key-otherKey)/(value-otherValue)*(intersectValue-otherValue);
 | |
|       break;
 | |
|     }
 | |
|     case 5:
 | |
|     {
 | |
|       break; // case 5 shouldn't happen for this function but we add it anyway to prevent potential discontinuity in branch table
 | |
|     }
 | |
|     case 6: // bottom edge
 | |
|     {
 | |
|       intersectValue = valueMin;
 | |
|       intersectKey = otherKey + (key-otherKey)/(value-otherValue)*(intersectValue-otherValue);
 | |
|       break;
 | |
|     }
 | |
|     case 7: // top and right edge
 | |
|     {
 | |
|       intersectValue = valueMax;
 | |
|       intersectKey = otherKey + (key-otherKey)/(value-otherValue)*(intersectValue-otherValue);
 | |
|       if (intersectKey < keyMin || intersectKey > keyMax) // doesn't intersect, so must intersect other:
 | |
|       {
 | |
|         intersectKey = keyMax;
 | |
|         intersectValue = otherValue + (value-otherValue)/(key-otherKey)*(intersectKey-otherKey);
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     case 8: // right edge
 | |
|     {
 | |
|       intersectKey = keyMax;
 | |
|       intersectValue = otherValue + (value-otherValue)/(key-otherKey)*(intersectKey-otherKey);
 | |
|       break;
 | |
|     }
 | |
|     case 9: // bottom and right edge
 | |
|     {
 | |
|       intersectValue = valueMin;
 | |
|       intersectKey = otherKey + (key-otherKey)/(value-otherValue)*(intersectValue-otherValue);
 | |
|       if (intersectKey < keyMin || intersectKey > keyMax) // doesn't intersect, so must intersect other:
 | |
|       {
 | |
|         intersectKey = keyMax;
 | |
|         intersectValue = otherValue + (value-otherValue)/(key-otherKey)*(intersectKey-otherKey);
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
|   return coordsToPixels(intersectKey, intersectValue);
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   This function is part of the curve optimization algorithm of \ref getCurveLines.
 | |
|   
 | |
|   In situations where a single segment skips over multiple regions it might become necessary to add
 | |
|   extra points at the corners of region 5 (see \ref getRegion) such that the optimized segment
 | |
|   doesn't unintentionally cut through the visible area of the axis rect and create plot artifacts.
 | |
|   This method provides these points that must be added, assuming the original segment doesn't
 | |
|   start, end, or traverse region 5. (Corner points where region 5 is traversed are calculated by
 | |
|   \ref getTraverseCornerPoints.)
 | |
|   
 | |
|   For example, consider a segment which directly goes from region 4 to 2 but originally is far out
 | |
|   to the top left such that it doesn't cross region 5. Naively optimizing these points by
 | |
|   projecting them on the top and left borders of region 5 will create a segment that surely crosses
 | |
|   5, creating a visual artifact in the plot. This method prevents this by providing extra points at
 | |
|   the top left corner, making the optimized curve correctly pass from region 4 to 1 to 2 without
 | |
|   traversing 5.
 | |
| */
 | |
| QVector<QPointF> QCPCurve::getOptimizedCornerPoints(int prevRegion, int currentRegion, double prevKey, double prevValue, double key, double value, double keyMin, double valueMax, double keyMax, double valueMin) const
 | |
| {
 | |
|   QVector<QPointF> result;
 | |
|   switch (prevRegion)
 | |
|   {
 | |
|     case 1:
 | |
|     {
 | |
|       switch (currentRegion)
 | |
|       {
 | |
|         case 2: { result << coordsToPixels(keyMin, valueMax); break; }
 | |
|         case 4: { result << coordsToPixels(keyMin, valueMax); break; }
 | |
|         case 3: { result << coordsToPixels(keyMin, valueMax) << coordsToPixels(keyMin, valueMin); break; }
 | |
|         case 7: { result << coordsToPixels(keyMin, valueMax) << coordsToPixels(keyMax, valueMax); break; }
 | |
|         case 6: { result << coordsToPixels(keyMin, valueMax) << coordsToPixels(keyMin, valueMin); result.append(result.last()); break; }
 | |
|         case 8: { result << coordsToPixels(keyMin, valueMax) << coordsToPixels(keyMax, valueMax); result.append(result.last()); break; }
 | |
|         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
 | |
|           if ((value-prevValue)/(key-prevKey)*(keyMin-key)+value < valueMin) // segment passes below R
 | |
|           { result << coordsToPixels(keyMin, valueMax) << coordsToPixels(keyMin, valueMin); result.append(result.last()); result << coordsToPixels(keyMax, valueMin); }
 | |
|           else
 | |
|           { result << coordsToPixels(keyMin, valueMax) << coordsToPixels(keyMax, valueMax); result.append(result.last()); result << coordsToPixels(keyMax, valueMin); }
 | |
|           break;
 | |
|         }
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     case 2:
 | |
|     {
 | |
|       switch (currentRegion)
 | |
|       {
 | |
|         case 1: { result << coordsToPixels(keyMin, valueMax); break; }
 | |
|         case 3: { result << coordsToPixels(keyMin, valueMin); break; }
 | |
|         case 4: { result << coordsToPixels(keyMin, valueMax); result.append(result.last()); break; }
 | |
|         case 6: { result << coordsToPixels(keyMin, valueMin); result.append(result.last()); break; }
 | |
|         case 7: { result << coordsToPixels(keyMin, valueMax); result.append(result.last()); result << coordsToPixels(keyMax, valueMax); break; }
 | |
|         case 9: { result << coordsToPixels(keyMin, valueMin); result.append(result.last()); result << coordsToPixels(keyMax, valueMin); break; }
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     case 3:
 | |
|     {
 | |
|       switch (currentRegion)
 | |
|       {
 | |
|         case 2: { result << coordsToPixels(keyMin, valueMin); break; }
 | |
|         case 6: { result << coordsToPixels(keyMin, valueMin); break; }
 | |
|         case 1: { result << coordsToPixels(keyMin, valueMin) << coordsToPixels(keyMin, valueMax); break; }
 | |
|         case 9: { result << coordsToPixels(keyMin, valueMin) << coordsToPixels(keyMax, valueMin); break; }
 | |
|         case 4: { result << coordsToPixels(keyMin, valueMin) << coordsToPixels(keyMin, valueMax); result.append(result.last()); break; }
 | |
|         case 8: { result << coordsToPixels(keyMin, valueMin) << coordsToPixels(keyMax, valueMin); result.append(result.last()); break; }
 | |
|         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
 | |
|           if ((value-prevValue)/(key-prevKey)*(keyMax-key)+value < valueMin) // segment passes below R
 | |
|           { result << coordsToPixels(keyMin, valueMin) << coordsToPixels(keyMax, valueMin); result.append(result.last()); result << coordsToPixels(keyMax, valueMax); }
 | |
|           else
 | |
|           { result << coordsToPixels(keyMin, valueMin) << coordsToPixels(keyMin, valueMax); result.append(result.last()); result << coordsToPixels(keyMax, valueMax); }
 | |
|           break;
 | |
|         }
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     case 4:
 | |
|     {
 | |
|       switch (currentRegion)
 | |
|       {
 | |
|         case 1: { result << coordsToPixels(keyMin, valueMax); break; }
 | |
|         case 7: { result << coordsToPixels(keyMax, valueMax); break; }
 | |
|         case 2: { result << coordsToPixels(keyMin, valueMax); result.append(result.last()); break; }
 | |
|         case 8: { result << coordsToPixels(keyMax, valueMax); result.append(result.last()); break; }
 | |
|         case 3: { result << coordsToPixels(keyMin, valueMax); result.append(result.last()); result << coordsToPixels(keyMin, valueMin); break; }
 | |
|         case 9: { result << coordsToPixels(keyMax, valueMax); result.append(result.last()); result << coordsToPixels(keyMax, valueMin); break; }
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     case 5:
 | |
|     {
 | |
|       switch (currentRegion)
 | |
|       {
 | |
|         case 1: { result << coordsToPixels(keyMin, valueMax); break; }
 | |
|         case 7: { result << coordsToPixels(keyMax, valueMax); break; }
 | |
|         case 9: { result << coordsToPixels(keyMax, valueMin); break; }
 | |
|         case 3: { result << coordsToPixels(keyMin, valueMin); break; }
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     case 6:
 | |
|     {
 | |
|       switch (currentRegion)
 | |
|       {
 | |
|         case 3: { result << coordsToPixels(keyMin, valueMin); break; }
 | |
|         case 9: { result << coordsToPixels(keyMax, valueMin); break; }
 | |
|         case 2: { result << coordsToPixels(keyMin, valueMin); result.append(result.last()); break; }
 | |
|         case 8: { result << coordsToPixels(keyMax, valueMin); result.append(result.last()); break; }
 | |
|         case 1: { result << coordsToPixels(keyMin, valueMin); result.append(result.last()); result << coordsToPixels(keyMin, valueMax); break; }
 | |
|         case 7: { result << coordsToPixels(keyMax, valueMin); result.append(result.last()); result << coordsToPixels(keyMax, valueMax); break; }
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     case 7:
 | |
|     {
 | |
|       switch (currentRegion)
 | |
|       {
 | |
|         case 4: { result << coordsToPixels(keyMax, valueMax); break; }
 | |
|         case 8: { result << coordsToPixels(keyMax, valueMax); break; }
 | |
|         case 1: { result << coordsToPixels(keyMax, valueMax) << coordsToPixels(keyMin, valueMax); break; }
 | |
|         case 9: { result << coordsToPixels(keyMax, valueMax) << coordsToPixels(keyMax, valueMin); break; }
 | |
|         case 2: { result << coordsToPixels(keyMax, valueMax) << coordsToPixels(keyMin, valueMax); result.append(result.last()); break; }
 | |
|         case 6: { result << coordsToPixels(keyMax, valueMax) << coordsToPixels(keyMax, valueMin); result.append(result.last()); break; }
 | |
|         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
 | |
|           if ((value-prevValue)/(key-prevKey)*(keyMax-key)+value < valueMin) // segment passes below R
 | |
|           { result << coordsToPixels(keyMax, valueMax) << coordsToPixels(keyMax, valueMin); result.append(result.last()); result << coordsToPixels(keyMin, valueMin); }
 | |
|           else
 | |
|           { result << coordsToPixels(keyMax, valueMax) << coordsToPixels(keyMin, valueMax); result.append(result.last()); result << coordsToPixels(keyMin, valueMin); }
 | |
|           break;
 | |
|         }
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     case 8:
 | |
|     {
 | |
|       switch (currentRegion)
 | |
|       {
 | |
|         case 7: { result << coordsToPixels(keyMax, valueMax); break; }
 | |
|         case 9: { result << coordsToPixels(keyMax, valueMin); break; }
 | |
|         case 4: { result << coordsToPixels(keyMax, valueMax); result.append(result.last()); break; }
 | |
|         case 6: { result << coordsToPixels(keyMax, valueMin); result.append(result.last()); break; }
 | |
|         case 1: { result << coordsToPixels(keyMax, valueMax); result.append(result.last()); result << coordsToPixels(keyMin, valueMax); break; }
 | |
|         case 3: { result << coordsToPixels(keyMax, valueMin); result.append(result.last()); result << coordsToPixels(keyMin, valueMin); break; }
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     case 9:
 | |
|     {
 | |
|       switch (currentRegion)
 | |
|       {
 | |
|         case 6: { result << coordsToPixels(keyMax, valueMin); break; }
 | |
|         case 8: { result << coordsToPixels(keyMax, valueMin); break; }
 | |
|         case 3: { result << coordsToPixels(keyMax, valueMin) << coordsToPixels(keyMin, valueMin); break; }
 | |
|         case 7: { result << coordsToPixels(keyMax, valueMin) << coordsToPixels(keyMax, valueMax); break; }
 | |
|         case 2: { result << coordsToPixels(keyMax, valueMin) << coordsToPixels(keyMin, valueMin); result.append(result.last()); break; }
 | |
|         case 4: { result << coordsToPixels(keyMax, valueMin) << coordsToPixels(keyMax, valueMax); result.append(result.last()); break; }
 | |
|         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
 | |
|           if ((value-prevValue)/(key-prevKey)*(keyMin-key)+value < valueMin) // segment passes below R
 | |
|           { result << coordsToPixels(keyMax, valueMin) << coordsToPixels(keyMin, valueMin); result.append(result.last()); result << coordsToPixels(keyMin, valueMax); }
 | |
|           else
 | |
|           { result << coordsToPixels(keyMax, valueMin) << coordsToPixels(keyMax, valueMax); result.append(result.last()); result << coordsToPixels(keyMin, valueMax); }
 | |
|           break;
 | |
|         }
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   This function is part of the curve optimization algorithm of \ref getCurveLines.
 | |
|   
 | |
|   This method returns whether a segment going from \a prevRegion to \a currentRegion (see \ref
 | |
|   getRegion) may traverse the visible region 5. This function assumes that neither \a prevRegion
 | |
|   nor \a currentRegion is 5 itself.
 | |
|   
 | |
|   If this method returns false, the segment for sure doesn't pass region 5. If it returns true, the
 | |
|   segment may or may not pass region 5 and a more fine-grained calculation must be used (\ref
 | |
|   getTraverse).
 | |
| */
 | |
| bool QCPCurve::mayTraverse(int prevRegion, int currentRegion) const
 | |
| {
 | |
|   switch (prevRegion)
 | |
|   {
 | |
|     case 1:
 | |
|     {
 | |
|       switch (currentRegion)
 | |
|       {
 | |
|         case 4:
 | |
|         case 7:
 | |
|         case 2:
 | |
|         case 3: return false;
 | |
|         default: return true;
 | |
|       }
 | |
|     }
 | |
|     case 2:
 | |
|     {
 | |
|       switch (currentRegion)
 | |
|       {
 | |
|         case 1:
 | |
|         case 3: return false;
 | |
|         default: return true;
 | |
|       }
 | |
|     }
 | |
|     case 3:
 | |
|     {
 | |
|       switch (currentRegion)
 | |
|       {
 | |
|         case 1:
 | |
|         case 2:
 | |
|         case 6:
 | |
|         case 9: return false;
 | |
|         default: return true;
 | |
|       }
 | |
|     }
 | |
|     case 4:
 | |
|     {
 | |
|       switch (currentRegion)
 | |
|       {
 | |
|         case 1:
 | |
|         case 7: return false;
 | |
|         default: return true;
 | |
|       }
 | |
|     }
 | |
|     case 5: return false; // should never occur
 | |
|     case 6:
 | |
|     {
 | |
|       switch (currentRegion)
 | |
|       {
 | |
|         case 3:
 | |
|         case 9: return false;
 | |
|         default: return true;
 | |
|       }
 | |
|     }
 | |
|     case 7:
 | |
|     {
 | |
|       switch (currentRegion)
 | |
|       {
 | |
|         case 1:
 | |
|         case 4:
 | |
|         case 8:
 | |
|         case 9: return false;
 | |
|         default: return true;
 | |
|       }
 | |
|     }
 | |
|     case 8:
 | |
|     {
 | |
|       switch (currentRegion)
 | |
|       {
 | |
|         case 7:
 | |
|         case 9: return false;
 | |
|         default: return true;
 | |
|       }
 | |
|     }
 | |
|     case 9:
 | |
|     {
 | |
|       switch (currentRegion)
 | |
|       {
 | |
|         case 3:
 | |
|         case 6:
 | |
|         case 8:
 | |
|         case 7: return false;
 | |
|         default: return true;
 | |
|       }
 | |
|     }
 | |
|     default: return true;
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   This function is part of the curve optimization algorithm of \ref getCurveLines.
 | |
|   
 | |
|   This method assumes that the \ref mayTraverse test has returned true, so there is a chance the
 | |
|   segment defined by (\a prevKey, \a prevValue) and (\a key, \a value) goes through the visible
 | |
|   region 5.
 | |
|   
 | |
|   The return value of this method indicates whether the segment actually traverses region 5 or not.
 | |
|   
 | |
|   If the segment traverses 5, the output parameters \a crossA and \a crossB indicate the entry and
 | |
|   exit points of region 5. They will become the optimized points for that segment.
 | |
| */
 | |
| bool QCPCurve::getTraverse(double prevKey, double prevValue, double key, double value, double keyMin, double valueMax, double keyMax, double valueMin, QPointF &crossA, QPointF &crossB) const
 | |
| {
 | |
|   QList<QPointF> intersections; // x of QPointF corresponds to key and y to value
 | |
|   if (qFuzzyIsNull(key-prevKey)) // line is parallel to value axis
 | |
|   {
 | |
|     // due to region filter in mayTraverseR(), if line is parallel to value or key axis, R is traversed here
 | |
|     intersections.append(QPointF(key, valueMin)); // direction will be taken care of at end of method
 | |
|     intersections.append(QPointF(key, valueMax));
 | |
|   } else if (qFuzzyIsNull(value-prevValue)) // line is parallel to key axis
 | |
|   {
 | |
|     // due to region filter in mayTraverseR(), if line is parallel to value or key axis, R is traversed here
 | |
|     intersections.append(QPointF(keyMin, value)); // direction will be taken care of at end of method
 | |
|     intersections.append(QPointF(keyMax, value));
 | |
|   } else // line is skewed
 | |
|   {
 | |
|     double gamma;
 | |
|     double keyPerValue = (key-prevKey)/(value-prevValue);
 | |
|     // check top of rect:
 | |
|     gamma = prevKey + (valueMax-prevValue)*keyPerValue;
 | |
|     if (gamma >= keyMin && gamma <= keyMax)
 | |
|       intersections.append(QPointF(gamma, valueMax));
 | |
|     // check bottom of rect:
 | |
|     gamma = prevKey + (valueMin-prevValue)*keyPerValue;
 | |
|     if (gamma >= keyMin && gamma <= keyMax)
 | |
|       intersections.append(QPointF(gamma, valueMin));
 | |
|     double valuePerKey = 1.0/keyPerValue;
 | |
|     // check left of rect:
 | |
|     gamma = prevValue + (keyMin-prevKey)*valuePerKey;
 | |
|     if (gamma >= valueMin && gamma <= valueMax)
 | |
|       intersections.append(QPointF(keyMin, gamma));
 | |
|     // check right of rect:
 | |
|     gamma = prevValue + (keyMax-prevKey)*valuePerKey;
 | |
|     if (gamma >= valueMin && gamma <= valueMax)
 | |
|       intersections.append(QPointF(keyMax, gamma));
 | |
|   }
 | |
|   
 | |
|   // handle cases where found points isn't exactly 2:
 | |
|   if (intersections.size() > 2)
 | |
|   {
 | |
|     // line probably goes through corner of rect, and we got duplicate points there. single out the point pair with greatest distance in between:
 | |
|     double distSqrMax = 0;
 | |
|     QPointF pv1, pv2;
 | |
|     for (int i=0; i<intersections.size()-1; ++i)
 | |
|     {
 | |
|       for (int k=i+1; k<intersections.size(); ++k)
 | |
|       {
 | |
|         QPointF distPoint = intersections.at(i)-intersections.at(k);
 | |
|         double distSqr = distPoint.x()*distPoint.x()+distPoint.y()+distPoint.y();
 | |
|         if (distSqr > distSqrMax)
 | |
|         {
 | |
|           pv1 = intersections.at(i);
 | |
|           pv2 = intersections.at(k);
 | |
|           distSqrMax = distSqr;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     intersections = QList<QPointF>() << pv1 << pv2;
 | |
|   } else if (intersections.size() != 2)
 | |
|   {
 | |
|     // one or even zero points found (shouldn't happen unless line perfectly tangent to corner), no need to draw segment
 | |
|     return false;
 | |
|   }
 | |
|   
 | |
|   // possibly re-sort points so optimized point segment has same direction as original segment:
 | |
|   if ((key-prevKey)*(intersections.at(1).x()-intersections.at(0).x()) + (value-prevValue)*(intersections.at(1).y()-intersections.at(0).y()) < 0) // scalar product of both segments < 0 -> opposite direction
 | |
|     intersections.move(0, 1);
 | |
|   crossA = coordsToPixels(intersections.at(0).x(), intersections.at(0).y());
 | |
|   crossB = coordsToPixels(intersections.at(1).x(), intersections.at(1).y());
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   This function is part of the curve optimization algorithm of \ref getCurveLines.
 | |
|   
 | |
|   This method assumes that the \ref getTraverse test has returned true, so the segment definitely
 | |
|   traverses the visible region 5 when going from \a prevRegion to \a currentRegion.
 | |
|   
 | |
|   In certain situations it is not sufficient to merely generate the entry and exit points of the
 | |
|   segment into/out of region 5, as \ref getTraverse provides. It may happen that a single segment, in
 | |
|   addition to traversing region 5, skips another region outside of region 5, which makes it
 | |
|   necessary to add an optimized corner point there (very similar to the job \ref
 | |
|   getOptimizedCornerPoints does for segments that are completely in outside regions and don't
 | |
|   traverse 5).
 | |
|   
 | |
|   As an example, consider a segment going from region 1 to region 6, traversing the lower left
 | |
|   corner of region 5. In this configuration, the segment additionally crosses the border between
 | |
|   region 1 and 2 before entering region 5. This makes it necessary to add an additional point in
 | |
|   the top left corner, before adding the optimized traverse points. So in this case, the output
 | |
|   parameter \a beforeTraverse will contain the top left corner point, and \a afterTraverse will be
 | |
|   empty.
 | |
|   
 | |
|   In some cases, such as when going from region 1 to 9, it may even be necessary to add additional
 | |
|   corner points before and after the traverse. Then both \a beforeTraverse and \a afterTraverse
 | |
|   return the respective corner points.
 | |
| */
 | |
| void QCPCurve::getTraverseCornerPoints(int prevRegion, int currentRegion, double keyMin, double valueMax, double keyMax, double valueMin, QVector<QPointF> &beforeTraverse, QVector<QPointF> &afterTraverse) const
 | |
| {
 | |
|   switch (prevRegion)
 | |
|   {
 | |
|     case 1:
 | |
|     {
 | |
|       switch (currentRegion)
 | |
|       {
 | |
|         case 6: { beforeTraverse << coordsToPixels(keyMin, valueMax); break; }
 | |
|         case 9: { beforeTraverse << coordsToPixels(keyMin, valueMax); afterTraverse << coordsToPixels(keyMax, valueMin); break; }
 | |
|         case 8: { beforeTraverse << coordsToPixels(keyMin, valueMax); break; }
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     case 2:
 | |
|     {
 | |
|       switch (currentRegion)
 | |
|       {
 | |
|         case 7: { afterTraverse << coordsToPixels(keyMax, valueMax); break; }
 | |
|         case 9: { afterTraverse << coordsToPixels(keyMax, valueMin); break; }
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     case 3:
 | |
|     {
 | |
|       switch (currentRegion)
 | |
|       {
 | |
|         case 4: { beforeTraverse << coordsToPixels(keyMin, valueMin); break; }
 | |
|         case 7: { beforeTraverse << coordsToPixels(keyMin, valueMin); afterTraverse << coordsToPixels(keyMax, valueMax); break; }
 | |
|         case 8: { beforeTraverse << coordsToPixels(keyMin, valueMin); break; }
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     case 4:
 | |
|     {
 | |
|       switch (currentRegion)
 | |
|       {
 | |
|         case 3: { afterTraverse << coordsToPixels(keyMin, valueMin); break; }
 | |
|         case 9: { afterTraverse << coordsToPixels(keyMax, valueMin); break; }
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     case 5: { break; } // shouldn't happen because this method only handles full traverses
 | |
|     case 6:
 | |
|     {
 | |
|       switch (currentRegion)
 | |
|       {
 | |
|         case 1: { afterTraverse << coordsToPixels(keyMin, valueMax); break; }
 | |
|         case 7: { afterTraverse << coordsToPixels(keyMax, valueMax); break; }
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     case 7:
 | |
|     {
 | |
|       switch (currentRegion)
 | |
|       {
 | |
|         case 2: { beforeTraverse << coordsToPixels(keyMax, valueMax); break; }
 | |
|         case 3: { beforeTraverse << coordsToPixels(keyMax, valueMax); afterTraverse << coordsToPixels(keyMin, valueMin); break; }
 | |
|         case 6: { beforeTraverse << coordsToPixels(keyMax, valueMax); break; }
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     case 8:
 | |
|     {
 | |
|       switch (currentRegion)
 | |
|       {
 | |
|         case 1: { afterTraverse << coordsToPixels(keyMin, valueMax); break; }
 | |
|         case 3: { afterTraverse << coordsToPixels(keyMin, valueMin); break; }
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     case 9:
 | |
|     {
 | |
|       switch (currentRegion)
 | |
|       {
 | |
|         case 2: { beforeTraverse << coordsToPixels(keyMax, valueMin); break; }
 | |
|         case 1: { beforeTraverse << coordsToPixels(keyMax, valueMin); afterTraverse << coordsToPixels(keyMin, valueMax); break; }
 | |
|         case 4: { beforeTraverse << coordsToPixels(keyMax, valueMin); break; }
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Calculates the (minimum) distance (in pixels) the curve's representation has from the given \a
 | |
|   pixelPoint in pixels. This is used to determine whether the curve was clicked or not, e.g. in
 | |
|   \ref selectTest. The closest data point to \a pixelPoint is returned in \a closestData. Note that
 | |
|   if the curve has a line representation, the returned distance may be smaller than the distance to
 | |
|   the \a closestData point, since the distance to the curve line is also taken into account.
 | |
|   
 | |
|   If either the curve has no data or if the line style is \ref lsNone and the scatter style's shape
 | |
|   is \ref QCPScatterStyle::ssNone (i.e. there is no visual representation of the curve), returns
 | |
|   -1.0.
 | |
| */
 | |
| double QCPCurve::pointDistance(const QPointF &pixelPoint, QCPCurveDataContainer::const_iterator &closestData) const
 | |
| {
 | |
|   closestData = mDataContainer->constEnd();
 | |
|   if (mDataContainer->isEmpty())
 | |
|     return -1.0;
 | |
|   if (mLineStyle == lsNone && mScatterStyle.isNone())
 | |
|     return -1.0;
 | |
|   
 | |
|   if (mDataContainer->size() == 1)
 | |
|   {
 | |
|     QPointF dataPoint = coordsToPixels(mDataContainer->constBegin()->key, mDataContainer->constBegin()->value);
 | |
|     closestData = mDataContainer->constBegin();
 | |
|     return QCPVector2D(dataPoint-pixelPoint).length();
 | |
|   }
 | |
|   
 | |
|   // calculate minimum distances to curve data points and find closestData iterator:
 | |
|   double minDistSqr = std::numeric_limits<double>::max();
 | |
|   // iterate over found data points and then choose the one with the shortest distance to pos:
 | |
|   QCPCurveDataContainer::const_iterator begin = mDataContainer->constBegin();
 | |
|   QCPCurveDataContainer::const_iterator end = mDataContainer->constEnd();
 | |
|   for (QCPCurveDataContainer::const_iterator it=begin; it!=end; ++it)
 | |
|   {
 | |
|     const double currentDistSqr = QCPVector2D(coordsToPixels(it->key, it->value)-pixelPoint).lengthSquared();
 | |
|     if (currentDistSqr < minDistSqr)
 | |
|     {
 | |
|       minDistSqr = currentDistSqr;
 | |
|       closestData = it;
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   // calculate distance to line if there is one (if so, will probably be smaller than distance to closest data point):
 | |
|   if (mLineStyle != lsNone)
 | |
|   {
 | |
|     QVector<QPointF> lines;
 | |
|     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
 | |
|     for (int i=0; i<lines.size()-1; ++i)
 | |
|     {
 | |
|       double currentDistSqr = QCPVector2D(pixelPoint).distanceSquaredToLine(lines.at(i), lines.at(i+1));
 | |
|       if (currentDistSqr < minDistSqr)
 | |
|         minDistSqr = currentDistSqr;
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   return qSqrt(minDistSqr);
 | |
| }
 | |
| /* end of 'src/plottables/plottable-curve.cpp' */
 | |
| 
 | |
| 
 | |
| /* including file 'src/plottables/plottable-bars.cpp', size 43512            */
 | |
| /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */
 | |
| 
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPBarsGroup
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPBarsGroup
 | |
|   \brief Groups multiple QCPBars together so they appear side by side
 | |
|   
 | |
|   \image html QCPBarsGroup.png
 | |
|   
 | |
|   When showing multiple QCPBars in one plot which have bars at identical keys, it may be desirable
 | |
|   to have them appearing next to each other at each key. This is what adding the respective QCPBars
 | |
|   plottables to a QCPBarsGroup achieves. (An alternative approach is to stack them on top of each
 | |
|   other, see \ref QCPBars::moveAbove.)
 | |
|   
 | |
|   \section qcpbarsgroup-usage Usage
 | |
|   
 | |
|   To add a QCPBars plottable to the group, create a new group and then add the respective bars
 | |
|   intances:
 | |
|   \snippet documentation/doc-code-snippets/mainwindow.cpp qcpbarsgroup-creation
 | |
|   Alternatively to appending to the group like shown above, you can also set the group on the
 | |
|   QCPBars plottable via \ref QCPBars::setBarsGroup.
 | |
|   
 | |
|   The spacing between the bars can be configured via \ref setSpacingType and \ref setSpacing. The
 | |
|   bars in this group appear in the plot in the order they were appended. To insert a bars plottable
 | |
|   at a certain index position, or to reposition a bars plottable which is already in the group, use
 | |
|   \ref insert.
 | |
|   
 | |
|   To remove specific bars from the group, use either \ref remove or call \ref
 | |
|   QCPBars::setBarsGroup "QCPBars::setBarsGroup(0)" on the respective bars plottable.
 | |
|   
 | |
|   To clear the entire group, call \ref clear, or simply delete the group.
 | |
|   
 | |
|   \section qcpbarsgroup-example Example
 | |
|   
 | |
|   The image above is generated with the following code:
 | |
|   \snippet documentation/doc-image-generator/mainwindow.cpp qcpbarsgroup-example
 | |
| */
 | |
| 
 | |
| /* start of documentation of inline functions */
 | |
| 
 | |
| /*! \fn QList<QCPBars*> QCPBarsGroup::bars() const
 | |
|   
 | |
|   Returns all bars currently in this group.
 | |
|   
 | |
|   \see bars(int index)
 | |
| */
 | |
| 
 | |
| /*! \fn int QCPBarsGroup::size() const
 | |
|   
 | |
|   Returns the number of QCPBars plottables that are part of this group.
 | |
|   
 | |
| */
 | |
| 
 | |
| /*! \fn bool QCPBarsGroup::isEmpty() const
 | |
|   
 | |
|   Returns whether this bars group is empty.
 | |
|   
 | |
|   \see size
 | |
| */
 | |
| 
 | |
| /*! \fn bool QCPBarsGroup::contains(QCPBars *bars)
 | |
|   
 | |
|   Returns whether the specified \a bars plottable is part of this group.
 | |
|   
 | |
| */
 | |
| 
 | |
| /* end of documentation of inline functions */
 | |
| 
 | |
| /*!
 | |
|   Constructs a new bars group for the specified QCustomPlot instance.
 | |
| */
 | |
| QCPBarsGroup::QCPBarsGroup(QCustomPlot *parentPlot) :
 | |
|   QObject(parentPlot),
 | |
|   mParentPlot(parentPlot),
 | |
|   mSpacingType(stAbsolute),
 | |
|   mSpacing(4)
 | |
| {
 | |
| }
 | |
| 
 | |
| QCPBarsGroup::~QCPBarsGroup()
 | |
| {
 | |
|   clear();
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets how the spacing between adjacent bars is interpreted. See \ref SpacingType.
 | |
|   
 | |
|   The actual spacing can then be specified with \ref setSpacing.
 | |
| 
 | |
|   \see setSpacing
 | |
| */
 | |
| void QCPBarsGroup::setSpacingType(SpacingType spacingType)
 | |
| {
 | |
|   mSpacingType = spacingType;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the spacing between adjacent bars. What the number passed as \a spacing actually means, is
 | |
|   defined by the current \ref SpacingType, which can be set with \ref setSpacingType.
 | |
| 
 | |
|   \see setSpacingType
 | |
| */
 | |
| void QCPBarsGroup::setSpacing(double spacing)
 | |
| {
 | |
|   mSpacing = spacing;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the QCPBars instance with the specified \a index in this group. If no such QCPBars
 | |
|   exists, returns 0.
 | |
| 
 | |
|   \see bars(), size
 | |
| */
 | |
| QCPBars *QCPBarsGroup::bars(int index) const
 | |
| {
 | |
|   if (index >= 0 && index < mBars.size())
 | |
|   {
 | |
|     return mBars.at(index);
 | |
|   } else
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "index out of bounds:" << index;
 | |
|     return 0;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Removes all QCPBars plottables from this group.
 | |
| 
 | |
|   \see isEmpty
 | |
| */
 | |
| void QCPBarsGroup::clear()
 | |
| {
 | |
|   foreach (QCPBars *bars, mBars) // since foreach takes a copy, removing bars in the loop is okay
 | |
|     bars->setBarsGroup(0); // removes itself via removeBars
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Adds the specified \a bars plottable to this group. Alternatively, you can also use \ref
 | |
|   QCPBars::setBarsGroup on the \a bars instance.
 | |
| 
 | |
|   \see insert, remove
 | |
| */
 | |
| void QCPBarsGroup::append(QCPBars *bars)
 | |
| {
 | |
|   if (!bars)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "bars is 0";
 | |
|     return;
 | |
|   }
 | |
|     
 | |
|   if (!mBars.contains(bars))
 | |
|     bars->setBarsGroup(this);
 | |
|   else
 | |
|     qDebug() << Q_FUNC_INFO << "bars plottable is already in this bars group:" << reinterpret_cast<quintptr>(bars);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Inserts the specified \a bars plottable into this group at the specified index position \a i.
 | |
|   This gives you full control over the ordering of the bars.
 | |
|   
 | |
|   \a bars may already be part of this group. In that case, \a bars is just moved to the new index
 | |
|   position.
 | |
| 
 | |
|   \see append, remove
 | |
| */
 | |
| void QCPBarsGroup::insert(int i, QCPBars *bars)
 | |
| {
 | |
|   if (!bars)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "bars is 0";
 | |
|     return;
 | |
|   }
 | |
|   
 | |
|   // first append to bars list normally:
 | |
|   if (!mBars.contains(bars))
 | |
|     bars->setBarsGroup(this);
 | |
|   // then move to according position:
 | |
|   mBars.move(mBars.indexOf(bars), qBound(0, i, mBars.size()-1));
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Removes the specified \a bars plottable from this group.
 | |
|   
 | |
|   \see contains, clear
 | |
| */
 | |
| void QCPBarsGroup::remove(QCPBars *bars)
 | |
| {
 | |
|   if (!bars)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "bars is 0";
 | |
|     return;
 | |
|   }
 | |
|   
 | |
|   if (mBars.contains(bars))
 | |
|     bars->setBarsGroup(0);
 | |
|   else
 | |
|     qDebug() << Q_FUNC_INFO << "bars plottable is not in this bars group:" << reinterpret_cast<quintptr>(bars);
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Adds the specified \a bars to the internal mBars list of bars. This method does not change the
 | |
|   barsGroup property on \a bars.
 | |
|   
 | |
|   \see unregisterBars
 | |
| */
 | |
| void QCPBarsGroup::registerBars(QCPBars *bars)
 | |
| {
 | |
|   if (!mBars.contains(bars))
 | |
|     mBars.append(bars);
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Removes the specified \a bars from the internal mBars list of bars. This method does not change
 | |
|   the barsGroup property on \a bars.
 | |
|   
 | |
|   \see registerBars
 | |
| */
 | |
| void QCPBarsGroup::unregisterBars(QCPBars *bars)
 | |
| {
 | |
|   mBars.removeOne(bars);
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns the pixel offset in the key dimension the specified \a bars plottable should have at the
 | |
|   given key coordinate \a keyCoord. The offset is relative to the pixel position of the key
 | |
|   coordinate \a keyCoord.
 | |
| */
 | |
| double QCPBarsGroup::keyPixelOffset(const QCPBars *bars, double keyCoord)
 | |
| {
 | |
|   // find list of all base bars in case some mBars are stacked:
 | |
|   QList<const QCPBars*> baseBars;
 | |
|   foreach (const QCPBars *b, mBars)
 | |
|   {
 | |
|     while (b->barBelow())
 | |
|       b = b->barBelow();
 | |
|     if (!baseBars.contains(b))
 | |
|       baseBars.append(b);
 | |
|   }
 | |
|   // find base bar this "bars" is stacked on:
 | |
|   const QCPBars *thisBase = bars;
 | |
|   while (thisBase->barBelow())
 | |
|     thisBase = thisBase->barBelow();
 | |
|   
 | |
|   // determine key pixel offset of this base bars considering all other base bars in this barsgroup:
 | |
|   double result = 0;
 | |
|   int index = baseBars.indexOf(thisBase);
 | |
|   if (index >= 0)
 | |
|   {
 | |
|     if (baseBars.size() % 2 == 1 && index == (baseBars.size()-1)/2) // is center bar (int division on purpose)
 | |
|     {
 | |
|       return result;
 | |
|     } else
 | |
|     {
 | |
|       double lowerPixelWidth, upperPixelWidth;
 | |
|       int startIndex;
 | |
|       int dir = (index <= (baseBars.size()-1)/2) ? -1 : 1; // if bar is to lower keys of center, dir is negative
 | |
|       if (baseBars.size() % 2 == 0) // even number of bars
 | |
|       {
 | |
|         startIndex = baseBars.size()/2 + (dir < 0 ? -1 : 0);
 | |
|         result += getPixelSpacing(baseBars.at(startIndex), keyCoord)*0.5; // half of middle spacing
 | |
|       } else // uneven number of bars
 | |
|       {
 | |
|         startIndex = (baseBars.size()-1)/2+dir;
 | |
|         baseBars.at((baseBars.size()-1)/2)->getPixelWidth(keyCoord, lowerPixelWidth, upperPixelWidth);
 | |
|         result += qAbs(upperPixelWidth-lowerPixelWidth)*0.5; // half of center bar
 | |
|         result += getPixelSpacing(baseBars.at((baseBars.size()-1)/2), keyCoord); // center bar spacing
 | |
|       }
 | |
|       for (int i = startIndex; i != index; i += dir) // add widths and spacings of bars in between center and our bars
 | |
|       {
 | |
|         baseBars.at(i)->getPixelWidth(keyCoord, lowerPixelWidth, upperPixelWidth);
 | |
|         result += qAbs(upperPixelWidth-lowerPixelWidth);
 | |
|         result += getPixelSpacing(baseBars.at(i), keyCoord);
 | |
|       }
 | |
|       // finally half of our bars width:
 | |
|       baseBars.at(index)->getPixelWidth(keyCoord, lowerPixelWidth, upperPixelWidth);
 | |
|       result += qAbs(upperPixelWidth-lowerPixelWidth)*0.5;
 | |
|       // correct sign of result depending on orientation and direction of key axis:
 | |
|       result *= dir*thisBase->keyAxis()->pixelOrientation();
 | |
|     }
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns the spacing in pixels which is between this \a bars and the following one, both at the
 | |
|   key coordinate \a keyCoord.
 | |
|   
 | |
|   \note Typically the returned value doesn't depend on \a bars or \a keyCoord. \a bars is only
 | |
|   needed to get access to the key axis transformation and axis rect for the modes \ref
 | |
|   stAxisRectRatio and \ref stPlotCoords. The \a keyCoord is only relevant for spacings given in
 | |
|   \ref stPlotCoords on a logarithmic axis.
 | |
| */
 | |
| double QCPBarsGroup::getPixelSpacing(const QCPBars *bars, double keyCoord)
 | |
| {
 | |
|   switch (mSpacingType)
 | |
|   {
 | |
|     case stAbsolute:
 | |
|     {
 | |
|       return mSpacing;
 | |
|     }
 | |
|     case stAxisRectRatio:
 | |
|     {
 | |
|       if (bars->keyAxis()->orientation() == Qt::Horizontal)
 | |
|         return bars->keyAxis()->axisRect()->width()*mSpacing;
 | |
|       else
 | |
|         return bars->keyAxis()->axisRect()->height()*mSpacing;
 | |
|     }
 | |
|     case stPlotCoords:
 | |
|     {
 | |
|       double keyPixel = bars->keyAxis()->coordToPixel(keyCoord);
 | |
|       return qAbs(bars->keyAxis()->coordToPixel(keyCoord+mSpacing)-keyPixel);
 | |
|     }
 | |
|   }
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPBarsData
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPBarsData
 | |
|   \brief Holds the data of one single data point (one bar) for QCPBars.
 | |
|   
 | |
|   The stored data is:
 | |
|   \li \a key: coordinate on the key axis of this bar (this is the \a mainKey and the \a sortKey)
 | |
|   \li \a value: height coordinate on the value axis of this bar (this is the \a mainValue)
 | |
|   
 | |
|   The container for storing multiple data points is \ref QCPBarsDataContainer. It is a typedef for
 | |
|   \ref QCPDataContainer with \ref QCPBarsData as the DataType template parameter. See the
 | |
|   documentation there for an explanation regarding the data type's generic methods.
 | |
|   
 | |
|   \see QCPBarsDataContainer
 | |
| */
 | |
| 
 | |
| /* start documentation of inline functions */
 | |
| 
 | |
| /*! \fn double QCPBarsData::sortKey() const
 | |
|   
 | |
|   Returns the \a key member of this data point.
 | |
|   
 | |
|   For a general explanation of what this method is good for in the context of the data container,
 | |
|   see the documentation of \ref QCPDataContainer.
 | |
| */
 | |
| 
 | |
| /*! \fn static QCPBarsData QCPBarsData::fromSortKey(double sortKey)
 | |
|   
 | |
|   Returns a data point with the specified \a sortKey. All other members are set to zero.
 | |
|   
 | |
|   For a general explanation of what this method is good for in the context of the data container,
 | |
|   see the documentation of \ref QCPDataContainer.
 | |
| */
 | |
| 
 | |
| /*! \fn static static bool QCPBarsData::sortKeyIsMainKey()
 | |
|   
 | |
|   Since the member \a key is both the data point key coordinate and the data ordering parameter,
 | |
|   this method returns true.
 | |
|   
 | |
|   For a general explanation of what this method is good for in the context of the data container,
 | |
|   see the documentation of \ref QCPDataContainer.
 | |
| */
 | |
| 
 | |
| /*! \fn double QCPBarsData::mainKey() const
 | |
|   
 | |
|   Returns the \a key member of this data point.
 | |
|   
 | |
|   For a general explanation of what this method is good for in the context of the data container,
 | |
|   see the documentation of \ref QCPDataContainer.
 | |
| */
 | |
| 
 | |
| /*! \fn double QCPBarsData::mainValue() const
 | |
|   
 | |
|   Returns the \a value member of this data point.
 | |
|   
 | |
|   For a general explanation of what this method is good for in the context of the data container,
 | |
|   see the documentation of \ref QCPDataContainer.
 | |
| */
 | |
| 
 | |
| /*! \fn QCPRange QCPBarsData::valueRange() const
 | |
|   
 | |
|   Returns a QCPRange with both lower and upper boundary set to \a value of this data point.
 | |
|   
 | |
|   For a general explanation of what this method is good for in the context of the data container,
 | |
|   see the documentation of \ref QCPDataContainer.
 | |
| */
 | |
| 
 | |
| /* end documentation of inline functions */
 | |
| 
 | |
| /*!
 | |
|   Constructs a bar data point with key and value set to zero.
 | |
| */
 | |
| QCPBarsData::QCPBarsData() :
 | |
|   key(0),
 | |
|   value(0)
 | |
| {
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Constructs a bar data point with the specified \a key and \a value.
 | |
| */
 | |
| QCPBarsData::QCPBarsData(double key, double value) :
 | |
|   key(key),
 | |
|   value(value)
 | |
| {
 | |
| }
 | |
| 
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPBars
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPBars
 | |
|   \brief A plottable representing a bar chart in a plot.
 | |
| 
 | |
|   \image html QCPBars.png
 | |
|   
 | |
|   To plot data, assign it with the \ref setData or \ref addData functions.
 | |
|   
 | |
|   \section qcpbars-appearance Changing the appearance
 | |
|   
 | |
|   The appearance of the bars is determined by the pen and the brush (\ref setPen, \ref setBrush).
 | |
|   The width of the individual bars can be controlled with \ref setWidthType and \ref setWidth.
 | |
|   
 | |
|   Bar charts are stackable. This means, two QCPBars plottables can be placed on top of each other
 | |
|   (see \ref QCPBars::moveAbove). So when two bars are at the same key position, they will appear
 | |
|   stacked.
 | |
|   
 | |
|   If you would like to group multiple QCPBars plottables together so they appear side by side as
 | |
|   shown below, use QCPBarsGroup.
 | |
|   
 | |
|   \image html QCPBarsGroup.png
 | |
|   
 | |
|   \section qcpbars-usage Usage
 | |
|   
 | |
|   Like all data representing objects in QCustomPlot, the QCPBars is a plottable
 | |
|   (QCPAbstractPlottable). So the plottable-interface of QCustomPlot applies
 | |
|   (QCustomPlot::plottable, QCustomPlot::removePlottable, etc.)
 | |
|   
 | |
|   Usually, you first create an instance:
 | |
|   \snippet documentation/doc-code-snippets/mainwindow.cpp qcpbars-creation-1
 | |
|   which registers it with the QCustomPlot instance of the passed axes. Note that this QCustomPlot instance takes
 | |
|   ownership of the plottable, so do not delete it manually but use QCustomPlot::removePlottable() instead.
 | |
|   The newly created plottable can be modified, e.g.:
 | |
|   \snippet documentation/doc-code-snippets/mainwindow.cpp qcpbars-creation-2
 | |
| */
 | |
| 
 | |
| /* start of documentation of inline functions */
 | |
| 
 | |
| /*! \fn QSharedPointer<QCPBarsDataContainer> QCPBars::data() const
 | |
|   
 | |
|   Returns a shared pointer to the internal data storage of type \ref QCPBarsDataContainer. You may
 | |
|   use it to directly manipulate the data, which may be more convenient and faster than using the
 | |
|   regular \ref setData or \ref addData methods.
 | |
| */
 | |
| 
 | |
| /*! \fn QCPBars *QCPBars::barBelow() const
 | |
|   Returns the bars plottable that is directly below this bars plottable.
 | |
|   If there is no such plottable, returns 0.
 | |
|   
 | |
|   \see barAbove, moveBelow, moveAbove
 | |
| */
 | |
| 
 | |
| /*! \fn QCPBars *QCPBars::barAbove() const
 | |
|   Returns the bars plottable that is directly above this bars plottable.
 | |
|   If there is no such plottable, returns 0.
 | |
|   
 | |
|   \see barBelow, moveBelow, moveAbove
 | |
| */
 | |
| 
 | |
| /* end of documentation of inline functions */
 | |
| 
 | |
| /*!
 | |
|   Constructs a bar chart which uses \a keyAxis as its key axis ("x") and \a valueAxis as its value
 | |
|   axis ("y"). \a keyAxis and \a valueAxis must reside in the same QCustomPlot instance and not have
 | |
|   the same orientation. If either of these restrictions is violated, a corresponding message is
 | |
|   printed to the debug output (qDebug), the construction is not aborted, though.
 | |
|   
 | |
|   The created QCPBars is automatically registered with the QCustomPlot instance inferred from \a
 | |
|   keyAxis. This QCustomPlot instance takes ownership of the QCPBars, so do not delete it manually
 | |
|   but use QCustomPlot::removePlottable() instead.
 | |
| */
 | |
| QCPBars::QCPBars(QCPAxis *keyAxis, QCPAxis *valueAxis) :
 | |
|   QCPAbstractPlottable1D<QCPBarsData>(keyAxis, valueAxis),
 | |
|   mWidth(0.75),
 | |
|   mWidthType(wtPlotCoords),
 | |
|   mBarsGroup(0),
 | |
|   mBaseValue(0),
 | |
|   mStackingGap(0)
 | |
| {
 | |
|   // modify inherited properties from abstract plottable:
 | |
|   mPen.setColor(Qt::blue);
 | |
|   mPen.setStyle(Qt::SolidLine);
 | |
|   mBrush.setColor(QColor(40, 50, 255, 30));
 | |
|   mBrush.setStyle(Qt::SolidPattern);
 | |
|   mSelectionDecorator->setBrush(QBrush(QColor(160, 160, 255)));
 | |
| }
 | |
| 
 | |
| QCPBars::~QCPBars()
 | |
| {
 | |
|   setBarsGroup(0);
 | |
|   if (mBarBelow || mBarAbove)
 | |
|     connectBars(mBarBelow.data(), mBarAbove.data()); // take this bar out of any stacking
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Replaces the current data container with the provided \a data container.
 | |
|   
 | |
|   Since a QSharedPointer is used, multiple QCPBars may share the same data container safely.
 | |
|   Modifying the data in the container will then affect all bars that share the container. Sharing
 | |
|   can be achieved by simply exchanging the data containers wrapped in shared pointers:
 | |
|   \snippet documentation/doc-code-snippets/mainwindow.cpp qcpbars-datasharing-1
 | |
|   
 | |
|   If you do not wish to share containers, but create a copy from an existing container, rather use
 | |
|   the \ref QCPDataContainer<DataType>::set method on the bar's data container directly:
 | |
|   \snippet documentation/doc-code-snippets/mainwindow.cpp qcpbars-datasharing-2
 | |
|   
 | |
|   \see addData
 | |
| */
 | |
| void QCPBars::setData(QSharedPointer<QCPBarsDataContainer> data)
 | |
| {
 | |
|   mDataContainer = data;
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Replaces the current data with the provided points in \a keys and \a values. The provided
 | |
|   vectors should have equal length. Else, the number of added points will be the size of the
 | |
|   smallest vector.
 | |
|   
 | |
|   If you can guarantee that the passed data points are sorted by \a keys in ascending order, you
 | |
|   can set \a alreadySorted to true, to improve performance by saving a sorting run.
 | |
|   
 | |
|   \see addData
 | |
| */
 | |
| void QCPBars::setData(const QVector<double> &keys, const QVector<double> &values, bool alreadySorted)
 | |
| {
 | |
|   mDataContainer->clear();
 | |
|   addData(keys, values, alreadySorted);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the width of the bars.
 | |
| 
 | |
|   How the number passed as \a width is interpreted (e.g. screen pixels, plot coordinates,...),
 | |
|   depends on the currently set width type, see \ref setWidthType and \ref WidthType.
 | |
| */
 | |
| void QCPBars::setWidth(double width)
 | |
| {
 | |
|   mWidth = width;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets how the width of the bars is defined. See the documentation of \ref WidthType for an
 | |
|   explanation of the possible values for \a widthType.
 | |
|   
 | |
|   The default value is \ref wtPlotCoords.
 | |
|   
 | |
|   \see setWidth
 | |
| */
 | |
| void QCPBars::setWidthType(QCPBars::WidthType widthType)
 | |
| {
 | |
|   mWidthType = widthType;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets to which QCPBarsGroup this QCPBars instance belongs to. Alternatively, you can also use \ref
 | |
|   QCPBarsGroup::append.
 | |
|   
 | |
|   To remove this QCPBars from any group, set \a barsGroup to 0.
 | |
| */
 | |
| void QCPBars::setBarsGroup(QCPBarsGroup *barsGroup)
 | |
| {
 | |
|   // deregister at old group:
 | |
|   if (mBarsGroup)
 | |
|     mBarsGroup->unregisterBars(this);
 | |
|   mBarsGroup = barsGroup;
 | |
|   // register at new group:
 | |
|   if (mBarsGroup)
 | |
|     mBarsGroup->registerBars(this);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the base value of this bars plottable.
 | |
| 
 | |
|   The base value defines where on the value coordinate the bars start. How far the bars extend from
 | |
|   the base value is given by their individual value data. For example, if the base value is set to
 | |
|   1, a bar with data value 2 will have its lowest point at value coordinate 1 and highest point at
 | |
|   3.
 | |
|   
 | |
|   For stacked bars, only the base value of the bottom-most QCPBars has meaning.
 | |
|   
 | |
|   The default base value is 0.
 | |
| */
 | |
| void QCPBars::setBaseValue(double baseValue)
 | |
| {
 | |
|   mBaseValue = baseValue;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   If this bars plottable is stacked on top of another bars plottable (\ref moveAbove), this method
 | |
|   allows specifying a distance in \a pixels, by which the drawn bar rectangles will be separated by
 | |
|   the bars below it.
 | |
| */
 | |
| void QCPBars::setStackingGap(double pixels)
 | |
| {
 | |
|   mStackingGap = pixels;
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Adds the provided points in \a keys and \a values to the current data. The provided vectors
 | |
|   should have equal length. Else, the number of added points will be the size of the smallest
 | |
|   vector.
 | |
|   
 | |
|   If you can guarantee that the passed data points are sorted by \a keys in ascending order, you
 | |
|   can set \a alreadySorted to true, to improve performance by saving a sorting run.
 | |
|   
 | |
|   Alternatively, you can also access and modify the data directly via the \ref data method, which
 | |
|   returns a pointer to the internal data container.
 | |
| */
 | |
| void QCPBars::addData(const QVector<double> &keys, const QVector<double> &values, bool alreadySorted)
 | |
| {
 | |
|   if (keys.size() != values.size())
 | |
|     qDebug() << Q_FUNC_INFO << "keys and values have different sizes:" << keys.size() << values.size();
 | |
|   const int n = qMin(keys.size(), values.size());
 | |
|   QVector<QCPBarsData> tempData(n);
 | |
|   QVector<QCPBarsData>::iterator it = tempData.begin();
 | |
|   const QVector<QCPBarsData>::iterator itEnd = tempData.end();
 | |
|   int i = 0;
 | |
|   while (it != itEnd)
 | |
|   {
 | |
|     it->key = keys[i];
 | |
|     it->value = values[i];
 | |
|     ++it;
 | |
|     ++i;
 | |
|   }
 | |
|   mDataContainer->add(tempData, alreadySorted); // don't modify tempData beyond this to prevent copy on write
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   Adds the provided data point as \a key and \a value to the current data.
 | |
|   
 | |
|   Alternatively, you can also access and modify the data directly via the \ref data method, which
 | |
|   returns a pointer to the internal data container.
 | |
| */
 | |
| void QCPBars::addData(double key, double value)
 | |
| {
 | |
|   mDataContainer->add(QCPBarsData(key, value));
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Moves this bars plottable below \a bars. In other words, the bars of this plottable will appear
 | |
|   below the bars of \a bars. The move target \a bars must use the same key and value axis as this
 | |
|   plottable.
 | |
|   
 | |
|   Inserting into and removing from existing bar stacking is handled gracefully. If \a bars already
 | |
|   has a bars object below itself, this bars object is inserted between the two. If this bars object
 | |
|   is already between two other bars, the two other bars will be stacked on top of each other after
 | |
|   the operation.
 | |
|   
 | |
|   To remove this bars plottable from any stacking, set \a bars to 0.
 | |
|   
 | |
|   \see moveBelow, barAbove, barBelow
 | |
| */
 | |
| void QCPBars::moveBelow(QCPBars *bars)
 | |
| {
 | |
|   if (bars == this) return;
 | |
|   if (bars && (bars->keyAxis() != mKeyAxis.data() || bars->valueAxis() != mValueAxis.data()))
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "passed QCPBars* doesn't have same key and value axis as this QCPBars";
 | |
|     return;
 | |
|   }
 | |
|   // remove from stacking:
 | |
|   connectBars(mBarBelow.data(), mBarAbove.data()); // Note: also works if one (or both) of them is 0
 | |
|   // if new bar given, insert this bar below it:
 | |
|   if (bars)
 | |
|   {
 | |
|     if (bars->mBarBelow)
 | |
|       connectBars(bars->mBarBelow.data(), this);
 | |
|     connectBars(this, bars);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Moves this bars plottable above \a bars. In other words, the bars of this plottable will appear
 | |
|   above the bars of \a bars. The move target \a bars must use the same key and value axis as this
 | |
|   plottable.
 | |
|   
 | |
|   Inserting into and removing from existing bar stacking is handled gracefully. If \a bars already
 | |
|   has a bars object above itself, this bars object is inserted between the two. If this bars object
 | |
|   is already between two other bars, the two other bars will be stacked on top of each other after
 | |
|   the operation.
 | |
|   
 | |
|   To remove this bars plottable from any stacking, set \a bars to 0.
 | |
|   
 | |
|   \see moveBelow, barBelow, barAbove
 | |
| */
 | |
| void QCPBars::moveAbove(QCPBars *bars)
 | |
| {
 | |
|   if (bars == this) return;
 | |
|   if (bars && (bars->keyAxis() != mKeyAxis.data() || bars->valueAxis() != mValueAxis.data()))
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "passed QCPBars* doesn't have same key and value axis as this QCPBars";
 | |
|     return;
 | |
|   }
 | |
|   // remove from stacking:
 | |
|   connectBars(mBarBelow.data(), mBarAbove.data()); // Note: also works if one (or both) of them is 0
 | |
|   // if new bar given, insert this bar above it:
 | |
|   if (bars)
 | |
|   {
 | |
|     if (bars->mBarAbove)
 | |
|       connectBars(this, bars->mBarAbove.data());
 | |
|     connectBars(bars, this);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   \copydoc QCPPlottableInterface1D::selectTestRect
 | |
| */
 | |
| QCPDataSelection QCPBars::selectTestRect(const QRectF &rect, bool onlySelectable) const
 | |
| {
 | |
|   QCPDataSelection result;
 | |
|   if ((onlySelectable && mSelectable == QCP::stNone) || mDataContainer->isEmpty())
 | |
|     return result;
 | |
|   if (!mKeyAxis || !mValueAxis)
 | |
|     return result;
 | |
|   
 | |
|   QCPBarsDataContainer::const_iterator visibleBegin, visibleEnd;
 | |
|   getVisibleDataBounds(visibleBegin, visibleEnd);
 | |
|   
 | |
|   for (QCPBarsDataContainer::const_iterator it=visibleBegin; it!=visibleEnd; ++it)
 | |
|   {
 | |
|     if (rect.intersects(getBarRect(it->key, it->value)))
 | |
|       result.addDataRange(QCPDataRange(it-mDataContainer->constBegin(), it-mDataContainer->constBegin()+1), false);
 | |
|   }
 | |
|   result.simplify();
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| double QCPBars::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
 | |
| {
 | |
|   Q_UNUSED(details)
 | |
|   if ((onlySelectable && mSelectable == QCP::stNone) || mDataContainer->isEmpty())
 | |
|     return -1;
 | |
|   if (!mKeyAxis || !mValueAxis)
 | |
|     return -1;
 | |
|   
 | |
|   if (mKeyAxis.data()->axisRect()->rect().contains(pos.toPoint()))
 | |
|   {
 | |
|     // get visible data range:
 | |
|     QCPBarsDataContainer::const_iterator visibleBegin, visibleEnd;
 | |
|     getVisibleDataBounds(visibleBegin, visibleEnd);
 | |
|     for (QCPBarsDataContainer::const_iterator it=visibleBegin; it!=visibleEnd; ++it)
 | |
|     {
 | |
|       if (getBarRect(it->key, it->value).contains(pos))
 | |
|       {
 | |
|         if (details)
 | |
|         {
 | |
|           int pointIndex = it-mDataContainer->constBegin();
 | |
|           details->setValue(QCPDataSelection(QCPDataRange(pointIndex, pointIndex+1)));
 | |
|         }
 | |
|         return mParentPlot->selectionTolerance()*0.99;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   return -1;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| QCPRange QCPBars::getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain) const
 | |
| {
 | |
|   /* Note: If this QCPBars uses absolute pixels as width (or is in a QCPBarsGroup with spacing in
 | |
|   absolute pixels), using this method to adapt the key axis range to fit the bars into the
 | |
|   currently visible axis range will not work perfectly. Because in the moment the axis range is
 | |
|   changed to the new range, the fixed pixel widths/spacings will represent different coordinate
 | |
|   spans than before, which in turn would require a different key range to perfectly fit, and so on.
 | |
|   The only solution would be to iteratively approach the perfect fitting axis range, but the
 | |
|   mismatch isn't large enough in most applications, to warrant this here. If a user does need a
 | |
|   better fit, he should call the corresponding axis rescale multiple times in a row.
 | |
|   */
 | |
|   QCPRange range;
 | |
|   range = mDataContainer->keyRange(foundRange, inSignDomain);
 | |
|   
 | |
|   // determine exact range of bars by including bar width and barsgroup offset:
 | |
|   if (foundRange && mKeyAxis)
 | |
|   {
 | |
|     double lowerPixelWidth, upperPixelWidth, keyPixel;
 | |
|     // lower range bound:
 | |
|     getPixelWidth(range.lower, lowerPixelWidth, upperPixelWidth);
 | |
|     keyPixel = mKeyAxis.data()->coordToPixel(range.lower) + lowerPixelWidth;
 | |
|     if (mBarsGroup)
 | |
|       keyPixel += mBarsGroup->keyPixelOffset(this, range.lower);
 | |
|     const double lowerCorrected = mKeyAxis.data()->pixelToCoord(keyPixel);
 | |
|     if (!qIsNaN(lowerCorrected) && qIsFinite(lowerCorrected) && range.lower > lowerCorrected)
 | |
|       range.lower = lowerCorrected;
 | |
|     // upper range bound:
 | |
|     getPixelWidth(range.upper, lowerPixelWidth, upperPixelWidth);
 | |
|     keyPixel = mKeyAxis.data()->coordToPixel(range.upper) + upperPixelWidth;
 | |
|     if (mBarsGroup)
 | |
|       keyPixel += mBarsGroup->keyPixelOffset(this, range.upper);
 | |
|     const double upperCorrected = mKeyAxis.data()->pixelToCoord(keyPixel);
 | |
|     if (!qIsNaN(upperCorrected) && qIsFinite(upperCorrected) && range.upper < upperCorrected)
 | |
|       range.upper = upperCorrected;
 | |
|   }
 | |
|   return range;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| QCPRange QCPBars::getValueRange(bool &foundRange, QCP::SignDomain inSignDomain, const QCPRange &inKeyRange) const
 | |
| {
 | |
|   // Note: can't simply use mDataContainer->valueRange here because we need to
 | |
|   // take into account bar base value and possible stacking of multiple bars
 | |
|   QCPRange range;
 | |
|   range.lower = mBaseValue;
 | |
|   range.upper = mBaseValue;
 | |
|   bool haveLower = true; // set to true, because baseValue should always be visible in bar charts
 | |
|   bool haveUpper = true; // set to true, because baseValue should always be visible in bar charts
 | |
|   QCPBarsDataContainer::const_iterator itBegin = mDataContainer->constBegin();
 | |
|   QCPBarsDataContainer::const_iterator itEnd = mDataContainer->constEnd();
 | |
|   if (inKeyRange != QCPRange())
 | |
|   {
 | |
|     itBegin = mDataContainer->findBegin(inKeyRange.lower);
 | |
|     itEnd = mDataContainer->findEnd(inKeyRange.upper);
 | |
|   }
 | |
|   for (QCPBarsDataContainer::const_iterator it = itBegin; it != itEnd; ++it)
 | |
|   {
 | |
|     const double current = it->value + getStackedBaseValue(it->key, it->value >= 0);
 | |
|     if (qIsNaN(current)) continue;
 | |
|     if (inSignDomain == QCP::sdBoth || (inSignDomain == QCP::sdNegative && current < 0) || (inSignDomain == QCP::sdPositive && current > 0))
 | |
|     {
 | |
|       if (current < range.lower || !haveLower)
 | |
|       {
 | |
|         range.lower = current;
 | |
|         haveLower = true;
 | |
|       }
 | |
|       if (current > range.upper || !haveUpper)
 | |
|       {
 | |
|         range.upper = current;
 | |
|         haveUpper = true;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   foundRange = true; // return true because bar charts always have the 0-line visible
 | |
|   return range;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| QPointF QCPBars::dataPixelPosition(int index) const
 | |
| {
 | |
|   if (index >= 0 && index < mDataContainer->size())
 | |
|   {
 | |
|     QCPAxis *keyAxis = mKeyAxis.data();
 | |
|     QCPAxis *valueAxis = mValueAxis.data();
 | |
|     if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return QPointF(); }
 | |
|     
 | |
|     const QCPDataContainer<QCPBarsData>::const_iterator it = mDataContainer->constBegin()+index;
 | |
|     const double valuePixel = valueAxis->coordToPixel(getStackedBaseValue(it->key, it->value >= 0) + it->value);
 | |
|     const double keyPixel = keyAxis->coordToPixel(it->key) + (mBarsGroup ? mBarsGroup->keyPixelOffset(this, it->key) : 0);
 | |
|     if (keyAxis->orientation() == Qt::Horizontal)
 | |
|       return QPointF(keyPixel, valuePixel);
 | |
|     else
 | |
|       return QPointF(valuePixel, keyPixel);
 | |
|   } else
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "Index out of bounds" << index;
 | |
|     return QPointF();
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPBars::draw(QCPPainter *painter)
 | |
| {
 | |
|   if (!mKeyAxis || !mValueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
 | |
|   if (mDataContainer->isEmpty()) return;
 | |
|   
 | |
|   QCPBarsDataContainer::const_iterator visibleBegin, visibleEnd;
 | |
|   getVisibleDataBounds(visibleBegin, visibleEnd);
 | |
|   
 | |
|   // loop over and draw segments of unselected/selected data:
 | |
|   QList<QCPDataRange> selectedSegments, unselectedSegments, allSegments;
 | |
|   getDataSegments(selectedSegments, unselectedSegments);
 | |
|   allSegments << unselectedSegments << selectedSegments;
 | |
|   for (int i=0; i<allSegments.size(); ++i)
 | |
|   {
 | |
|     bool isSelectedSegment = i >= unselectedSegments.size();
 | |
|     QCPBarsDataContainer::const_iterator begin = visibleBegin;
 | |
|     QCPBarsDataContainer::const_iterator end = visibleEnd;
 | |
|     mDataContainer->limitIteratorsToDataRange(begin, end, allSegments.at(i));
 | |
|     if (begin == end)
 | |
|       continue;
 | |
|     
 | |
|     for (QCPBarsDataContainer::const_iterator it=begin; it!=end; ++it)
 | |
|     {
 | |
|       // check data validity if flag set:
 | |
| #ifdef QCUSTOMPLOT_CHECK_DATA
 | |
|       if (QCP::isInvalidData(it->key, it->value))
 | |
|         qDebug() << Q_FUNC_INFO << "Data point at" << it->key << "of drawn range invalid." << "Plottable name:" << name();
 | |
| #endif
 | |
|       // draw bar:
 | |
|       if (isSelectedSegment && mSelectionDecorator)
 | |
|       {
 | |
|         mSelectionDecorator->applyBrush(painter);
 | |
|         mSelectionDecorator->applyPen(painter);
 | |
|       } else
 | |
|       {
 | |
|         painter->setBrush(mBrush);
 | |
|         painter->setPen(mPen);
 | |
|       }
 | |
|       applyDefaultAntialiasingHint(painter);
 | |
|       painter->drawPolygon(getBarRect(it->key, it->value));
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   // draw other selection decoration that isn't just line/scatter pens and brushes:
 | |
|   if (mSelectionDecorator)
 | |
|     mSelectionDecorator->drawDecoration(painter, selection());
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPBars::drawLegendIcon(QCPPainter *painter, const QRectF &rect) const
 | |
| {
 | |
|   // draw filled rect:
 | |
|   applyDefaultAntialiasingHint(painter);
 | |
|   painter->setBrush(mBrush);
 | |
|   painter->setPen(mPen);
 | |
|   QRectF r = QRectF(0, 0, rect.width()*0.67, rect.height()*0.67);
 | |
|   r.moveCenter(rect.center());
 | |
|   painter->drawRect(r);
 | |
| }
 | |
| 
 | |
| /*!  \internal
 | |
|   
 | |
|   called by \ref draw to determine which data (key) range is visible at the current key axis range
 | |
|   setting, so only that needs to be processed. It also takes into account the bar width.
 | |
|   
 | |
|   \a begin returns an iterator to the lowest data point that needs to be taken into account when
 | |
|   plotting. Note that in order to get a clean plot all the way to the edge of the axis rect, \a
 | |
|   lower may still be just outside the visible range.
 | |
|   
 | |
|   \a end returns an iterator one higher than the highest visible data point. Same as before, \a end
 | |
|   may also lie just outside of the visible range.
 | |
|   
 | |
|   if the plottable contains no data, both \a begin and \a end point to constEnd.
 | |
| */
 | |
| void QCPBars::getVisibleDataBounds(QCPBarsDataContainer::const_iterator &begin, QCPBarsDataContainer::const_iterator &end) const
 | |
| {
 | |
|   if (!mKeyAxis)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "invalid key axis";
 | |
|     begin = mDataContainer->constEnd();
 | |
|     end = mDataContainer->constEnd();
 | |
|     return;
 | |
|   }
 | |
|   if (mDataContainer->isEmpty())
 | |
|   {
 | |
|     begin = mDataContainer->constEnd();
 | |
|     end = mDataContainer->constEnd();
 | |
|     return;
 | |
|   }
 | |
|   
 | |
|   // get visible data range as QMap iterators
 | |
|   begin = mDataContainer->findBegin(mKeyAxis.data()->range().lower);
 | |
|   end = mDataContainer->findEnd(mKeyAxis.data()->range().upper);
 | |
|   double lowerPixelBound = mKeyAxis.data()->coordToPixel(mKeyAxis.data()->range().lower);
 | |
|   double upperPixelBound = mKeyAxis.data()->coordToPixel(mKeyAxis.data()->range().upper);
 | |
|   bool isVisible = false;
 | |
|   // walk left from begin to find lower bar that actually is completely outside visible pixel range:
 | |
|   QCPBarsDataContainer::const_iterator it = begin;
 | |
|   while (it != mDataContainer->constBegin())
 | |
|   {
 | |
|     --it;
 | |
|     const QRectF barRect = getBarRect(it->key, it->value);
 | |
|     if (mKeyAxis.data()->orientation() == Qt::Horizontal)
 | |
|       isVisible = ((!mKeyAxis.data()->rangeReversed() && barRect.right() >= lowerPixelBound) || (mKeyAxis.data()->rangeReversed() && barRect.left() <= lowerPixelBound));
 | |
|     else // keyaxis is vertical
 | |
|       isVisible = ((!mKeyAxis.data()->rangeReversed() && barRect.top() <= lowerPixelBound) || (mKeyAxis.data()->rangeReversed() && barRect.bottom() >= lowerPixelBound));
 | |
|     if (isVisible)
 | |
|       begin = it;
 | |
|     else
 | |
|       break;
 | |
|   }
 | |
|   // walk right from ubound to find upper bar that actually is completely outside visible pixel range:
 | |
|   it = end;
 | |
|   while (it != mDataContainer->constEnd())
 | |
|   {
 | |
|     const QRectF barRect = getBarRect(it->key, it->value);
 | |
|     if (mKeyAxis.data()->orientation() == Qt::Horizontal)
 | |
|       isVisible = ((!mKeyAxis.data()->rangeReversed() && barRect.left() <= upperPixelBound) || (mKeyAxis.data()->rangeReversed() && barRect.right() >= upperPixelBound));
 | |
|     else // keyaxis is vertical
 | |
|       isVisible = ((!mKeyAxis.data()->rangeReversed() && barRect.bottom() >= upperPixelBound) || (mKeyAxis.data()->rangeReversed() && barRect.top() <= upperPixelBound));
 | |
|     if (isVisible)
 | |
|       end = it+1;
 | |
|     else
 | |
|       break;
 | |
|     ++it;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns the rect in pixel coordinates of a single bar with the specified \a key and \a value. The
 | |
|   rect is shifted according to the bar stacking (see \ref moveAbove) and base value (see \ref
 | |
|   setBaseValue), and to have non-overlapping border lines with the bars stacked below.
 | |
| */
 | |
| QRectF QCPBars::getBarRect(double key, double value) const
 | |
| {
 | |
|   QCPAxis *keyAxis = mKeyAxis.data();
 | |
|   QCPAxis *valueAxis = mValueAxis.data();
 | |
|   if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return QRectF(); }
 | |
|   
 | |
|   double lowerPixelWidth, upperPixelWidth;
 | |
|   getPixelWidth(key, lowerPixelWidth, upperPixelWidth);
 | |
|   double base = getStackedBaseValue(key, value >= 0);
 | |
|   double basePixel = valueAxis->coordToPixel(base);
 | |
|   double valuePixel = valueAxis->coordToPixel(base+value);
 | |
|   double keyPixel = keyAxis->coordToPixel(key);
 | |
|   if (mBarsGroup)
 | |
|     keyPixel += mBarsGroup->keyPixelOffset(this, key);
 | |
|   double bottomOffset = (mBarBelow && mPen != Qt::NoPen ? 1 : 0)*(mPen.isCosmetic() ? 1 : mPen.widthF());
 | |
|   bottomOffset += mBarBelow ? mStackingGap : 0;
 | |
|   bottomOffset *= (value<0 ? -1 : 1)*valueAxis->pixelOrientation();
 | |
|   if (qAbs(valuePixel-basePixel) <= qAbs(bottomOffset))
 | |
|     bottomOffset = valuePixel-basePixel;
 | |
|   if (keyAxis->orientation() == Qt::Horizontal)
 | |
|   {
 | |
|     return QRectF(QPointF(keyPixel+lowerPixelWidth, valuePixel), QPointF(keyPixel+upperPixelWidth, basePixel+bottomOffset)).normalized();
 | |
|   } else
 | |
|   {
 | |
|     return QRectF(QPointF(basePixel+bottomOffset, keyPixel+lowerPixelWidth), QPointF(valuePixel, keyPixel+upperPixelWidth)).normalized();
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   This function is used to determine the width of the bar at coordinate \a key, according to the
 | |
|   specified width (\ref setWidth) and width type (\ref setWidthType).
 | |
|   
 | |
|   The output parameters \a lower and \a upper return the number of pixels the bar extends to lower
 | |
|   and higher keys, relative to the \a key coordinate (so with a non-reversed horizontal axis, \a
 | |
|   lower is negative and \a upper positive).
 | |
| */
 | |
| void QCPBars::getPixelWidth(double key, double &lower, double &upper) const
 | |
| {
 | |
|   lower = 0;
 | |
|   upper = 0;
 | |
|   switch (mWidthType)
 | |
|   {
 | |
|     case wtAbsolute:
 | |
|     {
 | |
|       upper = mWidth*0.5*mKeyAxis.data()->pixelOrientation();
 | |
|       lower = -upper;
 | |
|       break;
 | |
|     }
 | |
|     case wtAxisRectRatio:
 | |
|     {
 | |
|       if (mKeyAxis && mKeyAxis.data()->axisRect())
 | |
|       {
 | |
|         if (mKeyAxis.data()->orientation() == Qt::Horizontal)
 | |
|           upper = mKeyAxis.data()->axisRect()->width()*mWidth*0.5*mKeyAxis.data()->pixelOrientation();
 | |
|         else
 | |
|           upper = mKeyAxis.data()->axisRect()->height()*mWidth*0.5*mKeyAxis.data()->pixelOrientation();
 | |
|         lower = -upper;
 | |
|       } else
 | |
|         qDebug() << Q_FUNC_INFO << "No key axis or axis rect defined";
 | |
|       break;
 | |
|     }
 | |
|     case wtPlotCoords:
 | |
|     {
 | |
|       if (mKeyAxis)
 | |
|       {
 | |
|         double keyPixel = mKeyAxis.data()->coordToPixel(key);
 | |
|         upper = mKeyAxis.data()->coordToPixel(key+mWidth*0.5)-keyPixel;
 | |
|         lower = mKeyAxis.data()->coordToPixel(key-mWidth*0.5)-keyPixel;
 | |
|         // no need to qSwap(lower, higher) when range reversed, because higher/lower are gained by
 | |
|         // coordinate transform which includes range direction
 | |
|       } else
 | |
|         qDebug() << Q_FUNC_INFO << "No key axis defined";
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   This function is called to find at which value to start drawing the base of a bar at \a key, when
 | |
|   it is stacked on top of another QCPBars (e.g. with \ref moveAbove).
 | |
|   
 | |
|   positive and negative bars are separated per stack (positive are stacked above baseValue upwards,
 | |
|   negative are stacked below baseValue downwards). This can be indicated with \a positive. So if the
 | |
|   bar for which we need the base value is negative, set \a positive to false.
 | |
| */
 | |
| double QCPBars::getStackedBaseValue(double key, bool positive) const
 | |
| {
 | |
|   if (mBarBelow)
 | |
|   {
 | |
|     double max = 0; // don't initialize with mBaseValue here because only base value of bottom-most bar has meaning in a bar stack
 | |
|     // find bars of mBarBelow that are approximately at key and find largest one:
 | |
|     double epsilon = qAbs(key)*(sizeof(key)==4 ? 1e-6 : 1e-14); // should be safe even when changed to use float at some point
 | |
|     if (key == 0)
 | |
|       epsilon = (sizeof(key)==4 ? 1e-6 : 1e-14);
 | |
|     QCPBarsDataContainer::const_iterator it = mBarBelow.data()->mDataContainer->findBegin(key-epsilon);
 | |
|     QCPBarsDataContainer::const_iterator itEnd = mBarBelow.data()->mDataContainer->findEnd(key+epsilon);
 | |
|     while (it != itEnd)
 | |
|     {
 | |
|       if (it->key > key-epsilon && it->key < key+epsilon)
 | |
|       {
 | |
|         if ((positive && it->value > max) ||
 | |
|             (!positive && it->value < max))
 | |
|           max = it->value;
 | |
|       }
 | |
|       ++it;
 | |
|     }
 | |
|     // recurse down the bar-stack to find the total height:
 | |
|     return max + mBarBelow.data()->getStackedBaseValue(key, positive);
 | |
|   } else
 | |
|     return mBaseValue;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Connects \a below and \a above to each other via their mBarAbove/mBarBelow properties. The bar(s)
 | |
|   currently above lower and below upper will become disconnected to lower/upper.
 | |
|   
 | |
|   If lower is zero, upper will be disconnected at the bottom.
 | |
|   If upper is zero, lower will be disconnected at the top.
 | |
| */
 | |
| void QCPBars::connectBars(QCPBars *lower, QCPBars *upper)
 | |
| {
 | |
|   if (!lower && !upper) return;
 | |
|   
 | |
|   if (!lower) // disconnect upper at bottom
 | |
|   {
 | |
|     // disconnect old bar below upper:
 | |
|     if (upper->mBarBelow && upper->mBarBelow.data()->mBarAbove.data() == upper)
 | |
|       upper->mBarBelow.data()->mBarAbove = 0;
 | |
|     upper->mBarBelow = 0;
 | |
|   } else if (!upper) // disconnect lower at top
 | |
|   {
 | |
|     // disconnect old bar above lower:
 | |
|     if (lower->mBarAbove && lower->mBarAbove.data()->mBarBelow.data() == lower)
 | |
|       lower->mBarAbove.data()->mBarBelow = 0;
 | |
|     lower->mBarAbove = 0;
 | |
|   } else // connect lower and upper
 | |
|   {
 | |
|     // disconnect old bar above lower:
 | |
|     if (lower->mBarAbove && lower->mBarAbove.data()->mBarBelow.data() == lower)
 | |
|       lower->mBarAbove.data()->mBarBelow = 0;
 | |
|     // disconnect old bar below upper:
 | |
|     if (upper->mBarBelow && upper->mBarBelow.data()->mBarAbove.data() == upper)
 | |
|       upper->mBarBelow.data()->mBarAbove = 0;
 | |
|     lower->mBarAbove = upper;
 | |
|     upper->mBarBelow = lower;
 | |
|   }
 | |
| }
 | |
| /* end of 'src/plottables/plottable-bars.cpp' */
 | |
| 
 | |
| 
 | |
| /* including file 'src/plottables/plottable-statisticalbox.cpp', size 28622  */
 | |
| /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPStatisticalBoxData
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPStatisticalBoxData
 | |
|   \brief Holds the data of one single data point for QCPStatisticalBox.
 | |
|   
 | |
|   The stored data is:
 | |
|   
 | |
|   \li \a key: coordinate on the key axis of this data point (this is the \a mainKey and the \a sortKey)
 | |
|   
 | |
|   \li \a minimum: the position of the lower whisker, typically the minimum measurement of the
 | |
|   sample that's not considered an outlier.
 | |
|   
 | |
|   \li \a lowerQuartile: the lower end of the box. The lower and the upper quartiles are the two
 | |
|   statistical quartiles around the median of the sample, they should contain 50% of the sample
 | |
|   data.
 | |
|   
 | |
|   \li \a median: the value of the median mark inside the quartile box. The median separates the
 | |
|   sample data in half (50% of the sample data is below/above the median). (This is the \a mainValue)
 | |
|   
 | |
|   \li \a upperQuartile: the upper end of the box. The lower and the upper quartiles are the two
 | |
|   statistical quartiles around the median of the sample, they should contain 50% of the sample
 | |
|   data.
 | |
|   
 | |
|   \li \a maximum: the position of the upper whisker, typically the maximum measurement of the
 | |
|   sample that's not considered an outlier.
 | |
|   
 | |
|   \li \a outliers: a QVector of outlier values that will be drawn as scatter points at the \a key
 | |
|   coordinate of this data point (see \ref QCPStatisticalBox::setOutlierStyle)
 | |
|   
 | |
|   The container for storing multiple data points is \ref QCPStatisticalBoxDataContainer. It is a
 | |
|   typedef for \ref QCPDataContainer with \ref QCPStatisticalBoxData as the DataType template
 | |
|   parameter. See the documentation there for an explanation regarding the data type's generic
 | |
|   methods.
 | |
|   
 | |
|   \see QCPStatisticalBoxDataContainer
 | |
| */
 | |
| 
 | |
| /* start documentation of inline functions */
 | |
| 
 | |
| /*! \fn double QCPStatisticalBoxData::sortKey() const
 | |
|   
 | |
|   Returns the \a key member of this data point.
 | |
|   
 | |
|   For a general explanation of what this method is good for in the context of the data container,
 | |
|   see the documentation of \ref QCPDataContainer.
 | |
| */
 | |
| 
 | |
| /*! \fn static QCPStatisticalBoxData QCPStatisticalBoxData::fromSortKey(double sortKey)
 | |
|   
 | |
|   Returns a data point with the specified \a sortKey. All other members are set to zero.
 | |
|   
 | |
|   For a general explanation of what this method is good for in the context of the data container,
 | |
|   see the documentation of \ref QCPDataContainer.
 | |
| */
 | |
| 
 | |
| /*! \fn static static bool QCPStatisticalBoxData::sortKeyIsMainKey()
 | |
|   
 | |
|   Since the member \a key is both the data point key coordinate and the data ordering parameter,
 | |
|   this method returns true.
 | |
|   
 | |
|   For a general explanation of what this method is good for in the context of the data container,
 | |
|   see the documentation of \ref QCPDataContainer.
 | |
| */
 | |
| 
 | |
| /*! \fn double QCPStatisticalBoxData::mainKey() const
 | |
|   
 | |
|   Returns the \a key member of this data point.
 | |
|   
 | |
|   For a general explanation of what this method is good for in the context of the data container,
 | |
|   see the documentation of \ref QCPDataContainer.
 | |
| */
 | |
| 
 | |
| /*! \fn double QCPStatisticalBoxData::mainValue() const
 | |
|   
 | |
|   Returns the \a median member of this data point.
 | |
|   
 | |
|   For a general explanation of what this method is good for in the context of the data container,
 | |
|   see the documentation of \ref QCPDataContainer.
 | |
| */
 | |
| 
 | |
| /*! \fn QCPRange QCPStatisticalBoxData::valueRange() const
 | |
|   
 | |
|   Returns a QCPRange spanning from the \a minimum to the \a maximum member of this statistical box
 | |
|   data point, possibly further expanded by outliers.
 | |
|   
 | |
|   For a general explanation of what this method is good for in the context of the data container,
 | |
|   see the documentation of \ref QCPDataContainer.
 | |
| */
 | |
| 
 | |
| /* end documentation of inline functions */
 | |
| 
 | |
| /*!
 | |
|   Constructs a data point with key and all values set to zero.
 | |
| */
 | |
| QCPStatisticalBoxData::QCPStatisticalBoxData() :
 | |
|   key(0),
 | |
|   minimum(0),
 | |
|   lowerQuartile(0),
 | |
|   median(0),
 | |
|   upperQuartile(0),
 | |
|   maximum(0)
 | |
| {
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Constructs a data point with the specified \a key, \a minimum, \a lowerQuartile, \a median, \a
 | |
|   upperQuartile, \a maximum and optionally a number of \a outliers.
 | |
| */
 | |
| QCPStatisticalBoxData::QCPStatisticalBoxData(double key, double minimum, double lowerQuartile, double median, double upperQuartile, double maximum, const QVector<double> &outliers) :
 | |
|   key(key),
 | |
|   minimum(minimum),
 | |
|   lowerQuartile(lowerQuartile),
 | |
|   median(median),
 | |
|   upperQuartile(upperQuartile),
 | |
|   maximum(maximum),
 | |
|   outliers(outliers)
 | |
| {
 | |
| }
 | |
| 
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPStatisticalBox
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPStatisticalBox
 | |
|   \brief A plottable representing a single statistical box in a plot.
 | |
| 
 | |
|   \image html QCPStatisticalBox.png
 | |
|   
 | |
|   To plot data, assign it with the \ref setData or \ref addData functions. Alternatively, you can
 | |
|   also access and modify the data via the \ref data method, which returns a pointer to the internal
 | |
|   \ref QCPStatisticalBoxDataContainer.
 | |
|   
 | |
|   Additionally each data point can itself have a list of outliers, drawn as scatter points at the
 | |
|   key coordinate of the respective statistical box data point. They can either be set by using the
 | |
|   respective \ref addData(double,double,double,double,double,double,const QVector<double>&)
 | |
|   "addData" method or accessing the individual data points through \ref data, and setting the
 | |
|   <tt>QVector<double> outliers</tt> of the data points directly.
 | |
|   
 | |
|   \section qcpstatisticalbox-appearance Changing the appearance
 | |
|   
 | |
|   The appearance of each data point box, ranging from the lower to the upper quartile, is
 | |
|   controlled via \ref setPen and \ref setBrush. You may change the width of the boxes with \ref
 | |
|   setWidth in plot coordinates.
 | |
| 
 | |
|   Each data point's visual representation also consists of two whiskers. Whiskers are the lines
 | |
|   which reach from the upper quartile to the maximum, and from the lower quartile to the minimum.
 | |
|   The appearance of the whiskers can be modified with: \ref setWhiskerPen, \ref setWhiskerBarPen,
 | |
|   \ref setWhiskerWidth. The whisker width is the width of the bar perpendicular to the whisker at
 | |
|   the top (for maximum) and bottom (for minimum). If the whisker pen is changed, make sure to set
 | |
|   the \c capStyle to \c Qt::FlatCap. Otherwise the backbone line might exceed the whisker bars by a
 | |
|   few pixels due to the pen cap being not perfectly flat.
 | |
|   
 | |
|   The median indicator line inside the box has its own pen, \ref setMedianPen.
 | |
|   
 | |
|   The outlier data points are drawn as normal scatter points. Their look can be controlled with
 | |
|   \ref setOutlierStyle
 | |
|   
 | |
|   \section qcpstatisticalbox-usage Usage
 | |
|   
 | |
|   Like all data representing objects in QCustomPlot, the QCPStatisticalBox is a plottable
 | |
|   (QCPAbstractPlottable). So the plottable-interface of QCustomPlot applies
 | |
|   (QCustomPlot::plottable, QCustomPlot::removePlottable, etc.)
 | |
|   
 | |
|   Usually, you first create an instance:
 | |
|   \snippet documentation/doc-code-snippets/mainwindow.cpp qcpstatisticalbox-creation-1
 | |
|   which registers it with the QCustomPlot instance of the passed axes. Note that this QCustomPlot instance takes
 | |
|   ownership of the plottable, so do not delete it manually but use QCustomPlot::removePlottable() instead.
 | |
|   The newly created plottable can be modified, e.g.:
 | |
|   \snippet documentation/doc-code-snippets/mainwindow.cpp qcpstatisticalbox-creation-2
 | |
| */
 | |
| 
 | |
| /* start documentation of inline functions */
 | |
| 
 | |
| /*! \fn QSharedPointer<QCPStatisticalBoxDataContainer> QCPStatisticalBox::data() const
 | |
|   
 | |
|   Returns a shared pointer to the internal data storage of type \ref
 | |
|   QCPStatisticalBoxDataContainer. You may use it to directly manipulate the data, which may be more
 | |
|   convenient and faster than using the regular \ref setData or \ref addData methods.
 | |
| */
 | |
| 
 | |
| /* end documentation of inline functions */
 | |
| 
 | |
| /*!
 | |
|   Constructs a statistical box which uses \a keyAxis as its key axis ("x") and \a valueAxis as its
 | |
|   value axis ("y"). \a keyAxis and \a valueAxis must reside in the same QCustomPlot instance and
 | |
|   not have the same orientation. If either of these restrictions is violated, a corresponding
 | |
|   message is printed to the debug output (qDebug), the construction is not aborted, though.
 | |
|   
 | |
|   The created QCPStatisticalBox is automatically registered with the QCustomPlot instance inferred
 | |
|   from \a keyAxis. This QCustomPlot instance takes ownership of the QCPStatisticalBox, so do not
 | |
|   delete it manually but use QCustomPlot::removePlottable() instead.
 | |
| */
 | |
| QCPStatisticalBox::QCPStatisticalBox(QCPAxis *keyAxis, QCPAxis *valueAxis) :
 | |
|   QCPAbstractPlottable1D<QCPStatisticalBoxData>(keyAxis, valueAxis),
 | |
|   mWidth(0.5),
 | |
|   mWhiskerWidth(0.2),
 | |
|   mWhiskerPen(Qt::black, 0, Qt::DashLine, Qt::FlatCap),
 | |
|   mWhiskerBarPen(Qt::black),
 | |
|   mWhiskerAntialiased(false),
 | |
|   mMedianPen(Qt::black, 3, Qt::SolidLine, Qt::FlatCap),
 | |
|   mOutlierStyle(QCPScatterStyle::ssCircle, Qt::blue, 6)
 | |
| {
 | |
|   setPen(QPen(Qt::black));
 | |
|   setBrush(Qt::NoBrush);
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Replaces the current data container with the provided \a data container.
 | |
|   
 | |
|   Since a QSharedPointer is used, multiple QCPStatisticalBoxes may share the same data container
 | |
|   safely. Modifying the data in the container will then affect all statistical boxes that share the
 | |
|   container. Sharing can be achieved by simply exchanging the data containers wrapped in shared
 | |
|   pointers:
 | |
|   \snippet documentation/doc-code-snippets/mainwindow.cpp qcpstatisticalbox-datasharing-1
 | |
|   
 | |
|   If you do not wish to share containers, but create a copy from an existing container, rather use
 | |
|   the \ref QCPDataContainer<DataType>::set method on the statistical box data container directly:
 | |
|   \snippet documentation/doc-code-snippets/mainwindow.cpp qcpstatisticalbox-datasharing-2
 | |
|   
 | |
|   \see addData
 | |
| */
 | |
| void QCPStatisticalBox::setData(QSharedPointer<QCPStatisticalBoxDataContainer> data)
 | |
| {
 | |
|   mDataContainer = data;
 | |
| }
 | |
| /*! \overload
 | |
|   
 | |
|   Replaces the current data with the provided points in \a keys, \a minimum, \a lowerQuartile, \a
 | |
|   median, \a upperQuartile and \a maximum. The provided vectors should have equal length. Else, the
 | |
|   number of added points will be the size of the smallest vector.
 | |
|   
 | |
|   If you can guarantee that the passed data points are sorted by \a keys in ascending order, you
 | |
|   can set \a alreadySorted to true, to improve performance by saving a sorting run.
 | |
|   
 | |
|   \see addData
 | |
| */
 | |
| void 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)
 | |
| {
 | |
|   mDataContainer->clear();
 | |
|   addData(keys, minimum, lowerQuartile, median, upperQuartile, maximum, alreadySorted);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the width of the boxes in key coordinates.
 | |
|   
 | |
|   \see setWhiskerWidth
 | |
| */
 | |
| void QCPStatisticalBox::setWidth(double width)
 | |
| {
 | |
|   mWidth = width;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the width of the whiskers in key coordinates.
 | |
|   
 | |
|   Whiskers are the lines which reach from the upper quartile to the maximum, and from the lower
 | |
|   quartile to the minimum.
 | |
|   
 | |
|   \see setWidth
 | |
| */
 | |
| void QCPStatisticalBox::setWhiskerWidth(double width)
 | |
| {
 | |
|   mWhiskerWidth = width;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the pen used for drawing the whisker backbone.
 | |
|   
 | |
|   Whiskers are the lines which reach from the upper quartile to the maximum, and from the lower
 | |
|   quartile to the minimum.
 | |
|   
 | |
|   Make sure to set the \c capStyle of the passed \a pen to \c Qt::FlatCap. Otherwise the backbone
 | |
|   line might exceed the whisker bars by a few pixels due to the pen cap being not perfectly flat.
 | |
|   
 | |
|   \see setWhiskerBarPen
 | |
| */
 | |
| void QCPStatisticalBox::setWhiskerPen(const QPen &pen)
 | |
| {
 | |
|   mWhiskerPen = pen;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the pen used for drawing the whisker bars. Those are the lines parallel to the key axis at
 | |
|   each end of the whisker backbone.
 | |
|   
 | |
|   Whiskers are the lines which reach from the upper quartile to the maximum, and from the lower
 | |
|   quartile to the minimum.
 | |
|   
 | |
|   \see setWhiskerPen
 | |
| */
 | |
| void QCPStatisticalBox::setWhiskerBarPen(const QPen &pen)
 | |
| {
 | |
|   mWhiskerBarPen = pen;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets whether the statistical boxes whiskers are drawn with antialiasing or not.
 | |
| 
 | |
|   Note that antialiasing settings may be overridden by QCustomPlot::setAntialiasedElements and
 | |
|   QCustomPlot::setNotAntialiasedElements.
 | |
| */
 | |
| void QCPStatisticalBox::setWhiskerAntialiased(bool enabled)
 | |
| {
 | |
|   mWhiskerAntialiased = enabled;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the pen used for drawing the median indicator line inside the statistical boxes.
 | |
| */
 | |
| void QCPStatisticalBox::setMedianPen(const QPen &pen)
 | |
| {
 | |
|   mMedianPen = pen;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the appearance of the outlier data points.
 | |
| 
 | |
|   Outliers can be specified with the method
 | |
|   \ref addData(double key, double minimum, double lowerQuartile, double median, double upperQuartile, double maximum, const QVector<double> &outliers)
 | |
| */
 | |
| void QCPStatisticalBox::setOutlierStyle(const QCPScatterStyle &style)
 | |
| {
 | |
|   mOutlierStyle = style;
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|    
 | |
|   Adds the provided points in \a keys, \a minimum, \a lowerQuartile, \a median, \a upperQuartile and
 | |
|   \a maximum to the current data. The provided vectors should have equal length. Else, the number
 | |
|   of added points will be the size of the smallest vector.
 | |
|    
 | |
|   If you can guarantee that the passed data points are sorted by \a keys in ascending order, you
 | |
|   can set \a alreadySorted to true, to improve performance by saving a sorting run.
 | |
|    
 | |
|   Alternatively, you can also access and modify the data directly via the \ref data method, which
 | |
|   returns a pointer to the internal data container.
 | |
| */
 | |
| void 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)
 | |
| {
 | |
|   if (keys.size() != minimum.size() || minimum.size() != lowerQuartile.size() || lowerQuartile.size() != median.size() ||
 | |
|       median.size() != upperQuartile.size() || upperQuartile.size() != maximum.size() || maximum.size() != keys.size())
 | |
|     qDebug() << Q_FUNC_INFO << "keys, minimum, lowerQuartile, median, upperQuartile, maximum have different sizes:"
 | |
|              << keys.size() << minimum.size() << lowerQuartile.size() << median.size() << upperQuartile.size() << maximum.size();
 | |
|   const int n = qMin(keys.size(), qMin(minimum.size(), qMin(lowerQuartile.size(), qMin(median.size(), qMin(upperQuartile.size(), maximum.size())))));
 | |
|   QVector<QCPStatisticalBoxData> tempData(n);
 | |
|   QVector<QCPStatisticalBoxData>::iterator it = tempData.begin();
 | |
|   const QVector<QCPStatisticalBoxData>::iterator itEnd = tempData.end();
 | |
|   int i = 0;
 | |
|   while (it != itEnd)
 | |
|   {
 | |
|     it->key = keys[i];
 | |
|     it->minimum = minimum[i];
 | |
|     it->lowerQuartile = lowerQuartile[i];
 | |
|     it->median = median[i];
 | |
|     it->upperQuartile = upperQuartile[i];
 | |
|     it->maximum = maximum[i];
 | |
|     ++it;
 | |
|     ++i;
 | |
|   }
 | |
|   mDataContainer->add(tempData, alreadySorted); // don't modify tempData beyond this to prevent copy on write
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Adds the provided data point as \a key, \a minimum, \a lowerQuartile, \a median, \a upperQuartile
 | |
|   and \a maximum to the current data.
 | |
|   
 | |
|   Alternatively, you can also access and modify the data directly via the \ref data method, which
 | |
|   returns a pointer to the internal data container.
 | |
| */
 | |
| void QCPStatisticalBox::addData(double key, double minimum, double lowerQuartile, double median, double upperQuartile, double maximum, const QVector<double> &outliers)
 | |
| {
 | |
|   mDataContainer->add(QCPStatisticalBoxData(key, minimum, lowerQuartile, median, upperQuartile, maximum, outliers));
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   \copydoc QCPPlottableInterface1D::selectTestRect
 | |
| */
 | |
| QCPDataSelection QCPStatisticalBox::selectTestRect(const QRectF &rect, bool onlySelectable) const
 | |
| {
 | |
|   QCPDataSelection result;
 | |
|   if ((onlySelectable && mSelectable == QCP::stNone) || mDataContainer->isEmpty())
 | |
|     return result;
 | |
|   if (!mKeyAxis || !mValueAxis)
 | |
|     return result;
 | |
|   
 | |
|   QCPStatisticalBoxDataContainer::const_iterator visibleBegin, visibleEnd;
 | |
|   getVisibleDataBounds(visibleBegin, visibleEnd);
 | |
|   
 | |
|   for (QCPStatisticalBoxDataContainer::const_iterator it=visibleBegin; it!=visibleEnd; ++it)
 | |
|   {
 | |
|     if (rect.intersects(getQuartileBox(it)))
 | |
|       result.addDataRange(QCPDataRange(it-mDataContainer->constBegin(), it-mDataContainer->constBegin()+1), false);
 | |
|   }
 | |
|   result.simplify();
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| double QCPStatisticalBox::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
 | |
| {
 | |
|   Q_UNUSED(details)
 | |
|   if ((onlySelectable && mSelectable == QCP::stNone) || mDataContainer->isEmpty())
 | |
|     return -1;
 | |
|   if (!mKeyAxis || !mValueAxis)
 | |
|     return -1;
 | |
|   
 | |
|   if (mKeyAxis->axisRect()->rect().contains(pos.toPoint()))
 | |
|   {
 | |
|     // get visible data range:
 | |
|     QCPStatisticalBoxDataContainer::const_iterator visibleBegin, visibleEnd;
 | |
|     QCPStatisticalBoxDataContainer::const_iterator closestDataPoint = mDataContainer->constEnd();
 | |
|     getVisibleDataBounds(visibleBegin, visibleEnd);
 | |
|     double minDistSqr = std::numeric_limits<double>::max();
 | |
|     for (QCPStatisticalBoxDataContainer::const_iterator it=visibleBegin; it!=visibleEnd; ++it)
 | |
|     {
 | |
|       if (getQuartileBox(it).contains(pos)) // quartile box
 | |
|       {
 | |
|         double currentDistSqr = mParentPlot->selectionTolerance()*0.99 * mParentPlot->selectionTolerance()*0.99;
 | |
|         if (currentDistSqr < minDistSqr)
 | |
|         {
 | |
|           minDistSqr = currentDistSqr;
 | |
|           closestDataPoint = it;
 | |
|         }
 | |
|       } else // whiskers
 | |
|       {
 | |
|         const QVector<QLineF> whiskerBackbones(getWhiskerBackboneLines(it));
 | |
|         for (int i=0; i<whiskerBackbones.size(); ++i)
 | |
|         {
 | |
|           double currentDistSqr = QCPVector2D(pos).distanceSquaredToLine(whiskerBackbones.at(i));
 | |
|           if (currentDistSqr < minDistSqr)
 | |
|           {
 | |
|             minDistSqr = currentDistSqr;
 | |
|             closestDataPoint = it;
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     if (details)
 | |
|     {
 | |
|       int pointIndex = closestDataPoint-mDataContainer->constBegin();
 | |
|       details->setValue(QCPDataSelection(QCPDataRange(pointIndex, pointIndex+1)));
 | |
|     }
 | |
|     return qSqrt(minDistSqr);
 | |
|   }
 | |
|   return -1;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| QCPRange QCPStatisticalBox::getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain) const
 | |
| {
 | |
|   QCPRange range = mDataContainer->keyRange(foundRange, inSignDomain);
 | |
|   // determine exact range by including width of bars/flags:
 | |
|   if (foundRange)
 | |
|   {
 | |
|     if (inSignDomain != QCP::sdPositive || range.lower-mWidth*0.5 > 0)
 | |
|       range.lower -= mWidth*0.5;
 | |
|     if (inSignDomain != QCP::sdNegative || range.upper+mWidth*0.5 < 0)
 | |
|       range.upper += mWidth*0.5;
 | |
|   }
 | |
|   return range;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| QCPRange QCPStatisticalBox::getValueRange(bool &foundRange, QCP::SignDomain inSignDomain, const QCPRange &inKeyRange) const
 | |
| {
 | |
|   return mDataContainer->valueRange(foundRange, inSignDomain, inKeyRange);
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPStatisticalBox::draw(QCPPainter *painter)
 | |
| {
 | |
|   if (mDataContainer->isEmpty()) return;
 | |
|   QCPAxis *keyAxis = mKeyAxis.data();
 | |
|   QCPAxis *valueAxis = mValueAxis.data();
 | |
|   if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
 | |
|   
 | |
|   QCPStatisticalBoxDataContainer::const_iterator visibleBegin, visibleEnd;
 | |
|   getVisibleDataBounds(visibleBegin, visibleEnd);
 | |
|   
 | |
|   // loop over and draw segments of unselected/selected data:
 | |
|   QList<QCPDataRange> selectedSegments, unselectedSegments, allSegments;
 | |
|   getDataSegments(selectedSegments, unselectedSegments);
 | |
|   allSegments << unselectedSegments << selectedSegments;
 | |
|   for (int i=0; i<allSegments.size(); ++i)
 | |
|   {
 | |
|     bool isSelectedSegment = i >= unselectedSegments.size();
 | |
|     QCPStatisticalBoxDataContainer::const_iterator begin = visibleBegin;
 | |
|     QCPStatisticalBoxDataContainer::const_iterator end = visibleEnd;
 | |
|     mDataContainer->limitIteratorsToDataRange(begin, end, allSegments.at(i));
 | |
|     if (begin == end)
 | |
|       continue;
 | |
|     
 | |
|     for (QCPStatisticalBoxDataContainer::const_iterator it=begin; it!=end; ++it)
 | |
|     {
 | |
|       // check data validity if flag set:
 | |
| # ifdef QCUSTOMPLOT_CHECK_DATA
 | |
|       if (QCP::isInvalidData(it->key, it->minimum) ||
 | |
|           QCP::isInvalidData(it->lowerQuartile, it->median) ||
 | |
|           QCP::isInvalidData(it->upperQuartile, it->maximum))
 | |
|         qDebug() << Q_FUNC_INFO << "Data point at" << it->key << "of drawn range has invalid data." << "Plottable name:" << name();
 | |
|       for (int i=0; i<it->outliers.size(); ++i)
 | |
|         if (QCP::isInvalidData(it->outliers.at(i)))
 | |
|           qDebug() << Q_FUNC_INFO << "Data point outlier at" << it->key << "of drawn range invalid." << "Plottable name:" << name();
 | |
| # endif
 | |
|       
 | |
|       if (isSelectedSegment && mSelectionDecorator)
 | |
|       {
 | |
|         mSelectionDecorator->applyPen(painter);
 | |
|         mSelectionDecorator->applyBrush(painter);
 | |
|       } else
 | |
|       {
 | |
|         painter->setPen(mPen);
 | |
|         painter->setBrush(mBrush);
 | |
|       }
 | |
|       QCPScatterStyle finalOutlierStyle = mOutlierStyle;
 | |
|       if (isSelectedSegment && mSelectionDecorator)
 | |
|         finalOutlierStyle = mSelectionDecorator->getFinalScatterStyle(mOutlierStyle);
 | |
|       drawStatisticalBox(painter, it, finalOutlierStyle);
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   // draw other selection decoration that isn't just line/scatter pens and brushes:
 | |
|   if (mSelectionDecorator)
 | |
|     mSelectionDecorator->drawDecoration(painter, selection());
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPStatisticalBox::drawLegendIcon(QCPPainter *painter, const QRectF &rect) const
 | |
| {
 | |
|   // draw filled rect:
 | |
|   applyDefaultAntialiasingHint(painter);
 | |
|   painter->setPen(mPen);
 | |
|   painter->setBrush(mBrush);
 | |
|   QRectF r = QRectF(0, 0, rect.width()*0.67, rect.height()*0.67);
 | |
|   r.moveCenter(rect.center());
 | |
|   painter->drawRect(r);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Draws the graphical representation of a single statistical box with the data given by the
 | |
|   iterator \a it with the provided \a painter.
 | |
| 
 | |
|   If the statistical box has a set of outlier data points, they are drawn with \a outlierStyle.
 | |
| 
 | |
|   \see getQuartileBox, getWhiskerBackboneLines, getWhiskerBarLines
 | |
| */
 | |
| void QCPStatisticalBox::drawStatisticalBox(QCPPainter *painter, QCPStatisticalBoxDataContainer::const_iterator it, const QCPScatterStyle &outlierStyle) const
 | |
| {
 | |
|   // draw quartile box:
 | |
|   applyDefaultAntialiasingHint(painter);
 | |
|   const QRectF quartileBox = getQuartileBox(it);
 | |
|   painter->drawRect(quartileBox);
 | |
|   // draw median line with cliprect set to quartile box:
 | |
|   painter->save();
 | |
|   painter->setClipRect(quartileBox, Qt::IntersectClip);
 | |
|   painter->setPen(mMedianPen);
 | |
|   painter->drawLine(QLineF(coordsToPixels(it->key-mWidth*0.5, it->median), coordsToPixels(it->key+mWidth*0.5, it->median)));
 | |
|   painter->restore();
 | |
|   // draw whisker lines:
 | |
|   applyAntialiasingHint(painter, mWhiskerAntialiased, QCP::aePlottables);
 | |
|   painter->setPen(mWhiskerPen);
 | |
|   painter->drawLines(getWhiskerBackboneLines(it));
 | |
|   painter->setPen(mWhiskerBarPen);
 | |
|   painter->drawLines(getWhiskerBarLines(it));
 | |
|   // draw outliers:
 | |
|   applyScattersAntialiasingHint(painter);
 | |
|   outlierStyle.applyTo(painter, mPen);
 | |
|   for (int i=0; i<it->outliers.size(); ++i)
 | |
|     outlierStyle.drawShape(painter, coordsToPixels(it->key, it->outliers.at(i)));
 | |
| }
 | |
| 
 | |
| /*!  \internal
 | |
|   
 | |
|   called by \ref draw to determine which data (key) range is visible at the current key axis range
 | |
|   setting, so only that needs to be processed. It also takes into account the bar width.
 | |
|   
 | |
|   \a begin returns an iterator to the lowest data point that needs to be taken into account when
 | |
|   plotting. Note that in order to get a clean plot all the way to the edge of the axis rect, \a
 | |
|   lower may still be just outside the visible range.
 | |
|   
 | |
|   \a end returns an iterator one higher than the highest visible data point. Same as before, \a end
 | |
|   may also lie just outside of the visible range.
 | |
|   
 | |
|   if the plottable contains no data, both \a begin and \a end point to constEnd.
 | |
| */
 | |
| void QCPStatisticalBox::getVisibleDataBounds(QCPStatisticalBoxDataContainer::const_iterator &begin, QCPStatisticalBoxDataContainer::const_iterator &end) const
 | |
| {
 | |
|   if (!mKeyAxis)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "invalid key axis";
 | |
|     begin = mDataContainer->constEnd();
 | |
|     end = mDataContainer->constEnd();
 | |
|     return;
 | |
|   }
 | |
|   begin = mDataContainer->findBegin(mKeyAxis.data()->range().lower-mWidth*0.5); // subtract half width of box to include partially visible data points
 | |
|   end = mDataContainer->findEnd(mKeyAxis.data()->range().upper+mWidth*0.5); // add half width of box to include partially visible data points
 | |
| }
 | |
| 
 | |
| /*!  \internal
 | |
| 
 | |
|   Returns the box in plot coordinates (keys in x, values in y of the returned rect) that covers the
 | |
|   value range from the lower to the upper quartile, of the data given by \a it.
 | |
| 
 | |
|   \see drawStatisticalBox, getWhiskerBackboneLines, getWhiskerBarLines
 | |
| */
 | |
| QRectF QCPStatisticalBox::getQuartileBox(QCPStatisticalBoxDataContainer::const_iterator it) const
 | |
| {
 | |
|   QRectF result;
 | |
|   result.setTopLeft(coordsToPixels(it->key-mWidth*0.5, it->upperQuartile));
 | |
|   result.setBottomRight(coordsToPixels(it->key+mWidth*0.5, it->lowerQuartile));
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*!  \internal
 | |
| 
 | |
|   Returns the whisker backbones (keys in x, values in y of the returned lines) that cover the value
 | |
|   range from the minimum to the lower quartile, and from the upper quartile to the maximum of the
 | |
|   data given by \a it.
 | |
| 
 | |
|   \see drawStatisticalBox, getQuartileBox, getWhiskerBarLines
 | |
| */
 | |
| QVector<QLineF> QCPStatisticalBox::getWhiskerBackboneLines(QCPStatisticalBoxDataContainer::const_iterator it) const
 | |
| {
 | |
|   QVector<QLineF> result(2);
 | |
|   result[0].setPoints(coordsToPixels(it->key, it->lowerQuartile), coordsToPixels(it->key, it->minimum)); // min backbone
 | |
|   result[1].setPoints(coordsToPixels(it->key, it->upperQuartile), coordsToPixels(it->key, it->maximum)); // max backbone
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*!  \internal
 | |
| 
 | |
|   Returns the whisker bars (keys in x, values in y of the returned lines) that are placed at the
 | |
|   end of the whisker backbones, at the minimum and maximum of the data given by \a it.
 | |
| 
 | |
|   \see drawStatisticalBox, getQuartileBox, getWhiskerBackboneLines
 | |
| */
 | |
| QVector<QLineF> QCPStatisticalBox::getWhiskerBarLines(QCPStatisticalBoxDataContainer::const_iterator it) const
 | |
| {
 | |
|   QVector<QLineF> result(2);
 | |
|   result[0].setPoints(coordsToPixels(it->key-mWhiskerWidth*0.5, it->minimum), coordsToPixels(it->key+mWhiskerWidth*0.5, it->minimum)); // min bar
 | |
|   result[1].setPoints(coordsToPixels(it->key-mWhiskerWidth*0.5, it->maximum), coordsToPixels(it->key+mWhiskerWidth*0.5, it->maximum)); // max bar
 | |
|   return result;
 | |
| }
 | |
| /* end of 'src/plottables/plottable-statisticalbox.cpp' */
 | |
| 
 | |
| 
 | |
| /* including file 'src/plottables/plottable-colormap.cpp', size 47531        */
 | |
| /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPColorMapData
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPColorMapData
 | |
|   \brief Holds the two-dimensional data of a QCPColorMap plottable.
 | |
|   
 | |
|   This class is a data storage for \ref QCPColorMap. It holds a two-dimensional array, which \ref
 | |
|   QCPColorMap then displays as a 2D image in the plot, where the array values are represented by a
 | |
|   color, depending on the value.
 | |
|   
 | |
|   The size of the array can be controlled via \ref setSize (or \ref setKeySize, \ref setValueSize).
 | |
|   Which plot coordinates these cells correspond to can be configured with \ref setRange (or \ref
 | |
|   setKeyRange, \ref setValueRange).
 | |
|   
 | |
|   The data cells can be accessed in two ways: They can be directly addressed by an integer index
 | |
|   with \ref setCell. This is the fastest method. Alternatively, they can be addressed by their plot
 | |
|   coordinate with \ref setData. plot coordinate to cell index transformations and vice versa are
 | |
|   provided by the functions \ref coordToCell and \ref cellToCoord.
 | |
|   
 | |
|   A \ref QCPColorMapData also holds an on-demand two-dimensional array of alpha values which (if
 | |
|   allocated) has the same size as the data map. It can be accessed via \ref setAlpha, \ref
 | |
|   fillAlpha and \ref clearAlpha. The memory for the alpha map is only allocated if needed, i.e. on
 | |
|   the first call of \ref setAlpha. \ref clearAlpha restores full opacity and frees the alpha map.
 | |
|   
 | |
|   This class also buffers the minimum and maximum values that are in the data set, to provide
 | |
|   QCPColorMap::rescaleDataRange with the necessary information quickly. Setting a cell to a value
 | |
|   that is greater than the current maximum increases this maximum to the new value. However,
 | |
|   setting the cell that currently holds the maximum value to a smaller value doesn't decrease the
 | |
|   maximum again, because finding the true new maximum would require going through the entire data
 | |
|   array, which might be time consuming. The same holds for the data minimum. This functionality is
 | |
|   given by \ref recalculateDataBounds, such that you can decide when it is sensible to find the
 | |
|   true current minimum and maximum. The method QCPColorMap::rescaleDataRange offers a convenience
 | |
|   parameter \a recalculateDataBounds which may be set to true to automatically call \ref
 | |
|   recalculateDataBounds internally.
 | |
| */
 | |
| 
 | |
| /* start of documentation of inline functions */
 | |
| 
 | |
| /*! \fn bool QCPColorMapData::isEmpty() const
 | |
|   
 | |
|   Returns whether this instance carries no data. This is equivalent to having a size where at least
 | |
|   one of the dimensions is 0 (see \ref setSize).
 | |
| */
 | |
| 
 | |
| /* end of documentation of inline functions */
 | |
| 
 | |
| /*!
 | |
|   Constructs a new QCPColorMapData instance. The instance has \a keySize cells in the key direction
 | |
|   and \a valueSize cells in the value direction. These cells will be displayed by the \ref QCPColorMap
 | |
|   at the coordinates \a keyRange and \a valueRange.
 | |
|   
 | |
|   \see setSize, setKeySize, setValueSize, setRange, setKeyRange, setValueRange
 | |
| */
 | |
| QCPColorMapData::QCPColorMapData(int keySize, int valueSize, const QCPRange &keyRange, const QCPRange &valueRange) :
 | |
|   mKeySize(0),
 | |
|   mValueSize(0),
 | |
|   mKeyRange(keyRange),
 | |
|   mValueRange(valueRange),
 | |
|   mIsEmpty(true),
 | |
|   mData(0),
 | |
|   mAlpha(0),
 | |
|   mDataModified(true)
 | |
| {
 | |
|   setSize(keySize, valueSize);
 | |
|   fill(0);
 | |
| }
 | |
| 
 | |
| QCPColorMapData::~QCPColorMapData()
 | |
| {
 | |
|   if (mData)
 | |
|     delete[] mData;
 | |
|   if (mAlpha)
 | |
|     delete[] mAlpha;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Constructs a new QCPColorMapData instance copying the data and range of \a other.
 | |
| */
 | |
| QCPColorMapData::QCPColorMapData(const QCPColorMapData &other) :
 | |
|   mKeySize(0),
 | |
|   mValueSize(0),
 | |
|   mIsEmpty(true),
 | |
|   mData(0),
 | |
|   mAlpha(0),
 | |
|   mDataModified(true)
 | |
| {
 | |
|   *this = other;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Overwrites this color map data instance with the data stored in \a other. The alpha map state is
 | |
|   transferred, too.
 | |
| */
 | |
| QCPColorMapData &QCPColorMapData::operator=(const QCPColorMapData &other)
 | |
| {
 | |
|   if (&other != this)
 | |
|   {
 | |
|     const int keySize = other.keySize();
 | |
|     const int valueSize = other.valueSize();
 | |
|     if (!other.mAlpha && mAlpha)
 | |
|       clearAlpha();
 | |
|     setSize(keySize, valueSize);
 | |
|     if (other.mAlpha && !mAlpha)
 | |
|       createAlpha(false);
 | |
|     setRange(other.keyRange(), other.valueRange());
 | |
|     if (!isEmpty())
 | |
|     {
 | |
|       memcpy(mData, other.mData, sizeof(mData[0])*keySize*valueSize);
 | |
|       if (mAlpha)
 | |
|         memcpy(mAlpha, other.mAlpha, sizeof(mAlpha[0])*keySize*valueSize);
 | |
|     }
 | |
|     mDataBounds = other.mDataBounds;
 | |
|     mDataModified = true;
 | |
|   }
 | |
|   return *this;
 | |
| }
 | |
| 
 | |
| /* undocumented getter */
 | |
| double QCPColorMapData::data(double key, double value)
 | |
| {
 | |
|   int keyCell = (key-mKeyRange.lower)/(mKeyRange.upper-mKeyRange.lower)*(mKeySize-1)+0.5;
 | |
|   int valueCell = (value-mValueRange.lower)/(mValueRange.upper-mValueRange.lower)*(mValueSize-1)+0.5;
 | |
|   if (keyCell >= 0 && keyCell < mKeySize && valueCell >= 0 && valueCell < mValueSize)
 | |
|     return mData[valueCell*mKeySize + keyCell];
 | |
|   else
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| /* undocumented getter */
 | |
| double QCPColorMapData::cell(int keyIndex, int valueIndex)
 | |
| {
 | |
|   if (keyIndex >= 0 && keyIndex < mKeySize && valueIndex >= 0 && valueIndex < mValueSize)
 | |
|     return mData[valueIndex*mKeySize + keyIndex];
 | |
|   else
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Returns the alpha map value of the cell with the indices \a keyIndex and \a valueIndex.
 | |
| 
 | |
|   If this color map data doesn't have an alpha map (because \ref setAlpha was never called after
 | |
|   creation or after a call to \ref clearAlpha), returns 255, which corresponds to full opacity.
 | |
| 
 | |
|   \see setAlpha
 | |
| */
 | |
| unsigned char QCPColorMapData::alpha(int keyIndex, int valueIndex)
 | |
| {
 | |
|   if (mAlpha && keyIndex >= 0 && keyIndex < mKeySize && valueIndex >= 0 && valueIndex < mValueSize)
 | |
|     return mAlpha[valueIndex*mKeySize + keyIndex];
 | |
|   else
 | |
|     return 255;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Resizes the data array to have \a keySize cells in the key dimension and \a valueSize cells in
 | |
|   the value dimension.
 | |
| 
 | |
|   The current data is discarded and the map cells are set to 0, unless the map had already the
 | |
|   requested size.
 | |
|   
 | |
|   Setting at least one of \a keySize or \a valueSize to zero frees the internal data array and \ref
 | |
|   isEmpty returns true.
 | |
| 
 | |
|   \see setRange, setKeySize, setValueSize
 | |
| */
 | |
| void QCPColorMapData::setSize(int keySize, int valueSize)
 | |
| {
 | |
|   if (keySize != mKeySize || valueSize != mValueSize)
 | |
|   {
 | |
|     mKeySize = keySize;
 | |
|     mValueSize = valueSize;
 | |
|     if (mData)
 | |
|       delete[] mData;
 | |
|     mIsEmpty = mKeySize == 0 || mValueSize == 0;
 | |
|     if (!mIsEmpty)
 | |
|     {
 | |
| #ifdef __EXCEPTIONS
 | |
|       try { // 2D arrays get memory intensive fast. So if the allocation fails, at least output debug message
 | |
| #endif
 | |
|       mData = new double[mKeySize*mValueSize];
 | |
| #ifdef __EXCEPTIONS
 | |
|       } catch (...) { mData = 0; }
 | |
| #endif
 | |
|       if (mData)
 | |
|         fill(0);
 | |
|       else
 | |
|         qDebug() << Q_FUNC_INFO << "out of memory for data dimensions "<< mKeySize << "*" << mValueSize;
 | |
|     } else
 | |
|       mData = 0;
 | |
|     
 | |
|     if (mAlpha) // if we had an alpha map, recreate it with new size
 | |
|       createAlpha();
 | |
|     
 | |
|     mDataModified = true;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Resizes the data array to have \a keySize cells in the key dimension.
 | |
| 
 | |
|   The current data is discarded and the map cells are set to 0, unless the map had already the
 | |
|   requested size.
 | |
|   
 | |
|   Setting \a keySize to zero frees the internal data array and \ref isEmpty returns true.
 | |
| 
 | |
|   \see setKeyRange, setSize, setValueSize
 | |
| */
 | |
| void QCPColorMapData::setKeySize(int keySize)
 | |
| {
 | |
|   setSize(keySize, mValueSize);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Resizes the data array to have \a valueSize cells in the value dimension.
 | |
| 
 | |
|   The current data is discarded and the map cells are set to 0, unless the map had already the
 | |
|   requested size.
 | |
|   
 | |
|   Setting \a valueSize to zero frees the internal data array and \ref isEmpty returns true.
 | |
| 
 | |
|   \see setValueRange, setSize, setKeySize
 | |
| */
 | |
| void QCPColorMapData::setValueSize(int valueSize)
 | |
| {
 | |
|   setSize(mKeySize, valueSize);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the coordinate ranges the data shall be distributed over. This defines the rectangular area
 | |
|   covered by the color map in plot coordinates.
 | |
|   
 | |
|   The outer cells will be centered on the range boundaries given to this function. For example, if
 | |
|   the key size (\ref setKeySize) is 3 and \a keyRange is set to <tt>QCPRange(2, 3)</tt> there will
 | |
|   be cells centered on the key coordinates 2, 2.5 and 3.
 | |
|  
 | |
|   \see setSize
 | |
| */
 | |
| void QCPColorMapData::setRange(const QCPRange &keyRange, const QCPRange &valueRange)
 | |
| {
 | |
|   setKeyRange(keyRange);
 | |
|   setValueRange(valueRange);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the coordinate range the data shall be distributed over in the key dimension. Together with
 | |
|   the value range, This defines the rectangular area covered by the color map in plot coordinates.
 | |
|   
 | |
|   The outer cells will be centered on the range boundaries given to this function. For example, if
 | |
|   the key size (\ref setKeySize) is 3 and \a keyRange is set to <tt>QCPRange(2, 3)</tt> there will
 | |
|   be cells centered on the key coordinates 2, 2.5 and 3.
 | |
|  
 | |
|   \see setRange, setValueRange, setSize
 | |
| */
 | |
| void QCPColorMapData::setKeyRange(const QCPRange &keyRange)
 | |
| {
 | |
|   mKeyRange = keyRange;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the coordinate range the data shall be distributed over in the value dimension. Together with
 | |
|   the key range, This defines the rectangular area covered by the color map in plot coordinates.
 | |
|   
 | |
|   The outer cells will be centered on the range boundaries given to this function. For example, if
 | |
|   the value size (\ref setValueSize) is 3 and \a valueRange is set to <tt>QCPRange(2, 3)</tt> there
 | |
|   will be cells centered on the value coordinates 2, 2.5 and 3.
 | |
|  
 | |
|   \see setRange, setKeyRange, setSize
 | |
| */
 | |
| void QCPColorMapData::setValueRange(const QCPRange &valueRange)
 | |
| {
 | |
|   mValueRange = valueRange;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the data of the cell, which lies at the plot coordinates given by \a key and \a value, to \a
 | |
|   z.
 | |
|   
 | |
|   \note The QCPColorMap always displays the data at equal key/value intervals, even if the key or
 | |
|   value axis is set to a logarithmic scaling. If you want to use QCPColorMap with logarithmic axes,
 | |
|   you shouldn't use the \ref QCPColorMapData::setData method as it uses a linear transformation to
 | |
|   determine the cell index. Rather directly access the cell index with \ref
 | |
|   QCPColorMapData::setCell.
 | |
|  
 | |
|   \see setCell, setRange
 | |
| */
 | |
| void QCPColorMapData::setData(double key, double value, double z)
 | |
| {
 | |
|   int keyCell = (key-mKeyRange.lower)/(mKeyRange.upper-mKeyRange.lower)*(mKeySize-1)+0.5;
 | |
|   int valueCell = (value-mValueRange.lower)/(mValueRange.upper-mValueRange.lower)*(mValueSize-1)+0.5;
 | |
|   if (keyCell >= 0 && keyCell < mKeySize && valueCell >= 0 && valueCell < mValueSize)
 | |
|   {
 | |
|     mData[valueCell*mKeySize + keyCell] = z;
 | |
|     if (z < mDataBounds.lower)
 | |
|       mDataBounds.lower = z;
 | |
|     if (z > mDataBounds.upper)
 | |
|       mDataBounds.upper = z;
 | |
|      mDataModified = true;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the data of the cell with indices \a keyIndex and \a valueIndex to \a z. The indices
 | |
|   enumerate the cells starting from zero, up to the map's size-1 in the respective dimension (see
 | |
|   \ref setSize).
 | |
|   
 | |
|   In the standard plot configuration (horizontal key axis and vertical value axis, both not
 | |
|   range-reversed), the cell with indices (0, 0) is in the bottom left corner and the cell with
 | |
|   indices (keySize-1, valueSize-1) is in the top right corner of the color map.
 | |
|   
 | |
|   \see setData, setSize
 | |
| */
 | |
| void QCPColorMapData::setCell(int keyIndex, int valueIndex, double z)
 | |
| {
 | |
|   if (keyIndex >= 0 && keyIndex < mKeySize && valueIndex >= 0 && valueIndex < mValueSize)
 | |
|   {
 | |
|     mData[valueIndex*mKeySize + keyIndex] = z;
 | |
|     if (z < mDataBounds.lower)
 | |
|       mDataBounds.lower = z;
 | |
|     if (z > mDataBounds.upper)
 | |
|       mDataBounds.upper = z;
 | |
|      mDataModified = true;
 | |
|   } else
 | |
|     qDebug() << Q_FUNC_INFO << "index out of bounds:" << keyIndex << valueIndex;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the alpha of the color map cell given by \a keyIndex and \a valueIndex to \a alpha. A value
 | |
|   of 0 for \a alpha results in a fully transparent cell, and a value of 255 results in a fully
 | |
|   opaque cell.
 | |
| 
 | |
|   If an alpha map doesn't exist yet for this color map data, it will be created here. If you wish
 | |
|   to restore full opacity and free any allocated memory of the alpha map, call \ref clearAlpha.
 | |
| 
 | |
|   Note that the cell-wise alpha which can be configured here is independent of any alpha configured
 | |
|   in the color map's gradient (\ref QCPColorGradient). If a cell is affected both by the cell-wise
 | |
|   and gradient alpha, the alpha values will be blended accordingly during rendering of the color
 | |
|   map.
 | |
| 
 | |
|   \see fillAlpha, clearAlpha
 | |
| */
 | |
| void QCPColorMapData::setAlpha(int keyIndex, int valueIndex, unsigned char alpha)
 | |
| {
 | |
|   if (keyIndex >= 0 && keyIndex < mKeySize && valueIndex >= 0 && valueIndex < mValueSize)
 | |
|   {
 | |
|     if (mAlpha || createAlpha())
 | |
|     {
 | |
|       mAlpha[valueIndex*mKeySize + keyIndex] = alpha;
 | |
|       mDataModified = true;
 | |
|     }
 | |
|   } else
 | |
|     qDebug() << Q_FUNC_INFO << "index out of bounds:" << keyIndex << valueIndex;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Goes through the data and updates the buffered minimum and maximum data values.
 | |
|   
 | |
|   Calling this method is only advised if you are about to call \ref QCPColorMap::rescaleDataRange
 | |
|   and can not guarantee that the cells holding the maximum or minimum data haven't been overwritten
 | |
|   with a smaller or larger value respectively, since the buffered maximum/minimum values have been
 | |
|   updated the last time. Why this is the case is explained in the class description (\ref
 | |
|   QCPColorMapData).
 | |
|   
 | |
|   Note that the method \ref QCPColorMap::rescaleDataRange provides a parameter \a
 | |
|   recalculateDataBounds for convenience. Setting this to true will call this method for you, before
 | |
|   doing the rescale.
 | |
| */
 | |
| void QCPColorMapData::recalculateDataBounds()
 | |
| {
 | |
|   if (mKeySize > 0 && mValueSize > 0)
 | |
|   {
 | |
|     double minHeight = mData[0];
 | |
|     double maxHeight = mData[0];
 | |
|     const int dataCount = mValueSize*mKeySize;
 | |
|     for (int i=0; i<dataCount; ++i)
 | |
|     {
 | |
|       if (mData[i] > maxHeight)
 | |
|         maxHeight = mData[i];
 | |
|       if (mData[i] < minHeight)
 | |
|         minHeight = mData[i];
 | |
|     }
 | |
|     mDataBounds.lower = minHeight;
 | |
|     mDataBounds.upper = maxHeight;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Frees the internal data memory.
 | |
|   
 | |
|   This is equivalent to calling \ref setSize "setSize(0, 0)".
 | |
| */
 | |
| void QCPColorMapData::clear()
 | |
| {
 | |
|   setSize(0, 0);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Frees the internal alpha map. The color map will have full opacity again.
 | |
| */
 | |
| void QCPColorMapData::clearAlpha()
 | |
| {
 | |
|   if (mAlpha)
 | |
|   {
 | |
|     delete[] mAlpha;
 | |
|     mAlpha = 0;
 | |
|     mDataModified = true;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets all cells to the value \a z.
 | |
| */
 | |
| void QCPColorMapData::fill(double z)
 | |
| {
 | |
|   const int dataCount = mValueSize*mKeySize;
 | |
|   for (int i=0; i<dataCount; ++i)
 | |
|     mData[i] = z;
 | |
|   mDataBounds = QCPRange(z, z);
 | |
|   mDataModified = true;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the opacity of all color map cells to \a alpha. A value of 0 for \a alpha results in a fully
 | |
|   transparent color map, and a value of 255 results in a fully opaque color map.
 | |
| 
 | |
|   If you wish to restore opacity to 100% and free any used memory for the alpha map, rather use
 | |
|   \ref clearAlpha.
 | |
| 
 | |
|   \see setAlpha
 | |
| */
 | |
| void QCPColorMapData::fillAlpha(unsigned char alpha)
 | |
| {
 | |
|   if (mAlpha || createAlpha(false))
 | |
|   {
 | |
|     const int dataCount = mValueSize*mKeySize;
 | |
|     for (int i=0; i<dataCount; ++i)
 | |
|       mAlpha[i] = alpha;
 | |
|     mDataModified = true;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Transforms plot coordinates given by \a key and \a value to cell indices of this QCPColorMapData
 | |
|   instance. The resulting cell indices are returned via the output parameters \a keyIndex and \a
 | |
|   valueIndex.
 | |
|   
 | |
|   The retrieved key/value cell indices can then be used for example with \ref setCell.
 | |
|   
 | |
|   If you are only interested in a key or value index, you may pass 0 as \a valueIndex or \a
 | |
|   keyIndex.
 | |
|   
 | |
|   \note The QCPColorMap always displays the data at equal key/value intervals, even if the key or
 | |
|   value axis is set to a logarithmic scaling. If you want to use QCPColorMap with logarithmic axes,
 | |
|   you shouldn't use the \ref QCPColorMapData::coordToCell method as it uses a linear transformation to
 | |
|   determine the cell index.
 | |
|   
 | |
|   \see cellToCoord, QCPAxis::coordToPixel
 | |
| */
 | |
| void QCPColorMapData::coordToCell(double key, double value, int *keyIndex, int *valueIndex) const
 | |
| {
 | |
|   if (keyIndex)
 | |
|     *keyIndex = (key-mKeyRange.lower)/(mKeyRange.upper-mKeyRange.lower)*(mKeySize-1)+0.5;
 | |
|   if (valueIndex)
 | |
|     *valueIndex = (value-mValueRange.lower)/(mValueRange.upper-mValueRange.lower)*(mValueSize-1)+0.5;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Transforms cell indices given by \a keyIndex and \a valueIndex to cell indices of this QCPColorMapData
 | |
|   instance. The resulting coordinates are returned via the output parameters \a key and \a
 | |
|   value.
 | |
|   
 | |
|   If you are only interested in a key or value coordinate, you may pass 0 as \a key or \a
 | |
|   value.
 | |
|   
 | |
|   \note The QCPColorMap always displays the data at equal key/value intervals, even if the key or
 | |
|   value axis is set to a logarithmic scaling. If you want to use QCPColorMap with logarithmic axes,
 | |
|   you shouldn't use the \ref QCPColorMapData::cellToCoord method as it uses a linear transformation to
 | |
|   determine the cell index.
 | |
|   
 | |
|   \see coordToCell, QCPAxis::pixelToCoord
 | |
| */
 | |
| void QCPColorMapData::cellToCoord(int keyIndex, int valueIndex, double *key, double *value) const
 | |
| {
 | |
|   if (key)
 | |
|     *key = keyIndex/(double)(mKeySize-1)*(mKeyRange.upper-mKeyRange.lower)+mKeyRange.lower;
 | |
|   if (value)
 | |
|     *value = valueIndex/(double)(mValueSize-1)*(mValueRange.upper-mValueRange.lower)+mValueRange.lower;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Allocates the internal alpha map with the current data map key/value size and, if \a
 | |
|   initializeOpaque is true, initializes all values to 255. If \a initializeOpaque is false, the
 | |
|   values are not initialized at all. In this case, the alpha map should be initialized manually,
 | |
|   e.g. with \ref fillAlpha.
 | |
| 
 | |
|   If an alpha map exists already, it is deleted first. If this color map is empty (has either key
 | |
|   or value size zero, see \ref isEmpty), the alpha map is cleared.
 | |
| 
 | |
|   The return value indicates the existence of the alpha map after the call. So this method returns
 | |
|   true if the data map isn't empty and an alpha map was successfully allocated.
 | |
| */
 | |
| bool QCPColorMapData::createAlpha(bool initializeOpaque)
 | |
| {
 | |
|   clearAlpha();
 | |
|   if (isEmpty())
 | |
|     return false;
 | |
|   
 | |
| #ifdef __EXCEPTIONS
 | |
|   try { // 2D arrays get memory intensive fast. So if the allocation fails, at least output debug message
 | |
| #endif
 | |
|     mAlpha = new unsigned char[mKeySize*mValueSize];
 | |
| #ifdef __EXCEPTIONS
 | |
|   } catch (...) { mAlpha = 0; }
 | |
| #endif
 | |
|   if (mAlpha)
 | |
|   {
 | |
|     if (initializeOpaque)
 | |
|       fillAlpha(255);
 | |
|     return true;
 | |
|   } else
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "out of memory for data dimensions "<< mKeySize << "*" << mValueSize;
 | |
|     return false;
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPColorMap
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPColorMap
 | |
|   \brief A plottable representing a two-dimensional color map in a plot.
 | |
| 
 | |
|   \image html QCPColorMap.png
 | |
|   
 | |
|   The data is stored in the class \ref QCPColorMapData, which can be accessed via the data()
 | |
|   method.
 | |
|   
 | |
|   A color map has three dimensions to represent a data point: The \a key dimension, the \a value
 | |
|   dimension and the \a data dimension. As with other plottables such as graphs, \a key and \a value
 | |
|   correspond to two orthogonal axes on the QCustomPlot surface that you specify in the QCPColorMap
 | |
|   constructor. The \a data dimension however is encoded as the color of the point at (\a key, \a
 | |
|   value).
 | |
| 
 | |
|   Set the number of points (or \a cells) in the key/value dimension via \ref
 | |
|   QCPColorMapData::setSize. The plot coordinate range over which these points will be displayed is
 | |
|   specified via \ref QCPColorMapData::setRange. The first cell will be centered on the lower range
 | |
|   boundary and the last cell will be centered on the upper range boundary. The data can be set by
 | |
|   either accessing the cells directly with QCPColorMapData::setCell or by addressing the cells via
 | |
|   their plot coordinates with \ref QCPColorMapData::setData. If possible, you should prefer
 | |
|   setCell, since it doesn't need to do any coordinate transformation and thus performs a bit
 | |
|   better.
 | |
|   
 | |
|   The cell with index (0, 0) is at the bottom left, if the color map uses normal (i.e. not reversed)
 | |
|   key and value axes.
 | |
|   
 | |
|   To show the user which colors correspond to which \a data values, a \ref QCPColorScale is
 | |
|   typically placed to the right of the axis rect. See the documentation there for details on how to
 | |
|   add and use a color scale.
 | |
|   
 | |
|   \section qcpcolormap-appearance Changing the appearance
 | |
|   
 | |
|   The central part of the appearance is the color gradient, which can be specified via \ref
 | |
|   setGradient. See the documentation of \ref QCPColorGradient for details on configuring a color
 | |
|   gradient.
 | |
|   
 | |
|   The \a data range that is mapped to the colors of the gradient can be specified with \ref
 | |
|   setDataRange. To make the data range encompass the whole data set minimum to maximum, call \ref
 | |
|   rescaleDataRange.
 | |
|   
 | |
|   \section qcpcolormap-transparency Transparency
 | |
|   
 | |
|   Transparency in color maps can be achieved by two mechanisms. On one hand, you can specify alpha
 | |
|   values for color stops of the \ref QCPColorGradient, via the regular QColor interface. This will
 | |
|   cause the color map data which gets mapped to colors around those color stops to appear with the
 | |
|   accordingly interpolated transparency.
 | |
|   
 | |
|   On the other hand you can also directly apply an alpha value to each cell independent of its
 | |
|   data, by using the alpha map feature of \ref QCPColorMapData. The relevant methods are \ref
 | |
|   QCPColorMapData::setAlpha, QCPColorMapData::fillAlpha and \ref QCPColorMapData::clearAlpha().
 | |
|   
 | |
|   The two transparencies will be joined together in the plot and otherwise not interfere with each
 | |
|   other. They are mixed in a multiplicative matter, so an alpha of e.g. 50% (128/255) in both modes
 | |
|   simultaneously, will result in a total transparency of 25% (64/255).
 | |
|   
 | |
|   \section qcpcolormap-usage Usage
 | |
|   
 | |
|   Like all data representing objects in QCustomPlot, the QCPColorMap is a plottable
 | |
|   (QCPAbstractPlottable). So the plottable-interface of QCustomPlot applies
 | |
|   (QCustomPlot::plottable, QCustomPlot::removePlottable, etc.)
 | |
|   
 | |
|   Usually, you first create an instance:
 | |
|   \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcolormap-creation-1
 | |
|   which registers it with the QCustomPlot instance of the passed axes. Note that this QCustomPlot instance takes
 | |
|   ownership of the plottable, so do not delete it manually but use QCustomPlot::removePlottable() instead.
 | |
|   The newly created plottable can be modified, e.g.:
 | |
|   \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcolormap-creation-2
 | |
|   
 | |
|   \note The QCPColorMap always displays the data at equal key/value intervals, even if the key or
 | |
|   value axis is set to a logarithmic scaling. If you want to use QCPColorMap with logarithmic axes,
 | |
|   you shouldn't use the \ref QCPColorMapData::setData method as it uses a linear transformation to
 | |
|   determine the cell index. Rather directly access the cell index with \ref
 | |
|   QCPColorMapData::setCell.
 | |
| */
 | |
| 
 | |
| /* start documentation of inline functions */
 | |
| 
 | |
| /*! \fn QCPColorMapData *QCPColorMap::data() const
 | |
|   
 | |
|   Returns a pointer to the internal data storage of type \ref QCPColorMapData. Access this to
 | |
|   modify data points (cells) and the color map key/value range.
 | |
|   
 | |
|   \see setData
 | |
| */
 | |
| 
 | |
| /* end documentation of inline functions */
 | |
| 
 | |
| /* start documentation of signals */
 | |
| 
 | |
| /*! \fn void QCPColorMap::dataRangeChanged(const QCPRange &newRange);
 | |
|   
 | |
|   This signal is emitted when the data range changes.
 | |
|   
 | |
|   \see setDataRange
 | |
| */
 | |
| 
 | |
| /*! \fn void QCPColorMap::dataScaleTypeChanged(QCPAxis::ScaleType scaleType);
 | |
|   
 | |
|   This signal is emitted when the data scale type changes.
 | |
|   
 | |
|   \see setDataScaleType
 | |
| */
 | |
| 
 | |
| /*! \fn void QCPColorMap::gradientChanged(const QCPColorGradient &newGradient);
 | |
|   
 | |
|   This signal is emitted when the gradient changes.
 | |
|   
 | |
|   \see setGradient
 | |
| */
 | |
| 
 | |
| /* end documentation of signals */
 | |
| 
 | |
| /*!
 | |
|   Constructs a color map with the specified \a keyAxis and \a valueAxis.
 | |
|   
 | |
|   The created QCPColorMap is automatically registered with the QCustomPlot instance inferred from
 | |
|   \a keyAxis. This QCustomPlot instance takes ownership of the QCPColorMap, so do not delete it
 | |
|   manually but use QCustomPlot::removePlottable() instead.
 | |
| */
 | |
| QCPColorMap::QCPColorMap(QCPAxis *keyAxis, QCPAxis *valueAxis) :
 | |
|   QCPAbstractPlottable(keyAxis, valueAxis),
 | |
|   mDataScaleType(QCPAxis::stLinear),
 | |
|   mMapData(new QCPColorMapData(10, 10, QCPRange(0, 5), QCPRange(0, 5))),
 | |
|   mInterpolate(true),
 | |
|   mTightBoundary(false),
 | |
|   mMapImageInvalidated(true)
 | |
| {
 | |
| }
 | |
| 
 | |
| QCPColorMap::~QCPColorMap()
 | |
| {
 | |
|   delete mMapData;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Replaces the current \ref data with the provided \a data.
 | |
|   
 | |
|   If \a copy is set to true, the \a data object will only be copied. if false, the color map
 | |
|   takes ownership of the passed data and replaces the internal data pointer with it. This is
 | |
|   significantly faster than copying for large datasets.
 | |
| */
 | |
| void QCPColorMap::setData(QCPColorMapData *data, bool copy)
 | |
| {
 | |
|   if (mMapData == data)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "The data pointer is already in (and owned by) this plottable" << reinterpret_cast<quintptr>(data);
 | |
|     return;
 | |
|   }
 | |
|   if (copy)
 | |
|   {
 | |
|     *mMapData = *data;
 | |
|   } else
 | |
|   {
 | |
|     delete mMapData;
 | |
|     mMapData = data;
 | |
|   }
 | |
|   mMapImageInvalidated = true;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the data range of this color map to \a dataRange. The data range defines which data values
 | |
|   are mapped to the color gradient.
 | |
|   
 | |
|   To make the data range span the full range of the data set, use \ref rescaleDataRange.
 | |
|   
 | |
|   \see QCPColorScale::setDataRange
 | |
| */
 | |
| void QCPColorMap::setDataRange(const QCPRange &dataRange)
 | |
| {
 | |
|   if (!QCPRange::validRange(dataRange)) return;
 | |
|   if (mDataRange.lower != dataRange.lower || mDataRange.upper != dataRange.upper)
 | |
|   {
 | |
|     if (mDataScaleType == QCPAxis::stLogarithmic)
 | |
|       mDataRange = dataRange.sanitizedForLogScale();
 | |
|     else
 | |
|       mDataRange = dataRange.sanitizedForLinScale();
 | |
|     mMapImageInvalidated = true;
 | |
|     emit dataRangeChanged(mDataRange);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets whether the data is correlated with the color gradient linearly or logarithmically.
 | |
|   
 | |
|   \see QCPColorScale::setDataScaleType
 | |
| */
 | |
| void QCPColorMap::setDataScaleType(QCPAxis::ScaleType scaleType)
 | |
| {
 | |
|   if (mDataScaleType != scaleType)
 | |
|   {
 | |
|     mDataScaleType = scaleType;
 | |
|     mMapImageInvalidated = true;
 | |
|     emit dataScaleTypeChanged(mDataScaleType);
 | |
|     if (mDataScaleType == QCPAxis::stLogarithmic)
 | |
|       setDataRange(mDataRange.sanitizedForLogScale());
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the color gradient that is used to represent the data. For more details on how to create an
 | |
|   own gradient or use one of the preset gradients, see \ref QCPColorGradient.
 | |
|   
 | |
|   The colors defined by the gradient will be used to represent data values in the currently set
 | |
|   data range, see \ref setDataRange. Data points that are outside this data range will either be
 | |
|   colored uniformly with the respective gradient boundary color, or the gradient will repeat,
 | |
|   depending on \ref QCPColorGradient::setPeriodic.
 | |
|   
 | |
|   \see QCPColorScale::setGradient
 | |
| */
 | |
| void QCPColorMap::setGradient(const QCPColorGradient &gradient)
 | |
| {
 | |
|   if (mGradient != gradient)
 | |
|   {
 | |
|     mGradient = gradient;
 | |
|     mMapImageInvalidated = true;
 | |
|     emit gradientChanged(mGradient);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets whether the color map image shall use bicubic interpolation when displaying the color map
 | |
|   shrinked or expanded, and not at a 1:1 pixel-to-data scale.
 | |
|   
 | |
|   \image html QCPColorMap-interpolate.png "A 10*10 color map, with interpolation and without interpolation enabled"
 | |
| */
 | |
| void QCPColorMap::setInterpolate(bool enabled)
 | |
| {
 | |
|   mInterpolate = enabled;
 | |
|   mMapImageInvalidated = true; // because oversampling factors might need to change
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets whether the outer most data rows and columns are clipped to the specified key and value
 | |
|   range (see \ref QCPColorMapData::setKeyRange, \ref QCPColorMapData::setValueRange).
 | |
|   
 | |
|   if \a enabled is set to false, the data points at the border of the color map are drawn with the
 | |
|   same width and height as all other data points. Since the data points are represented by
 | |
|   rectangles of one color centered on the data coordinate, this means that the shown color map
 | |
|   extends by half a data point over the specified key/value range in each direction.
 | |
|   
 | |
|   \image html QCPColorMap-tightboundary.png "A color map, with tight boundary enabled and disabled"
 | |
| */
 | |
| void QCPColorMap::setTightBoundary(bool enabled)
 | |
| {
 | |
|   mTightBoundary = enabled;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Associates the color scale \a colorScale with this color map.
 | |
|   
 | |
|   This means that both the color scale and the color map synchronize their gradient, data range and
 | |
|   data scale type (\ref setGradient, \ref setDataRange, \ref setDataScaleType). Multiple color maps
 | |
|   can be associated with one single color scale. This causes the color maps to also synchronize
 | |
|   those properties, via the mutual color scale.
 | |
|   
 | |
|   This function causes the color map to adopt the current color gradient, data range and data scale
 | |
|   type of \a colorScale. After this call, you may change these properties at either the color map
 | |
|   or the color scale, and the setting will be applied to both.
 | |
|   
 | |
|   Pass 0 as \a colorScale to disconnect the color scale from this color map again.
 | |
| */
 | |
| void QCPColorMap::setColorScale(QCPColorScale *colorScale)
 | |
| {
 | |
|   if (mColorScale) // unconnect signals from old color scale
 | |
|   {
 | |
|     disconnect(this, SIGNAL(dataRangeChanged(QCPRange)), mColorScale.data(), SLOT(setDataRange(QCPRange)));
 | |
|     disconnect(this, SIGNAL(dataScaleTypeChanged(QCPAxis::ScaleType)), mColorScale.data(), SLOT(setDataScaleType(QCPAxis::ScaleType)));
 | |
|     disconnect(this, SIGNAL(gradientChanged(QCPColorGradient)), mColorScale.data(), SLOT(setGradient(QCPColorGradient)));
 | |
|     disconnect(mColorScale.data(), SIGNAL(dataRangeChanged(QCPRange)), this, SLOT(setDataRange(QCPRange)));
 | |
|     disconnect(mColorScale.data(), SIGNAL(gradientChanged(QCPColorGradient)), this, SLOT(setGradient(QCPColorGradient)));
 | |
|     disconnect(mColorScale.data(), SIGNAL(dataScaleTypeChanged(QCPAxis::ScaleType)), this, SLOT(setDataScaleType(QCPAxis::ScaleType)));
 | |
|   }
 | |
|   mColorScale = colorScale;
 | |
|   if (mColorScale) // connect signals to new color scale
 | |
|   {
 | |
|     setGradient(mColorScale.data()->gradient());
 | |
|     setDataRange(mColorScale.data()->dataRange());
 | |
|     setDataScaleType(mColorScale.data()->dataScaleType());
 | |
|     connect(this, SIGNAL(dataRangeChanged(QCPRange)), mColorScale.data(), SLOT(setDataRange(QCPRange)));
 | |
|     connect(this, SIGNAL(dataScaleTypeChanged(QCPAxis::ScaleType)), mColorScale.data(), SLOT(setDataScaleType(QCPAxis::ScaleType)));
 | |
|     connect(this, SIGNAL(gradientChanged(QCPColorGradient)), mColorScale.data(), SLOT(setGradient(QCPColorGradient)));
 | |
|     connect(mColorScale.data(), SIGNAL(dataRangeChanged(QCPRange)), this, SLOT(setDataRange(QCPRange)));
 | |
|     connect(mColorScale.data(), SIGNAL(gradientChanged(QCPColorGradient)), this, SLOT(setGradient(QCPColorGradient)));
 | |
|     connect(mColorScale.data(), SIGNAL(dataScaleTypeChanged(QCPAxis::ScaleType)), this, SLOT(setDataScaleType(QCPAxis::ScaleType)));
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the data range (\ref setDataRange) to span the minimum and maximum values that occur in the
 | |
|   current data set. This corresponds to the \ref rescaleKeyAxis or \ref rescaleValueAxis methods,
 | |
|   only for the third data dimension of the color map.
 | |
|   
 | |
|   The minimum and maximum values of the data set are buffered in the internal QCPColorMapData
 | |
|   instance (\ref data). As data is updated via its \ref QCPColorMapData::setCell or \ref
 | |
|   QCPColorMapData::setData, the buffered minimum and maximum values are updated, too. For
 | |
|   performance reasons, however, they are only updated in an expanding fashion. So the buffered
 | |
|   maximum can only increase and the buffered minimum can only decrease. In consequence, changes to
 | |
|   the data that actually lower the maximum of the data set (by overwriting the cell holding the
 | |
|   current maximum with a smaller value), aren't recognized and the buffered maximum overestimates
 | |
|   the true maximum of the data set. The same happens for the buffered minimum. To recalculate the
 | |
|   true minimum and maximum by explicitly looking at each cell, the method
 | |
|   QCPColorMapData::recalculateDataBounds can be used. For convenience, setting the parameter \a
 | |
|   recalculateDataBounds calls this method before setting the data range to the buffered minimum and
 | |
|   maximum.
 | |
|   
 | |
|   \see setDataRange
 | |
| */
 | |
| void QCPColorMap::rescaleDataRange(bool recalculateDataBounds)
 | |
| {
 | |
|   if (recalculateDataBounds)
 | |
|     mMapData->recalculateDataBounds();
 | |
|   setDataRange(mMapData->dataBounds());
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Takes the current appearance of the color map and updates the legend icon, which is used to
 | |
|   represent this color map in the legend (see \ref QCPLegend).
 | |
|   
 | |
|   The \a transformMode specifies whether the rescaling is done by a faster, low quality image
 | |
|   scaling algorithm (Qt::FastTransformation) or by a slower, higher quality algorithm
 | |
|   (Qt::SmoothTransformation).
 | |
|   
 | |
|   The current color map appearance is scaled down to \a thumbSize. Ideally, this should be equal to
 | |
|   the size of the legend icon (see \ref QCPLegend::setIconSize). If it isn't exactly the configured
 | |
|   legend icon size, the thumb will be rescaled during drawing of the legend item.
 | |
|   
 | |
|   \see setDataRange
 | |
| */
 | |
| void QCPColorMap::updateLegendIcon(Qt::TransformationMode transformMode, const QSize &thumbSize)
 | |
| {
 | |
|   if (mMapImage.isNull() && !data()->isEmpty())
 | |
|     updateMapImage(); // try to update map image if it's null (happens if no draw has happened yet)
 | |
|   
 | |
|   if (!mMapImage.isNull()) // might still be null, e.g. if data is empty, so check here again
 | |
|   {
 | |
|     bool mirrorX = (keyAxis()->orientation() == Qt::Horizontal ? keyAxis() : valueAxis())->rangeReversed();
 | |
|     bool mirrorY = (valueAxis()->orientation() == Qt::Vertical ? valueAxis() : keyAxis())->rangeReversed();
 | |
|     mLegendIcon = QPixmap::fromImage(mMapImage.mirrored(mirrorX, mirrorY)).scaled(thumbSize, Qt::KeepAspectRatio, transformMode);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| double QCPColorMap::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
 | |
| {
 | |
|   Q_UNUSED(details)
 | |
|   if ((onlySelectable && mSelectable == QCP::stNone) || mMapData->isEmpty())
 | |
|     return -1;
 | |
|   if (!mKeyAxis || !mValueAxis)
 | |
|     return -1;
 | |
|   
 | |
|   if (mKeyAxis.data()->axisRect()->rect().contains(pos.toPoint()))
 | |
|   {
 | |
|     double posKey, posValue;
 | |
|     pixelsToCoords(pos, posKey, posValue);
 | |
|     if (mMapData->keyRange().contains(posKey) && mMapData->valueRange().contains(posValue))
 | |
|     {
 | |
|       if (details)
 | |
|         details->setValue(QCPDataSelection(QCPDataRange(0, 1))); // temporary solution, to facilitate whole-plottable selection. Replace in future version with segmented 2D selection.
 | |
|       return mParentPlot->selectionTolerance()*0.99;
 | |
|     }
 | |
|   }
 | |
|   return -1;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| QCPRange QCPColorMap::getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain) const
 | |
| {
 | |
|   foundRange = true;
 | |
|   QCPRange result = mMapData->keyRange();
 | |
|   result.normalize();
 | |
|   if (inSignDomain == QCP::sdPositive)
 | |
|   {
 | |
|     if (result.lower <= 0 && result.upper > 0)
 | |
|       result.lower = result.upper*1e-3;
 | |
|     else if (result.lower <= 0 && result.upper <= 0)
 | |
|       foundRange = false;
 | |
|   } else if (inSignDomain == QCP::sdNegative)
 | |
|   {
 | |
|     if (result.upper >= 0 && result.lower < 0)
 | |
|       result.upper = result.lower*1e-3;
 | |
|     else if (result.upper >= 0 && result.lower >= 0)
 | |
|       foundRange = false;
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| QCPRange QCPColorMap::getValueRange(bool &foundRange, QCP::SignDomain inSignDomain, const QCPRange &inKeyRange) const
 | |
| {
 | |
|   if (inKeyRange != QCPRange())
 | |
|   {
 | |
|     if (mMapData->keyRange().upper < inKeyRange.lower || mMapData->keyRange().lower > inKeyRange.upper)
 | |
|     {
 | |
|       foundRange = false;
 | |
|       return QCPRange();
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   foundRange = true;
 | |
|   QCPRange result = mMapData->valueRange();
 | |
|   result.normalize();
 | |
|   if (inSignDomain == QCP::sdPositive)
 | |
|   {
 | |
|     if (result.lower <= 0 && result.upper > 0)
 | |
|       result.lower = result.upper*1e-3;
 | |
|     else if (result.lower <= 0 && result.upper <= 0)
 | |
|       foundRange = false;
 | |
|   } else if (inSignDomain == QCP::sdNegative)
 | |
|   {
 | |
|     if (result.upper >= 0 && result.lower < 0)
 | |
|       result.upper = result.lower*1e-3;
 | |
|     else if (result.upper >= 0 && result.lower >= 0)
 | |
|       foundRange = false;
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Updates the internal map image buffer by going through the internal \ref QCPColorMapData and
 | |
|   turning the data values into color pixels with \ref QCPColorGradient::colorize.
 | |
|   
 | |
|   This method is called by \ref QCPColorMap::draw if either the data has been modified or the map image
 | |
|   has been invalidated for a different reason (e.g. a change of the data range with \ref
 | |
|   setDataRange).
 | |
|   
 | |
|   If the map cell count is low, the image created will be oversampled in order to avoid a
 | |
|   QPainter::drawImage bug which makes inner pixel boundaries jitter when stretch-drawing images
 | |
|   without smooth transform enabled. Accordingly, oversampling isn't performed if \ref
 | |
|   setInterpolate is true.
 | |
| */
 | |
| void QCPColorMap::updateMapImage()
 | |
| {
 | |
|   QCPAxis *keyAxis = mKeyAxis.data();
 | |
|   if (!keyAxis) return;
 | |
|   if (mMapData->isEmpty()) return;
 | |
|   
 | |
|   const QImage::Format format = QImage::Format_ARGB32_Premultiplied;
 | |
|   const int keySize = mMapData->keySize();
 | |
|   const int valueSize = mMapData->valueSize();
 | |
|   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
 | |
|   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
 | |
|   
 | |
|   // resize mMapImage to correct dimensions including possible oversampling factors, according to key/value axes orientation:
 | |
|   if (keyAxis->orientation() == Qt::Horizontal && (mMapImage.width() != keySize*keyOversamplingFactor || mMapImage.height() != valueSize*valueOversamplingFactor))
 | |
|     mMapImage = QImage(QSize(keySize*keyOversamplingFactor, valueSize*valueOversamplingFactor), format);
 | |
|   else if (keyAxis->orientation() == Qt::Vertical && (mMapImage.width() != valueSize*valueOversamplingFactor || mMapImage.height() != keySize*keyOversamplingFactor))
 | |
|     mMapImage = QImage(QSize(valueSize*valueOversamplingFactor, keySize*keyOversamplingFactor), format);
 | |
|   
 | |
|   QImage *localMapImage = &mMapImage; // this is the image on which the colorization operates. Either the final mMapImage, or if we need oversampling, mUndersampledMapImage
 | |
|   if (keyOversamplingFactor > 1 || valueOversamplingFactor > 1)
 | |
|   {
 | |
|     // resize undersampled map image to actual key/value cell sizes:
 | |
|     if (keyAxis->orientation() == Qt::Horizontal && (mUndersampledMapImage.width() != keySize || mUndersampledMapImage.height() != valueSize))
 | |
|       mUndersampledMapImage = QImage(QSize(keySize, valueSize), format);
 | |
|     else if (keyAxis->orientation() == Qt::Vertical && (mUndersampledMapImage.width() != valueSize || mUndersampledMapImage.height() != keySize))
 | |
|       mUndersampledMapImage = QImage(QSize(valueSize, keySize), format);
 | |
|     localMapImage = &mUndersampledMapImage; // make the colorization run on the undersampled image
 | |
|   } else if (!mUndersampledMapImage.isNull())
 | |
|     mUndersampledMapImage = QImage(); // don't need oversampling mechanism anymore (map size has changed) but mUndersampledMapImage still has nonzero size, free it
 | |
|   
 | |
|   const double *rawData = mMapData->mData;
 | |
|   const unsigned char *rawAlpha = mMapData->mAlpha;
 | |
|   if (keyAxis->orientation() == Qt::Horizontal)
 | |
|   {
 | |
|     const int lineCount = valueSize;
 | |
|     const int rowCount = keySize;
 | |
|     for (int line=0; line<lineCount; ++line)
 | |
|     {
 | |
|       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)
 | |
|       if (rawAlpha)
 | |
|         mGradient.colorize(rawData+line*rowCount, rawAlpha+line*rowCount, mDataRange, pixels, rowCount, 1, mDataScaleType==QCPAxis::stLogarithmic);
 | |
|       else
 | |
|         mGradient.colorize(rawData+line*rowCount, mDataRange, pixels, rowCount, 1, mDataScaleType==QCPAxis::stLogarithmic);
 | |
|     }
 | |
|   } else // keyAxis->orientation() == Qt::Vertical
 | |
|   {
 | |
|     const int lineCount = keySize;
 | |
|     const int rowCount = valueSize;
 | |
|     for (int line=0; line<lineCount; ++line)
 | |
|     {
 | |
|       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)
 | |
|       if (rawAlpha)
 | |
|         mGradient.colorize(rawData+line, rawAlpha+line, mDataRange, pixels, rowCount, lineCount, mDataScaleType==QCPAxis::stLogarithmic);
 | |
|       else
 | |
|         mGradient.colorize(rawData+line, mDataRange, pixels, rowCount, lineCount, mDataScaleType==QCPAxis::stLogarithmic);
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   if (keyOversamplingFactor > 1 || valueOversamplingFactor > 1)
 | |
|   {
 | |
|     if (keyAxis->orientation() == Qt::Horizontal)
 | |
|       mMapImage = mUndersampledMapImage.scaled(keySize*keyOversamplingFactor, valueSize*valueOversamplingFactor, Qt::IgnoreAspectRatio, Qt::FastTransformation);
 | |
|     else
 | |
|       mMapImage = mUndersampledMapImage.scaled(valueSize*valueOversamplingFactor, keySize*keyOversamplingFactor, Qt::IgnoreAspectRatio, Qt::FastTransformation);
 | |
|   }
 | |
|   mMapData->mDataModified = false;
 | |
|   mMapImageInvalidated = false;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPColorMap::draw(QCPPainter *painter)
 | |
| {
 | |
|   if (mMapData->isEmpty()) return;
 | |
|   if (!mKeyAxis || !mValueAxis) return;
 | |
|   applyDefaultAntialiasingHint(painter);
 | |
|   
 | |
|   if (mMapData->mDataModified || mMapImageInvalidated)
 | |
|     updateMapImage();
 | |
|   
 | |
|   // use buffer if painting vectorized (PDF):
 | |
|   const bool useBuffer = painter->modes().testFlag(QCPPainter::pmVectorized);
 | |
|   QCPPainter *localPainter = painter; // will be redirected to paint on mapBuffer if painting vectorized
 | |
|   QRectF mapBufferTarget; // the rect in absolute widget coordinates where the visible map portion/buffer will end up in
 | |
|   QPixmap mapBuffer;
 | |
|   if (useBuffer)
 | |
|   {
 | |
|     const double mapBufferPixelRatio = 3; // factor by which DPI is increased in embedded bitmaps
 | |
|     mapBufferTarget = painter->clipRegion().boundingRect();
 | |
|     mapBuffer = QPixmap((mapBufferTarget.size()*mapBufferPixelRatio).toSize());
 | |
|     mapBuffer.fill(Qt::transparent);
 | |
|     localPainter = new QCPPainter(&mapBuffer);
 | |
|     localPainter->scale(mapBufferPixelRatio, mapBufferPixelRatio);
 | |
|     localPainter->translate(-mapBufferTarget.topLeft());
 | |
|   }
 | |
|   
 | |
|   QRectF imageRect = QRectF(coordsToPixels(mMapData->keyRange().lower, mMapData->valueRange().lower),
 | |
|                             coordsToPixels(mMapData->keyRange().upper, mMapData->valueRange().upper)).normalized();
 | |
|   // extend imageRect to contain outer halves/quarters of bordering/cornering pixels (cells are centered on map range boundary):
 | |
|   double halfCellWidth = 0; // in pixels
 | |
|   double halfCellHeight = 0; // in pixels
 | |
|   if (keyAxis()->orientation() == Qt::Horizontal)
 | |
|   {
 | |
|     if (mMapData->keySize() > 1)
 | |
|       halfCellWidth = 0.5*imageRect.width()/(double)(mMapData->keySize()-1);
 | |
|     if (mMapData->valueSize() > 1)
 | |
|       halfCellHeight = 0.5*imageRect.height()/(double)(mMapData->valueSize()-1);
 | |
|   } else // keyAxis orientation is Qt::Vertical
 | |
|   {
 | |
|     if (mMapData->keySize() > 1)
 | |
|       halfCellHeight = 0.5*imageRect.height()/(double)(mMapData->keySize()-1);
 | |
|     if (mMapData->valueSize() > 1)
 | |
|       halfCellWidth = 0.5*imageRect.width()/(double)(mMapData->valueSize()-1);
 | |
|   }
 | |
|   imageRect.adjust(-halfCellWidth, -halfCellHeight, halfCellWidth, halfCellHeight);
 | |
|   const bool mirrorX = (keyAxis()->orientation() == Qt::Horizontal ? keyAxis() : valueAxis())->rangeReversed();
 | |
|   const bool mirrorY = (valueAxis()->orientation() == Qt::Vertical ? valueAxis() : keyAxis())->rangeReversed();
 | |
|   const bool smoothBackup = localPainter->renderHints().testFlag(QPainter::SmoothPixmapTransform);
 | |
|   localPainter->setRenderHint(QPainter::SmoothPixmapTransform, mInterpolate);
 | |
|   QRegion clipBackup;
 | |
|   if (mTightBoundary)
 | |
|   {
 | |
|     clipBackup = localPainter->clipRegion();
 | |
|     QRectF tightClipRect = QRectF(coordsToPixels(mMapData->keyRange().lower, mMapData->valueRange().lower),
 | |
|                                   coordsToPixels(mMapData->keyRange().upper, mMapData->valueRange().upper)).normalized();
 | |
|     localPainter->setClipRect(tightClipRect, Qt::IntersectClip);
 | |
|   }
 | |
|   localPainter->drawImage(imageRect, mMapImage.mirrored(mirrorX, mirrorY));
 | |
|   if (mTightBoundary)
 | |
|     localPainter->setClipRegion(clipBackup);
 | |
|   localPainter->setRenderHint(QPainter::SmoothPixmapTransform, smoothBackup);
 | |
|   
 | |
|   if (useBuffer) // localPainter painted to mapBuffer, so now draw buffer with original painter
 | |
|   {
 | |
|     delete localPainter;
 | |
|     painter->drawPixmap(mapBufferTarget.toRect(), mapBuffer);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPColorMap::drawLegendIcon(QCPPainter *painter, const QRectF &rect) const
 | |
| {
 | |
|   applyDefaultAntialiasingHint(painter);
 | |
|   // draw map thumbnail:
 | |
|   if (!mLegendIcon.isNull())
 | |
|   {
 | |
|     QPixmap scaledIcon = mLegendIcon.scaled(rect.size().toSize(), Qt::KeepAspectRatio, Qt::FastTransformation);
 | |
|     QRectF iconRect = QRectF(0, 0, scaledIcon.width(), scaledIcon.height());
 | |
|     iconRect.moveCenter(rect.center());
 | |
|     painter->drawPixmap(iconRect.topLeft(), scaledIcon);
 | |
|   }
 | |
|   /*
 | |
|   // draw frame:
 | |
|   painter->setBrush(Qt::NoBrush);
 | |
|   painter->setPen(Qt::black);
 | |
|   painter->drawRect(rect.adjusted(1, 1, 0, 0));
 | |
|   */
 | |
| }
 | |
| /* end of 'src/plottables/plottable-colormap.cpp' */
 | |
| 
 | |
| 
 | |
| /* including file 'src/plottables/plottable-financial.cpp', size 42610       */
 | |
| /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPFinancialData
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPFinancialData
 | |
|   \brief Holds the data of one single data point for QCPFinancial.
 | |
|   
 | |
|   The stored data is:
 | |
|   \li \a key: coordinate on the key axis of this data point (this is the \a mainKey and the \a sortKey)
 | |
|   \li \a open: The opening value at the data point (this is the \a mainValue)
 | |
|   \li \a high: The high/maximum value at the data point
 | |
|   \li \a low: The low/minimum value at the data point
 | |
|   \li \a close: The closing value at the data point
 | |
|   
 | |
|   The container for storing multiple data points is \ref QCPFinancialDataContainer. It is a typedef
 | |
|   for \ref QCPDataContainer with \ref QCPFinancialData as the DataType template parameter. See the
 | |
|   documentation there for an explanation regarding the data type's generic methods.
 | |
|   
 | |
|   \see QCPFinancialDataContainer
 | |
| */
 | |
| 
 | |
| /* start documentation of inline functions */
 | |
| 
 | |
| /*! \fn double QCPFinancialData::sortKey() const
 | |
|   
 | |
|   Returns the \a key member of this data point.
 | |
|   
 | |
|   For a general explanation of what this method is good for in the context of the data container,
 | |
|   see the documentation of \ref QCPDataContainer.
 | |
| */
 | |
| 
 | |
| /*! \fn static QCPFinancialData QCPFinancialData::fromSortKey(double sortKey)
 | |
|   
 | |
|   Returns a data point with the specified \a sortKey. All other members are set to zero.
 | |
|   
 | |
|   For a general explanation of what this method is good for in the context of the data container,
 | |
|   see the documentation of \ref QCPDataContainer.
 | |
| */
 | |
| 
 | |
| /*! \fn static static bool QCPFinancialData::sortKeyIsMainKey()
 | |
|   
 | |
|   Since the member \a key is both the data point key coordinate and the data ordering parameter,
 | |
|   this method returns true.
 | |
|   
 | |
|   For a general explanation of what this method is good for in the context of the data container,
 | |
|   see the documentation of \ref QCPDataContainer.
 | |
| */
 | |
| 
 | |
| /*! \fn double QCPFinancialData::mainKey() const
 | |
|   
 | |
|   Returns the \a key member of this data point.
 | |
|   
 | |
|   For a general explanation of what this method is good for in the context of the data container,
 | |
|   see the documentation of \ref QCPDataContainer.
 | |
| */
 | |
| 
 | |
| /*! \fn double QCPFinancialData::mainValue() const
 | |
|   
 | |
|   Returns the \a open member of this data point.
 | |
|   
 | |
|   For a general explanation of what this method is good for in the context of the data container,
 | |
|   see the documentation of \ref QCPDataContainer.
 | |
| */
 | |
| 
 | |
| /*! \fn QCPRange QCPFinancialData::valueRange() const
 | |
|   
 | |
|   Returns a QCPRange spanning from the \a low to the \a high value of this data point.
 | |
|   
 | |
|   For a general explanation of what this method is good for in the context of the data container,
 | |
|   see the documentation of \ref QCPDataContainer.
 | |
| */
 | |
| 
 | |
| /* end documentation of inline functions */
 | |
| 
 | |
| /*!
 | |
|   Constructs a data point with key and all values set to zero.
 | |
| */
 | |
| QCPFinancialData::QCPFinancialData() :
 | |
|   key(0),
 | |
|   open(0),
 | |
|   high(0),
 | |
|   low(0),
 | |
|   close(0)
 | |
| {
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Constructs a data point with the specified \a key and OHLC values.
 | |
| */
 | |
| QCPFinancialData::QCPFinancialData(double key, double open, double high, double low, double close) :
 | |
|   key(key),
 | |
|   open(open),
 | |
|   high(high),
 | |
|   low(low),
 | |
|   close(close)
 | |
| {
 | |
| }
 | |
| 
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPFinancial
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPFinancial
 | |
|   \brief A plottable representing a financial stock chart
 | |
| 
 | |
|   \image html QCPFinancial.png
 | |
| 
 | |
|   This plottable represents time series data binned to certain intervals, mainly used for stock
 | |
|   charts. The two common representations OHLC (Open-High-Low-Close) bars and Candlesticks can be
 | |
|   set via \ref setChartStyle.
 | |
| 
 | |
|   The data is passed via \ref setData as a set of open/high/low/close values at certain keys
 | |
|   (typically times). This means the data must be already binned appropriately. If data is only
 | |
|   available as a series of values (e.g. \a price against \a time), you can use the static
 | |
|   convenience function \ref timeSeriesToOhlc to generate binned OHLC-data which can then be passed
 | |
|   to \ref setData.
 | |
| 
 | |
|   The width of the OHLC bars/candlesticks can be controlled with \ref setWidth and \ref
 | |
|   setWidthType. A typical choice is to set the width type to \ref wtPlotCoords (the default) and
 | |
|   the width to (or slightly less than) one time bin interval width.
 | |
| 
 | |
|   \section qcpfinancial-appearance Changing the appearance
 | |
| 
 | |
|   Charts can be either single- or two-colored (\ref setTwoColored). If set to be single-colored,
 | |
|   lines are drawn with the plottable's pen (\ref setPen) and fills with the brush (\ref setBrush).
 | |
| 
 | |
|   If set to two-colored, positive changes of the value during an interval (\a close >= \a open) are
 | |
|   represented with a different pen and brush than negative changes (\a close < \a open). These can
 | |
|   be configured with \ref setPenPositive, \ref setPenNegative, \ref setBrushPositive, and \ref
 | |
|   setBrushNegative. In two-colored mode, the normal plottable pen/brush is ignored. Upon selection
 | |
|   however, the normal selected pen/brush (provided by the \ref selectionDecorator) is used,
 | |
|   irrespective of whether the chart is single- or two-colored.
 | |
| 
 | |
|   \section qcpfinancial-usage Usage
 | |
| 
 | |
|   Like all data representing objects in QCustomPlot, the QCPFinancial is a plottable
 | |
|   (QCPAbstractPlottable). So the plottable-interface of QCustomPlot applies
 | |
|   (QCustomPlot::plottable, QCustomPlot::removePlottable, etc.)
 | |
| 
 | |
|   Usually, you first create an instance:
 | |
| 
 | |
|   \snippet documentation/doc-code-snippets/mainwindow.cpp qcpfinancial-creation-1
 | |
|   which registers it with the QCustomPlot instance of the passed axes. Note that this QCustomPlot
 | |
|   instance takes ownership of the plottable, so do not delete it manually but use
 | |
|   QCustomPlot::removePlottable() instead. The newly created plottable can be modified, e.g.:
 | |
| 
 | |
|   \snippet documentation/doc-code-snippets/mainwindow.cpp qcpfinancial-creation-2
 | |
|   Here we have used the static helper method \ref timeSeriesToOhlc, to turn a time-price data
 | |
|   series into a 24-hour binned open-high-low-close data series as QCPFinancial uses.
 | |
| */
 | |
| 
 | |
| /* start of documentation of inline functions */
 | |
| 
 | |
| /*! \fn QCPFinancialDataContainer *QCPFinancial::data() const
 | |
|   
 | |
|   Returns a pointer to the internal data storage of type \ref QCPFinancialDataContainer. You may
 | |
|   use it to directly manipulate the data, which may be more convenient and faster than using the
 | |
|   regular \ref setData or \ref addData methods, in certain situations.
 | |
| */
 | |
| 
 | |
| /* end of documentation of inline functions */
 | |
| 
 | |
| /*!
 | |
|   Constructs a financial chart which uses \a keyAxis as its key axis ("x") and \a valueAxis as its value
 | |
|   axis ("y"). \a keyAxis and \a valueAxis must reside in the same QCustomPlot instance and not have
 | |
|   the same orientation. If either of these restrictions is violated, a corresponding message is
 | |
|   printed to the debug output (qDebug), the construction is not aborted, though.
 | |
|   
 | |
|   The created QCPFinancial is automatically registered with the QCustomPlot instance inferred from \a
 | |
|   keyAxis. This QCustomPlot instance takes ownership of the QCPFinancial, so do not delete it manually
 | |
|   but use QCustomPlot::removePlottable() instead.
 | |
| */
 | |
| QCPFinancial::QCPFinancial(QCPAxis *keyAxis, QCPAxis *valueAxis) :
 | |
|   QCPAbstractPlottable1D<QCPFinancialData>(keyAxis, valueAxis),
 | |
|   mChartStyle(csCandlestick),
 | |
|   mWidth(0.5),
 | |
|   mWidthType(wtPlotCoords),
 | |
|   mTwoColored(true),
 | |
|   mBrushPositive(QBrush(QColor(50, 160, 0))),
 | |
|   mBrushNegative(QBrush(QColor(180, 0, 15))),
 | |
|   mPenPositive(QPen(QColor(40, 150, 0))),
 | |
|   mPenNegative(QPen(QColor(170, 5, 5)))
 | |
| {
 | |
|   mSelectionDecorator->setBrush(QBrush(QColor(160, 160, 255)));
 | |
| }
 | |
| 
 | |
| QCPFinancial::~QCPFinancial()
 | |
| {
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Replaces the current data container with the provided \a data container.
 | |
|   
 | |
|   Since a QSharedPointer is used, multiple QCPFinancials may share the same data container safely.
 | |
|   Modifying the data in the container will then affect all financials that share the container.
 | |
|   Sharing can be achieved by simply exchanging the data containers wrapped in shared pointers:
 | |
|   \snippet documentation/doc-code-snippets/mainwindow.cpp qcpfinancial-datasharing-1
 | |
|   
 | |
|   If you do not wish to share containers, but create a copy from an existing container, rather use
 | |
|   the \ref QCPDataContainer<DataType>::set method on the financial's data container directly:
 | |
|   \snippet documentation/doc-code-snippets/mainwindow.cpp qcpfinancial-datasharing-2
 | |
|   
 | |
|   \see addData, timeSeriesToOhlc
 | |
| */
 | |
| void QCPFinancial::setData(QSharedPointer<QCPFinancialDataContainer> data)
 | |
| {
 | |
|   mDataContainer = data;
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Replaces the current data with the provided points in \a keys, \a open, \a high, \a low and \a
 | |
|   close. The provided vectors should have equal length. Else, the number of added points will be
 | |
|   the size of the smallest vector.
 | |
|   
 | |
|   If you can guarantee that the passed data points are sorted by \a keys in ascending order, you
 | |
|   can set \a alreadySorted to true, to improve performance by saving a sorting run.
 | |
|   
 | |
|   \see addData, timeSeriesToOhlc
 | |
| */
 | |
| void QCPFinancial::setData(const QVector<double> &keys, const QVector<double> &open, const QVector<double> &high, const QVector<double> &low, const QVector<double> &close, bool alreadySorted)
 | |
| {
 | |
|   mDataContainer->clear();
 | |
|   addData(keys, open, high, low, close, alreadySorted);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets which representation style shall be used to display the OHLC data.
 | |
| */
 | |
| void QCPFinancial::setChartStyle(QCPFinancial::ChartStyle style)
 | |
| {
 | |
|   mChartStyle = style;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the width of the individual bars/candlesticks to \a width in plot key coordinates.
 | |
|   
 | |
|   A typical choice is to set it to (or slightly less than) one bin interval width.
 | |
| */
 | |
| void QCPFinancial::setWidth(double width)
 | |
| {
 | |
|   mWidth = width;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets how the width of the financial bars is defined. See the documentation of \ref WidthType for
 | |
|   an explanation of the possible values for \a widthType.
 | |
| 
 | |
|   The default value is \ref wtPlotCoords.
 | |
| 
 | |
|   \see setWidth
 | |
| */
 | |
| void QCPFinancial::setWidthType(QCPFinancial::WidthType widthType)
 | |
| {
 | |
|   mWidthType = widthType;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets whether this chart shall contrast positive from negative trends per data point by using two
 | |
|   separate colors to draw the respective bars/candlesticks.
 | |
|   
 | |
|   If \a twoColored is false, the normal plottable's pen and brush are used (\ref setPen, \ref
 | |
|   setBrush).
 | |
|   
 | |
|   \see setPenPositive, setPenNegative, setBrushPositive, setBrushNegative
 | |
| */
 | |
| void QCPFinancial::setTwoColored(bool twoColored)
 | |
| {
 | |
|   mTwoColored = twoColored;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   If \ref setTwoColored is set to true, this function controls the brush that is used to draw fills
 | |
|   of data points with a positive trend (i.e. bars/candlesticks with close >= open).
 | |
|   
 | |
|   If \a twoColored is false, the normal plottable's pen and brush are used (\ref setPen, \ref
 | |
|   setBrush).
 | |
|   
 | |
|   \see setBrushNegative, setPenPositive, setPenNegative
 | |
| */
 | |
| void QCPFinancial::setBrushPositive(const QBrush &brush)
 | |
| {
 | |
|   mBrushPositive = brush;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   If \ref setTwoColored is set to true, this function controls the brush that is used to draw fills
 | |
|   of data points with a negative trend (i.e. bars/candlesticks with close < open).
 | |
|   
 | |
|   If \a twoColored is false, the normal plottable's pen and brush are used (\ref setPen, \ref
 | |
|   setBrush).
 | |
|   
 | |
|   \see setBrushPositive, setPenNegative, setPenPositive
 | |
| */
 | |
| void QCPFinancial::setBrushNegative(const QBrush &brush)
 | |
| {
 | |
|   mBrushNegative = brush;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   If \ref setTwoColored is set to true, this function controls the pen that is used to draw
 | |
|   outlines of data points with a positive trend (i.e. bars/candlesticks with close >= open).
 | |
|   
 | |
|   If \a twoColored is false, the normal plottable's pen and brush are used (\ref setPen, \ref
 | |
|   setBrush).
 | |
|   
 | |
|   \see setPenNegative, setBrushPositive, setBrushNegative
 | |
| */
 | |
| void QCPFinancial::setPenPositive(const QPen &pen)
 | |
| {
 | |
|   mPenPositive = pen;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   If \ref setTwoColored is set to true, this function controls the pen that is used to draw
 | |
|   outlines of data points with a negative trend (i.e. bars/candlesticks with close < open).
 | |
|   
 | |
|   If \a twoColored is false, the normal plottable's pen and brush are used (\ref setPen, \ref
 | |
|   setBrush).
 | |
|   
 | |
|   \see setPenPositive, setBrushNegative, setBrushPositive
 | |
| */
 | |
| void QCPFinancial::setPenNegative(const QPen &pen)
 | |
| {
 | |
|   mPenNegative = pen;
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Adds the provided points in \a keys, \a open, \a high, \a low and \a close to the current data.
 | |
|   The provided vectors should have equal length. Else, the number of added points will be the size
 | |
|   of the smallest vector.
 | |
|   
 | |
|   If you can guarantee that the passed data points are sorted by \a keys in ascending order, you
 | |
|   can set \a alreadySorted to true, to improve performance by saving a sorting run.
 | |
|   
 | |
|   Alternatively, you can also access and modify the data directly via the \ref data method, which
 | |
|   returns a pointer to the internal data container.
 | |
|   
 | |
|   \see timeSeriesToOhlc
 | |
| */
 | |
| void QCPFinancial::addData(const QVector<double> &keys, const QVector<double> &open, const QVector<double> &high, const QVector<double> &low, const QVector<double> &close, bool alreadySorted)
 | |
| {
 | |
|   if (keys.size() != open.size() || open.size() != high.size() || high.size() != low.size() || low.size() != close.size() || close.size() != keys.size())
 | |
|     qDebug() << Q_FUNC_INFO << "keys, open, high, low, close have different sizes:" << keys.size() << open.size() << high.size() << low.size() << close.size();
 | |
|   const int n = qMin(keys.size(), qMin(open.size(), qMin(high.size(), qMin(low.size(), close.size()))));
 | |
|   QVector<QCPFinancialData> tempData(n);
 | |
|   QVector<QCPFinancialData>::iterator it = tempData.begin();
 | |
|   const QVector<QCPFinancialData>::iterator itEnd = tempData.end();
 | |
|   int i = 0;
 | |
|   while (it != itEnd)
 | |
|   {
 | |
|     it->key = keys[i];
 | |
|     it->open = open[i];
 | |
|     it->high = high[i];
 | |
|     it->low = low[i];
 | |
|     it->close = close[i];
 | |
|     ++it;
 | |
|     ++i;
 | |
|   }
 | |
|   mDataContainer->add(tempData, alreadySorted); // don't modify tempData beyond this to prevent copy on write
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
|   
 | |
|   Adds the provided data point as \a key, \a open, \a high, \a low and \a close to the current
 | |
|   data.
 | |
|   
 | |
|   Alternatively, you can also access and modify the data directly via the \ref data method, which
 | |
|   returns a pointer to the internal data container.
 | |
|   
 | |
|   \see timeSeriesToOhlc
 | |
| */
 | |
| void QCPFinancial::addData(double key, double open, double high, double low, double close)
 | |
| {
 | |
|   mDataContainer->add(QCPFinancialData(key, open, high, low, close));
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   \copydoc QCPPlottableInterface1D::selectTestRect
 | |
| */
 | |
| QCPDataSelection QCPFinancial::selectTestRect(const QRectF &rect, bool onlySelectable) const
 | |
| {
 | |
|   QCPDataSelection result;
 | |
|   if ((onlySelectable && mSelectable == QCP::stNone) || mDataContainer->isEmpty())
 | |
|     return result;
 | |
|   if (!mKeyAxis || !mValueAxis)
 | |
|     return result;
 | |
|   
 | |
|   QCPFinancialDataContainer::const_iterator visibleBegin, visibleEnd;
 | |
|   getVisibleDataBounds(visibleBegin, visibleEnd);
 | |
|   
 | |
|   for (QCPFinancialDataContainer::const_iterator it=visibleBegin; it!=visibleEnd; ++it)
 | |
|   {
 | |
|     if (rect.intersects(selectionHitBox(it)))
 | |
|       result.addDataRange(QCPDataRange(it-mDataContainer->constBegin(), it-mDataContainer->constBegin()+1), false);
 | |
|   }
 | |
|   result.simplify();
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| double QCPFinancial::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
 | |
| {
 | |
|   Q_UNUSED(details)
 | |
|   if ((onlySelectable && mSelectable == QCP::stNone) || mDataContainer->isEmpty())
 | |
|     return -1;
 | |
|   if (!mKeyAxis || !mValueAxis)
 | |
|     return -1;
 | |
|   
 | |
|   if (mKeyAxis.data()->axisRect()->rect().contains(pos.toPoint()))
 | |
|   {
 | |
|     // get visible data range:
 | |
|     QCPFinancialDataContainer::const_iterator visibleBegin, visibleEnd;
 | |
|     QCPFinancialDataContainer::const_iterator closestDataPoint = mDataContainer->constEnd();
 | |
|     getVisibleDataBounds(visibleBegin, visibleEnd);
 | |
|     // perform select test according to configured style:
 | |
|     double result = -1;
 | |
|     switch (mChartStyle)
 | |
|     {
 | |
|       case QCPFinancial::csOhlc:
 | |
|         result = ohlcSelectTest(pos, visibleBegin, visibleEnd, closestDataPoint); break;
 | |
|       case QCPFinancial::csCandlestick:
 | |
|         result = candlestickSelectTest(pos, visibleBegin, visibleEnd, closestDataPoint); break;
 | |
|     }
 | |
|     if (details)
 | |
|     {
 | |
|       int pointIndex = closestDataPoint-mDataContainer->constBegin();
 | |
|       details->setValue(QCPDataSelection(QCPDataRange(pointIndex, pointIndex+1)));
 | |
|     }
 | |
|     return result;
 | |
|   }
 | |
|   
 | |
|   return -1;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| QCPRange QCPFinancial::getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain) const
 | |
| {
 | |
|   QCPRange range = mDataContainer->keyRange(foundRange, inSignDomain);
 | |
|   // determine exact range by including width of bars/flags:
 | |
|   if (foundRange)
 | |
|   {
 | |
|     if (inSignDomain != QCP::sdPositive || range.lower-mWidth*0.5 > 0)
 | |
|       range.lower -= mWidth*0.5;
 | |
|     if (inSignDomain != QCP::sdNegative || range.upper+mWidth*0.5 < 0)
 | |
|       range.upper += mWidth*0.5;
 | |
|   }
 | |
|   return range;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| QCPRange QCPFinancial::getValueRange(bool &foundRange, QCP::SignDomain inSignDomain, const QCPRange &inKeyRange) const
 | |
| {
 | |
|   return mDataContainer->valueRange(foundRange, inSignDomain, inKeyRange);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   A convenience function that converts time series data (\a value against \a time) to OHLC binned
 | |
|   data points. The return value can then be passed on to \ref QCPFinancialDataContainer::set(const
 | |
|   QCPFinancialDataContainer&).
 | |
|   
 | |
|   The size of the bins can be controlled with \a timeBinSize in the same units as \a time is given.
 | |
|   For example, if the unit of \a time is seconds and single OHLC/Candlesticks should span an hour
 | |
|   each, set \a timeBinSize to 3600.
 | |
|   
 | |
|   \a timeBinOffset allows to control precisely at what \a time coordinate a bin should start. The
 | |
|   value passed as \a timeBinOffset doesn't need to be in the range encompassed by the \a time keys.
 | |
|   It merely defines the mathematical offset/phase of the bins that will be used to process the
 | |
|   data.
 | |
| */
 | |
| QCPFinancialDataContainer QCPFinancial::timeSeriesToOhlc(const QVector<double> &time, const QVector<double> &value, double timeBinSize, double timeBinOffset)
 | |
| {
 | |
|   QCPFinancialDataContainer data;
 | |
|   int count = qMin(time.size(), value.size());
 | |
|   if (count == 0)
 | |
|     return QCPFinancialDataContainer();
 | |
|   
 | |
|   QCPFinancialData currentBinData(0, value.first(), value.first(), value.first(), value.first());
 | |
|   int currentBinIndex = qFloor((time.first()-timeBinOffset)/timeBinSize+0.5);
 | |
|   for (int i=0; i<count; ++i)
 | |
|   {
 | |
|     int index = qFloor((time.at(i)-timeBinOffset)/timeBinSize+0.5);
 | |
|     if (currentBinIndex == index) // data point still in current bin, extend high/low:
 | |
|     {
 | |
|       if (value.at(i) < currentBinData.low) currentBinData.low = value.at(i);
 | |
|       if (value.at(i) > currentBinData.high) currentBinData.high = value.at(i);
 | |
|       if (i == count-1) // last data point is in current bin, finalize bin:
 | |
|       {
 | |
|         currentBinData.close = value.at(i);
 | |
|         currentBinData.key = timeBinOffset+(index)*timeBinSize;
 | |
|         data.add(currentBinData);
 | |
|       }
 | |
|     } else // data point not anymore in current bin, set close of old and open of new bin, and add old to map:
 | |
|     {
 | |
|       // finalize current bin:
 | |
|       currentBinData.close = value.at(i-1);
 | |
|       currentBinData.key = timeBinOffset+(index-1)*timeBinSize;
 | |
|       data.add(currentBinData);
 | |
|       // start next bin:
 | |
|       currentBinIndex = index;
 | |
|       currentBinData.open = value.at(i);
 | |
|       currentBinData.high = value.at(i);
 | |
|       currentBinData.low = value.at(i);
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   return data;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPFinancial::draw(QCPPainter *painter)
 | |
| {
 | |
|   // get visible data range:
 | |
|   QCPFinancialDataContainer::const_iterator visibleBegin, visibleEnd;
 | |
|   getVisibleDataBounds(visibleBegin, visibleEnd);
 | |
|   
 | |
|   // loop over and draw segments of unselected/selected data:
 | |
|   QList<QCPDataRange> selectedSegments, unselectedSegments, allSegments;
 | |
|   getDataSegments(selectedSegments, unselectedSegments);
 | |
|   allSegments << unselectedSegments << selectedSegments;
 | |
|   for (int i=0; i<allSegments.size(); ++i)
 | |
|   {
 | |
|     bool isSelectedSegment = i >= unselectedSegments.size();
 | |
|     QCPFinancialDataContainer::const_iterator begin = visibleBegin;
 | |
|     QCPFinancialDataContainer::const_iterator end = visibleEnd;
 | |
|     mDataContainer->limitIteratorsToDataRange(begin, end, allSegments.at(i));
 | |
|     if (begin == end)
 | |
|       continue;
 | |
|     
 | |
|     // draw data segment according to configured style:
 | |
|     switch (mChartStyle)
 | |
|     {
 | |
|       case QCPFinancial::csOhlc:
 | |
|         drawOhlcPlot(painter, begin, end, isSelectedSegment); break;
 | |
|       case QCPFinancial::csCandlestick:
 | |
|         drawCandlestickPlot(painter, begin, end, isSelectedSegment); break;
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   // draw other selection decoration that isn't just line/scatter pens and brushes:
 | |
|   if (mSelectionDecorator)
 | |
|     mSelectionDecorator->drawDecoration(painter, selection());
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPFinancial::drawLegendIcon(QCPPainter *painter, const QRectF &rect) const
 | |
| {
 | |
|   painter->setAntialiasing(false); // legend icon especially of csCandlestick looks better without antialiasing
 | |
|   if (mChartStyle == csOhlc)
 | |
|   {
 | |
|     if (mTwoColored)
 | |
|     {
 | |
|       // draw upper left half icon with positive color:
 | |
|       painter->setBrush(mBrushPositive);
 | |
|       painter->setPen(mPenPositive);
 | |
|       painter->setClipRegion(QRegion(QPolygon() << rect.bottomLeft().toPoint() << rect.topRight().toPoint() << rect.topLeft().toPoint()));
 | |
|       painter->drawLine(QLineF(0, rect.height()*0.5, rect.width(), rect.height()*0.5).translated(rect.topLeft()));
 | |
|       painter->drawLine(QLineF(rect.width()*0.2, rect.height()*0.3, rect.width()*0.2, rect.height()*0.5).translated(rect.topLeft()));
 | |
|       painter->drawLine(QLineF(rect.width()*0.8, rect.height()*0.5, rect.width()*0.8, rect.height()*0.7).translated(rect.topLeft()));
 | |
|       // draw bottom right half icon with negative color:
 | |
|       painter->setBrush(mBrushNegative);
 | |
|       painter->setPen(mPenNegative);
 | |
|       painter->setClipRegion(QRegion(QPolygon() << rect.bottomLeft().toPoint() << rect.topRight().toPoint() << rect.bottomRight().toPoint()));
 | |
|       painter->drawLine(QLineF(0, rect.height()*0.5, rect.width(), rect.height()*0.5).translated(rect.topLeft()));
 | |
|       painter->drawLine(QLineF(rect.width()*0.2, rect.height()*0.3, rect.width()*0.2, rect.height()*0.5).translated(rect.topLeft()));
 | |
|       painter->drawLine(QLineF(rect.width()*0.8, rect.height()*0.5, rect.width()*0.8, rect.height()*0.7).translated(rect.topLeft()));
 | |
|     } else
 | |
|     {
 | |
|       painter->setBrush(mBrush);
 | |
|       painter->setPen(mPen);
 | |
|       painter->drawLine(QLineF(0, rect.height()*0.5, rect.width(), rect.height()*0.5).translated(rect.topLeft()));
 | |
|       painter->drawLine(QLineF(rect.width()*0.2, rect.height()*0.3, rect.width()*0.2, rect.height()*0.5).translated(rect.topLeft()));
 | |
|       painter->drawLine(QLineF(rect.width()*0.8, rect.height()*0.5, rect.width()*0.8, rect.height()*0.7).translated(rect.topLeft()));
 | |
|     }
 | |
|   } else if (mChartStyle == csCandlestick)
 | |
|   {
 | |
|     if (mTwoColored)
 | |
|     {
 | |
|       // draw upper left half icon with positive color:
 | |
|       painter->setBrush(mBrushPositive);
 | |
|       painter->setPen(mPenPositive);
 | |
|       painter->setClipRegion(QRegion(QPolygon() << rect.bottomLeft().toPoint() << rect.topRight().toPoint() << rect.topLeft().toPoint()));
 | |
|       painter->drawLine(QLineF(0, rect.height()*0.5, rect.width()*0.25, rect.height()*0.5).translated(rect.topLeft()));
 | |
|       painter->drawLine(QLineF(rect.width()*0.75, rect.height()*0.5, rect.width(), rect.height()*0.5).translated(rect.topLeft()));
 | |
|       painter->drawRect(QRectF(rect.width()*0.25, rect.height()*0.25, rect.width()*0.5, rect.height()*0.5).translated(rect.topLeft()));
 | |
|       // draw bottom right half icon with negative color:
 | |
|       painter->setBrush(mBrushNegative);
 | |
|       painter->setPen(mPenNegative);
 | |
|       painter->setClipRegion(QRegion(QPolygon() << rect.bottomLeft().toPoint() << rect.topRight().toPoint() << rect.bottomRight().toPoint()));
 | |
|       painter->drawLine(QLineF(0, rect.height()*0.5, rect.width()*0.25, rect.height()*0.5).translated(rect.topLeft()));
 | |
|       painter->drawLine(QLineF(rect.width()*0.75, rect.height()*0.5, rect.width(), rect.height()*0.5).translated(rect.topLeft()));
 | |
|       painter->drawRect(QRectF(rect.width()*0.25, rect.height()*0.25, rect.width()*0.5, rect.height()*0.5).translated(rect.topLeft()));
 | |
|     } else
 | |
|     {
 | |
|       painter->setBrush(mBrush);
 | |
|       painter->setPen(mPen);
 | |
|       painter->drawLine(QLineF(0, rect.height()*0.5, rect.width()*0.25, rect.height()*0.5).translated(rect.topLeft()));
 | |
|       painter->drawLine(QLineF(rect.width()*0.75, rect.height()*0.5, rect.width(), rect.height()*0.5).translated(rect.topLeft()));
 | |
|       painter->drawRect(QRectF(rect.width()*0.25, rect.height()*0.25, rect.width()*0.5, rect.height()*0.5).translated(rect.topLeft()));
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Draws the data from \a begin to \a end-1 as OHLC bars with the provided \a painter.
 | |
| 
 | |
|   This method is a helper function for \ref draw. It is used when the chart style is \ref csOhlc.
 | |
| */
 | |
| void QCPFinancial::drawOhlcPlot(QCPPainter *painter, const QCPFinancialDataContainer::const_iterator &begin, const QCPFinancialDataContainer::const_iterator &end, bool isSelected)
 | |
| {
 | |
|   QCPAxis *keyAxis = mKeyAxis.data();
 | |
|   QCPAxis *valueAxis = mValueAxis.data();
 | |
|   if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
 | |
|   
 | |
|   if (keyAxis->orientation() == Qt::Horizontal)
 | |
|   {
 | |
|     for (QCPFinancialDataContainer::const_iterator it = begin; it != end; ++it)
 | |
|     {
 | |
|       if (isSelected && mSelectionDecorator)
 | |
|         mSelectionDecorator->applyPen(painter);
 | |
|       else if (mTwoColored)
 | |
|         painter->setPen(it->close >= it->open ? mPenPositive : mPenNegative);
 | |
|       else
 | |
|         painter->setPen(mPen);
 | |
|       double keyPixel = keyAxis->coordToPixel(it->key);
 | |
|       double openPixel = valueAxis->coordToPixel(it->open);
 | |
|       double closePixel = valueAxis->coordToPixel(it->close);
 | |
|       // draw backbone:
 | |
|       painter->drawLine(QPointF(keyPixel, valueAxis->coordToPixel(it->high)), QPointF(keyPixel, valueAxis->coordToPixel(it->low)));
 | |
|       // draw open:
 | |
|       double pixelWidth = getPixelWidth(it->key, keyPixel); // sign of this makes sure open/close are on correct sides
 | |
|       painter->drawLine(QPointF(keyPixel-pixelWidth, openPixel), QPointF(keyPixel, openPixel));
 | |
|       // draw close:
 | |
|       painter->drawLine(QPointF(keyPixel, closePixel), QPointF(keyPixel+pixelWidth, closePixel));
 | |
|     }
 | |
|   } else
 | |
|   {
 | |
|     for (QCPFinancialDataContainer::const_iterator it = begin; it != end; ++it)
 | |
|     {
 | |
|       if (isSelected && mSelectionDecorator)
 | |
|         mSelectionDecorator->applyPen(painter);
 | |
|       else if (mTwoColored)
 | |
|         painter->setPen(it->close >= it->open ? mPenPositive : mPenNegative);
 | |
|       else
 | |
|         painter->setPen(mPen);
 | |
|       double keyPixel = keyAxis->coordToPixel(it->key);
 | |
|       double openPixel = valueAxis->coordToPixel(it->open);
 | |
|       double closePixel = valueAxis->coordToPixel(it->close);
 | |
|       // draw backbone:
 | |
|       painter->drawLine(QPointF(valueAxis->coordToPixel(it->high), keyPixel), QPointF(valueAxis->coordToPixel(it->low), keyPixel));
 | |
|       // draw open:
 | |
|       double pixelWidth = getPixelWidth(it->key, keyPixel); // sign of this makes sure open/close are on correct sides
 | |
|       painter->drawLine(QPointF(openPixel, keyPixel-pixelWidth), QPointF(openPixel, keyPixel));
 | |
|       // draw close:
 | |
|       painter->drawLine(QPointF(closePixel, keyPixel), QPointF(closePixel, keyPixel+pixelWidth));
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Draws the data from \a begin to \a end-1 as Candlesticks with the provided \a painter.
 | |
| 
 | |
|   This method is a helper function for \ref draw. It is used when the chart style is \ref csCandlestick.
 | |
| */
 | |
| void QCPFinancial::drawCandlestickPlot(QCPPainter *painter, const QCPFinancialDataContainer::const_iterator &begin, const QCPFinancialDataContainer::const_iterator &end, bool isSelected)
 | |
| {
 | |
|   QCPAxis *keyAxis = mKeyAxis.data();
 | |
|   QCPAxis *valueAxis = mValueAxis.data();
 | |
|   if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
 | |
|   
 | |
|   if (keyAxis->orientation() == Qt::Horizontal)
 | |
|   {
 | |
|     for (QCPFinancialDataContainer::const_iterator it = begin; it != end; ++it)
 | |
|     {
 | |
|       if (isSelected && mSelectionDecorator)
 | |
|       {
 | |
|         mSelectionDecorator->applyPen(painter);
 | |
|         mSelectionDecorator->applyBrush(painter);
 | |
|       } else if (mTwoColored)
 | |
|       {
 | |
|         painter->setPen(it->close >= it->open ? mPenPositive : mPenNegative);
 | |
|         painter->setBrush(it->close >= it->open ? mBrushPositive : mBrushNegative);
 | |
|       } else
 | |
|       {
 | |
|         painter->setPen(mPen);
 | |
|         painter->setBrush(mBrush);
 | |
|       }
 | |
|       double keyPixel = keyAxis->coordToPixel(it->key);
 | |
|       double openPixel = valueAxis->coordToPixel(it->open);
 | |
|       double closePixel = valueAxis->coordToPixel(it->close);
 | |
|       // draw high:
 | |
|       painter->drawLine(QPointF(keyPixel, valueAxis->coordToPixel(it->high)), QPointF(keyPixel, valueAxis->coordToPixel(qMax(it->open, it->close))));
 | |
|       // draw low:
 | |
|       painter->drawLine(QPointF(keyPixel, valueAxis->coordToPixel(it->low)), QPointF(keyPixel, valueAxis->coordToPixel(qMin(it->open, it->close))));
 | |
|       // draw open-close box:
 | |
|       double pixelWidth = getPixelWidth(it->key, keyPixel);
 | |
|       painter->drawRect(QRectF(QPointF(keyPixel-pixelWidth, closePixel), QPointF(keyPixel+pixelWidth, openPixel)));
 | |
|     }
 | |
|   } else // keyAxis->orientation() == Qt::Vertical
 | |
|   {
 | |
|     for (QCPFinancialDataContainer::const_iterator it = begin; it != end; ++it)
 | |
|     {
 | |
|       if (isSelected && mSelectionDecorator)
 | |
|       {
 | |
|         mSelectionDecorator->applyPen(painter);
 | |
|         mSelectionDecorator->applyBrush(painter);
 | |
|       } else if (mTwoColored)
 | |
|       {
 | |
|         painter->setPen(it->close >= it->open ? mPenPositive : mPenNegative);
 | |
|         painter->setBrush(it->close >= it->open ? mBrushPositive : mBrushNegative);
 | |
|       } else
 | |
|       {
 | |
|         painter->setPen(mPen);
 | |
|         painter->setBrush(mBrush);
 | |
|       }
 | |
|       double keyPixel = keyAxis->coordToPixel(it->key);
 | |
|       double openPixel = valueAxis->coordToPixel(it->open);
 | |
|       double closePixel = valueAxis->coordToPixel(it->close);
 | |
|       // draw high:
 | |
|       painter->drawLine(QPointF(valueAxis->coordToPixel(it->high), keyPixel), QPointF(valueAxis->coordToPixel(qMax(it->open, it->close)), keyPixel));
 | |
|       // draw low:
 | |
|       painter->drawLine(QPointF(valueAxis->coordToPixel(it->low), keyPixel), QPointF(valueAxis->coordToPixel(qMin(it->open, it->close)), keyPixel));
 | |
|       // draw open-close box:
 | |
|       double pixelWidth = getPixelWidth(it->key, keyPixel);
 | |
|       painter->drawRect(QRectF(QPointF(closePixel, keyPixel-pixelWidth), QPointF(openPixel, keyPixel+pixelWidth)));
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   This function is used to determine the width of the bar at coordinate \a key, according to the
 | |
|   specified width (\ref setWidth) and width type (\ref setWidthType). Provide the pixel position of
 | |
|   \a key in \a keyPixel (because usually this was already calculated via \ref QCPAxis::coordToPixel
 | |
|   when this function is called).
 | |
| 
 | |
|   It returns the number of pixels the bar extends to higher keys, relative to the \a key
 | |
|   coordinate. So with a non-reversed horizontal axis, the return value is positive. With a reversed
 | |
|   horizontal axis, the return value is negative. This is important so the open/close flags on the
 | |
|   \ref csOhlc bar are drawn to the correct side.
 | |
| */
 | |
| double QCPFinancial::getPixelWidth(double key, double keyPixel) const
 | |
| {
 | |
|   double result = 0;
 | |
|   switch (mWidthType)
 | |
|   {
 | |
|     case wtAbsolute:
 | |
|     {
 | |
|       if (mKeyAxis)
 | |
|         result = mWidth*0.5*mKeyAxis.data()->pixelOrientation();
 | |
|       break;
 | |
|     }
 | |
|     case wtAxisRectRatio:
 | |
|     {
 | |
|       if (mKeyAxis && mKeyAxis.data()->axisRect())
 | |
|       {
 | |
|         if (mKeyAxis.data()->orientation() == Qt::Horizontal)
 | |
|           result = mKeyAxis.data()->axisRect()->width()*mWidth*0.5*mKeyAxis.data()->pixelOrientation();
 | |
|         else
 | |
|           result = mKeyAxis.data()->axisRect()->height()*mWidth*0.5*mKeyAxis.data()->pixelOrientation();
 | |
|       } else
 | |
|         qDebug() << Q_FUNC_INFO << "No key axis or axis rect defined";
 | |
|       break;
 | |
|     }
 | |
|     case wtPlotCoords:
 | |
|     {
 | |
|       if (mKeyAxis)
 | |
|         result = mKeyAxis.data()->coordToPixel(key+mWidth*0.5)-keyPixel;
 | |
|       else
 | |
|         qDebug() << Q_FUNC_INFO << "No key axis defined";
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   This method is a helper function for \ref selectTest. It is used to test for selection when the
 | |
|   chart style is \ref csOhlc. It only tests against the data points between \a begin and \a end.
 | |
|   
 | |
|   Like \ref selectTest, this method returns the shortest distance of \a pos to the graphical
 | |
|   representation of the plottable, and \a closestDataPoint will point to the respective data point.
 | |
| */
 | |
| double QCPFinancial::ohlcSelectTest(const QPointF &pos, const QCPFinancialDataContainer::const_iterator &begin, const QCPFinancialDataContainer::const_iterator &end, QCPFinancialDataContainer::const_iterator &closestDataPoint) const
 | |
| {
 | |
|   closestDataPoint = mDataContainer->constEnd();
 | |
|   QCPAxis *keyAxis = mKeyAxis.data();
 | |
|   QCPAxis *valueAxis = mValueAxis.data();
 | |
|   if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return -1; }
 | |
| 
 | |
|   double minDistSqr = std::numeric_limits<double>::max();
 | |
|   if (keyAxis->orientation() == Qt::Horizontal)
 | |
|   {
 | |
|     for (QCPFinancialDataContainer::const_iterator it=begin; it!=end; ++it)
 | |
|     {
 | |
|       double keyPixel = keyAxis->coordToPixel(it->key);
 | |
|       // calculate distance to backbone:
 | |
|       double currentDistSqr = QCPVector2D(pos).distanceSquaredToLine(QCPVector2D(keyPixel, valueAxis->coordToPixel(it->high)), QCPVector2D(keyPixel, valueAxis->coordToPixel(it->low)));
 | |
|       if (currentDistSqr < minDistSqr)
 | |
|       {
 | |
|         minDistSqr = currentDistSqr;
 | |
|         closestDataPoint = it;
 | |
|       }
 | |
|     }
 | |
|   } else // keyAxis->orientation() == Qt::Vertical
 | |
|   {
 | |
|     for (QCPFinancialDataContainer::const_iterator it=begin; it!=end; ++it)
 | |
|     {
 | |
|       double keyPixel = keyAxis->coordToPixel(it->key);
 | |
|       // calculate distance to backbone:
 | |
|       double currentDistSqr = QCPVector2D(pos).distanceSquaredToLine(QCPVector2D(valueAxis->coordToPixel(it->high), keyPixel), QCPVector2D(valueAxis->coordToPixel(it->low), keyPixel));
 | |
|       if (currentDistSqr < minDistSqr)
 | |
|       {
 | |
|         minDistSqr = currentDistSqr;
 | |
|         closestDataPoint = it;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   return qSqrt(minDistSqr);
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   This method is a helper function for \ref selectTest. It is used to test for selection when the
 | |
|   chart style is \ref csCandlestick. It only tests against the data points between \a begin and \a
 | |
|   end.
 | |
|   
 | |
|   Like \ref selectTest, this method returns the shortest distance of \a pos to the graphical
 | |
|   representation of the plottable, and \a closestDataPoint will point to the respective data point.
 | |
| */
 | |
| double QCPFinancial::candlestickSelectTest(const QPointF &pos, const QCPFinancialDataContainer::const_iterator &begin, const QCPFinancialDataContainer::const_iterator &end, QCPFinancialDataContainer::const_iterator &closestDataPoint) const
 | |
| {
 | |
|   closestDataPoint = mDataContainer->constEnd();
 | |
|   QCPAxis *keyAxis = mKeyAxis.data();
 | |
|   QCPAxis *valueAxis = mValueAxis.data();
 | |
|   if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return -1; }
 | |
| 
 | |
|   double minDistSqr = std::numeric_limits<double>::max();
 | |
|   if (keyAxis->orientation() == Qt::Horizontal)
 | |
|   {
 | |
|     for (QCPFinancialDataContainer::const_iterator it=begin; it!=end; ++it)
 | |
|     {
 | |
|       double currentDistSqr;
 | |
|       // determine whether pos is in open-close-box:
 | |
|       QCPRange boxKeyRange(it->key-mWidth*0.5, it->key+mWidth*0.5);
 | |
|       QCPRange boxValueRange(it->close, it->open);
 | |
|       double posKey, posValue;
 | |
|       pixelsToCoords(pos, posKey, posValue);
 | |
|       if (boxKeyRange.contains(posKey) && boxValueRange.contains(posValue)) // is in open-close-box
 | |
|       {
 | |
|         currentDistSqr = mParentPlot->selectionTolerance()*0.99 * mParentPlot->selectionTolerance()*0.99;
 | |
|       } else
 | |
|       {
 | |
|         // calculate distance to high/low lines:
 | |
|         double keyPixel = keyAxis->coordToPixel(it->key);
 | |
|         double highLineDistSqr = QCPVector2D(pos).distanceSquaredToLine(QCPVector2D(keyPixel, valueAxis->coordToPixel(it->high)), QCPVector2D(keyPixel, valueAxis->coordToPixel(qMax(it->open, it->close))));
 | |
|         double lowLineDistSqr = QCPVector2D(pos).distanceSquaredToLine(QCPVector2D(keyPixel, valueAxis->coordToPixel(it->low)), QCPVector2D(keyPixel, valueAxis->coordToPixel(qMin(it->open, it->close))));
 | |
|         currentDistSqr = qMin(highLineDistSqr, lowLineDistSqr);
 | |
|       }
 | |
|       if (currentDistSqr < minDistSqr)
 | |
|       {
 | |
|         minDistSqr = currentDistSqr;
 | |
|         closestDataPoint = it;
 | |
|       }
 | |
|     }
 | |
|   } else // keyAxis->orientation() == Qt::Vertical
 | |
|   {
 | |
|     for (QCPFinancialDataContainer::const_iterator it=begin; it!=end; ++it)
 | |
|     {
 | |
|       double currentDistSqr;
 | |
|       // determine whether pos is in open-close-box:
 | |
|       QCPRange boxKeyRange(it->key-mWidth*0.5, it->key+mWidth*0.5);
 | |
|       QCPRange boxValueRange(it->close, it->open);
 | |
|       double posKey, posValue;
 | |
|       pixelsToCoords(pos, posKey, posValue);
 | |
|       if (boxKeyRange.contains(posKey) && boxValueRange.contains(posValue)) // is in open-close-box
 | |
|       {
 | |
|         currentDistSqr = mParentPlot->selectionTolerance()*0.99 * mParentPlot->selectionTolerance()*0.99;
 | |
|       } else
 | |
|       {
 | |
|         // calculate distance to high/low lines:
 | |
|         double keyPixel = keyAxis->coordToPixel(it->key);
 | |
|         double highLineDistSqr = QCPVector2D(pos).distanceSquaredToLine(QCPVector2D(valueAxis->coordToPixel(it->high), keyPixel), QCPVector2D(valueAxis->coordToPixel(qMax(it->open, it->close)), keyPixel));
 | |
|         double lowLineDistSqr = QCPVector2D(pos).distanceSquaredToLine(QCPVector2D(valueAxis->coordToPixel(it->low), keyPixel), QCPVector2D(valueAxis->coordToPixel(qMin(it->open, it->close)), keyPixel));
 | |
|         currentDistSqr = qMin(highLineDistSqr, lowLineDistSqr);
 | |
|       }
 | |
|       if (currentDistSqr < minDistSqr)
 | |
|       {
 | |
|         minDistSqr = currentDistSqr;
 | |
|         closestDataPoint = it;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   return qSqrt(minDistSqr);
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   called by the drawing methods to determine which data (key) range is visible at the current key
 | |
|   axis range setting, so only that needs to be processed.
 | |
|   
 | |
|   \a begin returns an iterator to the lowest data point that needs to be taken into account when
 | |
|   plotting. Note that in order to get a clean plot all the way to the edge of the axis rect, \a
 | |
|   begin may still be just outside the visible range.
 | |
|   
 | |
|   \a end returns the iterator just above the highest data point that needs to be taken into
 | |
|   account. Same as before, \a end may also lie just outside of the visible range
 | |
|   
 | |
|   if the plottable contains no data, both \a begin and \a end point to \c constEnd.
 | |
| */
 | |
| void QCPFinancial::getVisibleDataBounds(QCPFinancialDataContainer::const_iterator &begin, QCPFinancialDataContainer::const_iterator &end) const
 | |
| {
 | |
|   if (!mKeyAxis)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "invalid key axis";
 | |
|     begin = mDataContainer->constEnd();
 | |
|     end = mDataContainer->constEnd();
 | |
|     return;
 | |
|   }
 | |
|   begin = mDataContainer->findBegin(mKeyAxis.data()->range().lower-mWidth*0.5); // subtract half width of ohlc/candlestick to include partially visible data points
 | |
|   end = mDataContainer->findEnd(mKeyAxis.data()->range().upper+mWidth*0.5); // add half width of ohlc/candlestick to include partially visible data points
 | |
| }
 | |
| 
 | |
| /*!  \internal
 | |
| 
 | |
|   Returns the hit box in pixel coordinates that will be used for data selection with the selection
 | |
|   rect (\ref selectTestRect), of the data point given by \a it.
 | |
| */
 | |
| QRectF QCPFinancial::selectionHitBox(QCPFinancialDataContainer::const_iterator it) const
 | |
| {
 | |
|   QCPAxis *keyAxis = mKeyAxis.data();
 | |
|   QCPAxis *valueAxis = mValueAxis.data();
 | |
|   if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return QRectF(); }
 | |
|   
 | |
|   double keyPixel = keyAxis->coordToPixel(it->key);
 | |
|   double highPixel = valueAxis->coordToPixel(it->high);
 | |
|   double lowPixel = valueAxis->coordToPixel(it->low);
 | |
|   double keyWidthPixels = keyPixel-keyAxis->coordToPixel(it->key-mWidth*0.5);
 | |
|   if (keyAxis->orientation() == Qt::Horizontal)
 | |
|     return QRectF(keyPixel-keyWidthPixels, highPixel, keyWidthPixels*2, lowPixel-highPixel).normalized();
 | |
|   else
 | |
|     return QRectF(highPixel, keyPixel-keyWidthPixels, lowPixel-highPixel, keyWidthPixels*2).normalized();
 | |
| }
 | |
| /* end of 'src/plottables/plottable-financial.cpp' */
 | |
| 
 | |
| 
 | |
| /* including file 'src/plottables/plottable-errorbar.cpp', size 37210        */
 | |
| /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPErrorBarsData
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPErrorBarsData
 | |
|   \brief Holds the data of one single error bar for QCPErrorBars.
 | |
| 
 | |
|   The stored data is:
 | |
|   \li \a errorMinus: how much the error bar extends towards negative coordinates from the data
 | |
|   point position
 | |
|   \li \a errorPlus: how much the error bar extends towards positive coordinates from the data point
 | |
|   position
 | |
| 
 | |
|   The container for storing the error bar information is \ref QCPErrorBarsDataContainer. It is a
 | |
|   typedef for <tt>QVector<\ref QCPErrorBarsData></tt>.
 | |
| 
 | |
|   \see QCPErrorBarsDataContainer
 | |
| */
 | |
| 
 | |
| /*!
 | |
|   Constructs an error bar with errors set to zero.
 | |
| */
 | |
| QCPErrorBarsData::QCPErrorBarsData() :
 | |
|   errorMinus(0),
 | |
|   errorPlus(0)
 | |
| {
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Constructs an error bar with equal \a error in both negative and positive direction.
 | |
| */
 | |
| QCPErrorBarsData::QCPErrorBarsData(double error) :
 | |
|   errorMinus(error),
 | |
|   errorPlus(error)
 | |
| {
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Constructs an error bar with negative and positive errors set to \a errorMinus and \a errorPlus,
 | |
|   respectively.
 | |
| */
 | |
| QCPErrorBarsData::QCPErrorBarsData(double errorMinus, double errorPlus) :
 | |
|   errorMinus(errorMinus),
 | |
|   errorPlus(errorPlus)
 | |
| {
 | |
| }
 | |
| 
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPErrorBars
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPErrorBars
 | |
|   \brief A plottable that adds a set of error bars to other plottables.
 | |
| 
 | |
|   \image html QCPErrorBars.png
 | |
| 
 | |
|   The \ref QCPErrorBars plottable can be attached to other one-dimensional plottables (e.g. \ref
 | |
|   QCPGraph, \ref QCPCurve, \ref QCPBars, etc.) and equips them with error bars.
 | |
| 
 | |
|   Use \ref setDataPlottable to define for which plottable the \ref QCPErrorBars shall display the
 | |
|   error bars. The orientation of the error bars can be controlled with \ref setErrorType.
 | |
| 
 | |
|   By using \ref setData, you can supply the actual error data, either as symmetric error or
 | |
|   plus/minus asymmetric errors. \ref QCPErrorBars only stores the error data. The absolute
 | |
|   key/value position of each error bar will be adopted from the configured data plottable. The
 | |
|   error data of the \ref QCPErrorBars are associated one-to-one via their index to the data points
 | |
|   of the data plottable. You can directly access and manipulate the error bar data via \ref data.
 | |
| 
 | |
|   Set either of the plus/minus errors to NaN (<tt>qQNaN()</tt> or
 | |
|   <tt>std::numeric_limits<double>::quiet_NaN()</tt>) to not show the respective error bar on the data point at
 | |
|   that index.
 | |
| 
 | |
|   \section qcperrorbars-appearance Changing the appearance
 | |
| 
 | |
|   The appearance of the error bars is defined by the pen (\ref setPen), and the width of the
 | |
|   whiskers (\ref setWhiskerWidth). Further, the error bar backbones may leave a gap around the data
 | |
|   point center to prevent that error bars are drawn too close to or even through scatter points.
 | |
|   This gap size can be controlled via \ref setSymbolGap.
 | |
| */
 | |
| 
 | |
| /* start of documentation of inline functions */
 | |
| 
 | |
| /*! \fn QSharedPointer<QCPErrorBarsDataContainer> QCPErrorBars::data() const
 | |
| 
 | |
|   Returns a shared pointer to the internal data storage of type \ref QCPErrorBarsDataContainer. You
 | |
|   may use it to directly manipulate the error values, which may be more convenient and faster than
 | |
|   using the regular \ref setData methods.
 | |
| */
 | |
| 
 | |
| /* end of documentation of inline functions */
 | |
| 
 | |
| /*!
 | |
|   Constructs an error bars plottable which uses \a keyAxis as its key axis ("x") and \a valueAxis as its value
 | |
|   axis ("y"). \a keyAxis and \a valueAxis must reside in the same QCustomPlot instance and not have
 | |
|   the same orientation. If either of these restrictions is violated, a corresponding message is
 | |
|   printed to the debug output (qDebug), the construction is not aborted, though.
 | |
| 
 | |
|   It is also important that the \a keyAxis and \a valueAxis are the same for the error bars
 | |
|   plottable and the data plottable that the error bars shall be drawn on (\ref setDataPlottable).
 | |
| 
 | |
|   The created \ref QCPErrorBars is automatically registered with the QCustomPlot instance inferred
 | |
|   from \a keyAxis. This QCustomPlot instance takes ownership of the \ref QCPErrorBars, so do not
 | |
|   delete it manually but use \ref QCustomPlot::removePlottable() instead.
 | |
| */
 | |
| QCPErrorBars::QCPErrorBars(QCPAxis *keyAxis, QCPAxis *valueAxis) :
 | |
|   QCPAbstractPlottable(keyAxis, valueAxis),
 | |
|   mDataContainer(new QVector<QCPErrorBarsData>),
 | |
|   mErrorType(etValueError),
 | |
|   mWhiskerWidth(9),
 | |
|   mSymbolGap(10)
 | |
| {
 | |
|   setPen(QPen(Qt::black, 0));
 | |
|   setBrush(Qt::NoBrush);
 | |
| }
 | |
| 
 | |
| QCPErrorBars::~QCPErrorBars()
 | |
| {
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
| 
 | |
|   Replaces the current data container with the provided \a data container.
 | |
| 
 | |
|   Since a QSharedPointer is used, multiple \ref QCPErrorBars instances may share the same data
 | |
|   container safely. Modifying the data in the container will then affect all \ref QCPErrorBars
 | |
|   instances that share the container. Sharing can be achieved by simply exchanging the data
 | |
|   containers wrapped in shared pointers:
 | |
|   \snippet documentation/doc-code-snippets/mainwindow.cpp qcperrorbars-datasharing-1
 | |
| 
 | |
|   If you do not wish to share containers, but create a copy from an existing container, assign the
 | |
|   data containers directly:
 | |
|   \snippet documentation/doc-code-snippets/mainwindow.cpp qcperrorbars-datasharing-2
 | |
|   (This uses different notation compared with other plottables, because the \ref QCPErrorBars
 | |
|   uses a \c QVector<QCPErrorBarsData> as its data container, instead of a \ref QCPDataContainer.)
 | |
| 
 | |
|   \see addData
 | |
| */
 | |
| void QCPErrorBars::setData(QSharedPointer<QCPErrorBarsDataContainer> data)
 | |
| {
 | |
|   mDataContainer = data;
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
| 
 | |
|   Sets symmetrical error values as specified in \a error. The errors will be associated one-to-one
 | |
|   by the data point index to the associated data plottable (\ref setDataPlottable).
 | |
| 
 | |
|   You can directly access and manipulate the error bar data via \ref data.
 | |
| 
 | |
|   \see addData
 | |
| */
 | |
| void QCPErrorBars::setData(const QVector<double> &error)
 | |
| {
 | |
|   mDataContainer->clear();
 | |
|   addData(error);
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
| 
 | |
|   Sets asymmetrical errors as specified in \a errorMinus and \a errorPlus. The errors will be
 | |
|   associated one-to-one by the data point index to the associated data plottable (\ref
 | |
|   setDataPlottable).
 | |
| 
 | |
|   You can directly access and manipulate the error bar data via \ref data.
 | |
| 
 | |
|   \see addData
 | |
| */
 | |
| void QCPErrorBars::setData(const QVector<double> &errorMinus, const QVector<double> &errorPlus)
 | |
| {
 | |
|   mDataContainer->clear();
 | |
|   addData(errorMinus, errorPlus);
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the data plottable to which the error bars will be applied. The error values specified e.g.
 | |
|   via \ref setData will be associated one-to-one by the data point index to the data points of \a
 | |
|   plottable. This means that the error bars will adopt the key/value coordinates of the data point
 | |
|   with the same index.
 | |
| 
 | |
|   The passed \a plottable must be a one-dimensional plottable, i.e. it must implement the \ref
 | |
|   QCPPlottableInterface1D. Further, it must not be a \ref QCPErrorBars instance itself. If either
 | |
|   of these restrictions is violated, a corresponding qDebug output is generated, and the data
 | |
|   plottable of this \ref QCPErrorBars instance is set to zero.
 | |
| 
 | |
|   For proper display, care must also be taken that the key and value axes of the \a plottable match
 | |
|   those configured for this \ref QCPErrorBars instance.
 | |
| */
 | |
| void QCPErrorBars::setDataPlottable(QCPAbstractPlottable *plottable)
 | |
| {
 | |
|   if (plottable && qobject_cast<QCPErrorBars*>(plottable))
 | |
|   {
 | |
|     mDataPlottable = 0;
 | |
|     qDebug() << Q_FUNC_INFO << "can't set another QCPErrorBars instance as data plottable";
 | |
|     return;
 | |
|   }
 | |
|   if (plottable && !plottable->interface1D())
 | |
|   {
 | |
|     mDataPlottable = 0;
 | |
|     qDebug() << Q_FUNC_INFO << "passed plottable doesn't implement 1d interface, can't associate with QCPErrorBars";
 | |
|     return;
 | |
|   }
 | |
|   
 | |
|   mDataPlottable = plottable;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets in which orientation the error bars shall appear on the data points. If your data needs both
 | |
|   error dimensions, create two \ref QCPErrorBars with different \a type.
 | |
| */
 | |
| void QCPErrorBars::setErrorType(ErrorType type)
 | |
| {
 | |
|   mErrorType = type;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the width of the whiskers (the short bars at the end of the actual error bar backbones) to
 | |
|   \a pixels.
 | |
| */
 | |
| void QCPErrorBars::setWhiskerWidth(double pixels)
 | |
| {
 | |
|   mWhiskerWidth = pixels;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the gap diameter around the data points that will be left out when drawing the error bar
 | |
|   backbones. This gap prevents that error bars are drawn too close to or even through scatter
 | |
|   points.
 | |
| */
 | |
| void QCPErrorBars::setSymbolGap(double pixels)
 | |
| {
 | |
|   mSymbolGap = pixels;
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
| 
 | |
|   Adds symmetrical error values as specified in \a error. The errors will be associated one-to-one
 | |
|   by the data point index to the associated data plottable (\ref setDataPlottable).
 | |
| 
 | |
|   You can directly access and manipulate the error bar data via \ref data.
 | |
| 
 | |
|   \see setData
 | |
| */
 | |
| void QCPErrorBars::addData(const QVector<double> &error)
 | |
| {
 | |
|   addData(error, error);
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
| 
 | |
|   Adds asymmetrical errors as specified in \a errorMinus and \a errorPlus. The errors will be
 | |
|   associated one-to-one by the data point index to the associated data plottable (\ref
 | |
|   setDataPlottable).
 | |
| 
 | |
|   You can directly access and manipulate the error bar data via \ref data.
 | |
| 
 | |
|   \see setData
 | |
| */
 | |
| void QCPErrorBars::addData(const QVector<double> &errorMinus, const QVector<double> &errorPlus)
 | |
| {
 | |
|   if (errorMinus.size() != errorPlus.size())
 | |
|     qDebug() << Q_FUNC_INFO << "minus and plus error vectors have different sizes:" << errorMinus.size() << errorPlus.size();
 | |
|   const int n = qMin(errorMinus.size(), errorPlus.size());
 | |
|   mDataContainer->reserve(n);
 | |
|   for (int i=0; i<n; ++i)
 | |
|     mDataContainer->append(QCPErrorBarsData(errorMinus.at(i), errorPlus.at(i)));
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
| 
 | |
|   Adds a single symmetrical error bar as specified in \a error. The errors will be associated
 | |
|   one-to-one by the data point index to the associated data plottable (\ref setDataPlottable).
 | |
| 
 | |
|   You can directly access and manipulate the error bar data via \ref data.
 | |
| 
 | |
|   \see setData
 | |
| */
 | |
| void QCPErrorBars::addData(double error)
 | |
| {
 | |
|   mDataContainer->append(QCPErrorBarsData(error));
 | |
| }
 | |
| 
 | |
| /*! \overload
 | |
| 
 | |
|   Adds a single asymmetrical error bar as specified in \a errorMinus and \a errorPlus. The errors
 | |
|   will be associated one-to-one by the data point index to the associated data plottable (\ref
 | |
|   setDataPlottable).
 | |
| 
 | |
|   You can directly access and manipulate the error bar data via \ref data.
 | |
| 
 | |
|   \see setData
 | |
| */
 | |
| void QCPErrorBars::addData(double errorMinus, double errorPlus)
 | |
| {
 | |
|   mDataContainer->append(QCPErrorBarsData(errorMinus, errorPlus));
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| int QCPErrorBars::dataCount() const
 | |
| {
 | |
|   return mDataContainer->size();
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| double QCPErrorBars::dataMainKey(int index) const
 | |
| {
 | |
|   if (mDataPlottable)
 | |
|     return mDataPlottable->interface1D()->dataMainKey(index);
 | |
|   else
 | |
|     qDebug() << Q_FUNC_INFO << "no data plottable set";
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| double QCPErrorBars::dataSortKey(int index) const
 | |
| {
 | |
|   if (mDataPlottable)
 | |
|     return mDataPlottable->interface1D()->dataSortKey(index);
 | |
|   else
 | |
|     qDebug() << Q_FUNC_INFO << "no data plottable set";
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| double QCPErrorBars::dataMainValue(int index) const
 | |
| {
 | |
|   if (mDataPlottable)
 | |
|     return mDataPlottable->interface1D()->dataMainValue(index);
 | |
|   else
 | |
|     qDebug() << Q_FUNC_INFO << "no data plottable set";
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| QCPRange QCPErrorBars::dataValueRange(int index) const
 | |
| {
 | |
|   if (mDataPlottable)
 | |
|   {
 | |
|     const double value = mDataPlottable->interface1D()->dataMainValue(index);
 | |
|     if (index >= 0 && index < mDataContainer->size() && mErrorType == etValueError)
 | |
|       return QCPRange(value-mDataContainer->at(index).errorMinus, value+mDataContainer->at(index).errorPlus);
 | |
|     else
 | |
|       return QCPRange(value, value);
 | |
|   } else
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "no data plottable set";
 | |
|     return QCPRange();
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| QPointF QCPErrorBars::dataPixelPosition(int index) const
 | |
| {
 | |
|   if (mDataPlottable)
 | |
|     return mDataPlottable->interface1D()->dataPixelPosition(index);
 | |
|   else
 | |
|     qDebug() << Q_FUNC_INFO << "no data plottable set";
 | |
|   return QPointF();
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| bool QCPErrorBars::sortKeyIsMainKey() const
 | |
| {
 | |
|   if (mDataPlottable)
 | |
|   {
 | |
|     return mDataPlottable->interface1D()->sortKeyIsMainKey();
 | |
|   } else
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "no data plottable set";
 | |
|     return true;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   \copydoc QCPPlottableInterface1D::selectTestRect
 | |
| */
 | |
| QCPDataSelection QCPErrorBars::selectTestRect(const QRectF &rect, bool onlySelectable) const
 | |
| {
 | |
|   QCPDataSelection result;
 | |
|   if (!mDataPlottable)
 | |
|     return result;
 | |
|   if ((onlySelectable && mSelectable == QCP::stNone) || mDataContainer->isEmpty())
 | |
|     return result;
 | |
|   if (!mKeyAxis || !mValueAxis)
 | |
|     return result;
 | |
|   
 | |
|   QCPErrorBarsDataContainer::const_iterator visibleBegin, visibleEnd;
 | |
|   getVisibleDataBounds(visibleBegin, visibleEnd, QCPDataRange(0, dataCount()));
 | |
|   
 | |
|   QVector<QLineF> backbones, whiskers;
 | |
|   for (QCPErrorBarsDataContainer::const_iterator it=visibleBegin; it!=visibleEnd; ++it)
 | |
|   {
 | |
|     backbones.clear();
 | |
|     whiskers.clear();
 | |
|     getErrorBarLines(it, backbones, whiskers);
 | |
|     for (int i=0; i<backbones.size(); ++i)
 | |
|     {
 | |
|       if (rectIntersectsLine(rect, backbones.at(i)))
 | |
|       {
 | |
|         result.addDataRange(QCPDataRange(it-mDataContainer->constBegin(), it-mDataContainer->constBegin()+1), false);
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   result.simplify();
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| int QCPErrorBars::findBegin(double sortKey, bool expandedRange) const
 | |
| {
 | |
|   if (mDataPlottable)
 | |
|   {
 | |
|     if (mDataContainer->isEmpty())
 | |
|       return 0;
 | |
|     int beginIndex = mDataPlottable->interface1D()->findBegin(sortKey, expandedRange);
 | |
|     if (beginIndex >= mDataContainer->size())
 | |
|       beginIndex = mDataContainer->size()-1;
 | |
|     return beginIndex;
 | |
|   } else
 | |
|     qDebug() << Q_FUNC_INFO << "no data plottable set";
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| int QCPErrorBars::findEnd(double sortKey, bool expandedRange) const
 | |
| {
 | |
|   if (mDataPlottable)
 | |
|   {
 | |
|     if (mDataContainer->isEmpty())
 | |
|       return 0;
 | |
|     int endIndex = mDataPlottable->interface1D()->findEnd(sortKey, expandedRange);
 | |
|     if (endIndex > mDataContainer->size())
 | |
|       endIndex = mDataContainer->size();
 | |
|     return endIndex;
 | |
|   } else
 | |
|     qDebug() << Q_FUNC_INFO << "no data plottable set";
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| double QCPErrorBars::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
 | |
| {
 | |
|   if (!mDataPlottable) return -1;
 | |
|   
 | |
|   if ((onlySelectable && mSelectable == QCP::stNone) || mDataContainer->isEmpty())
 | |
|     return -1;
 | |
|   if (!mKeyAxis || !mValueAxis)
 | |
|     return -1;
 | |
|   
 | |
|   if (mKeyAxis.data()->axisRect()->rect().contains(pos.toPoint()))
 | |
|   {
 | |
|     QCPErrorBarsDataContainer::const_iterator closestDataPoint = mDataContainer->constEnd();
 | |
|     double result = pointDistance(pos, closestDataPoint);
 | |
|     if (details)
 | |
|     {
 | |
|       int pointIndex = closestDataPoint-mDataContainer->constBegin();
 | |
|       details->setValue(QCPDataSelection(QCPDataRange(pointIndex, pointIndex+1)));
 | |
|     }
 | |
|     return result;
 | |
|   } else
 | |
|     return -1;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPErrorBars::draw(QCPPainter *painter)
 | |
| {
 | |
|   if (!mDataPlottable) return;
 | |
|   if (!mKeyAxis || !mValueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
 | |
|   if (mKeyAxis.data()->range().size() <= 0 || mDataContainer->isEmpty()) return;
 | |
|   
 | |
|   // if the sort key isn't the main key, we must check the visibility for each data point/error bar individually
 | |
|   // (getVisibleDataBounds applies range restriction, but otherwise can only return full data range):
 | |
|   bool checkPointVisibility = !mDataPlottable->interface1D()->sortKeyIsMainKey();
 | |
|       
 | |
|     // check data validity if flag set:
 | |
| #ifdef QCUSTOMPLOT_CHECK_DATA
 | |
|   QCPErrorBarsDataContainer::const_iterator it;
 | |
|   for (it = mDataContainer->constBegin(); it != mDataContainer->constEnd(); ++it)
 | |
|   {
 | |
|     if (QCP::isInvalidData(it->errorMinus, it->errorPlus))
 | |
|       qDebug() << Q_FUNC_INFO << "Data point at index" << it-mDataContainer->constBegin() << "invalid." << "Plottable name:" << name();
 | |
|   }
 | |
| #endif
 | |
|   
 | |
|   applyDefaultAntialiasingHint(painter);
 | |
|   painter->setBrush(Qt::NoBrush);
 | |
|   // loop over and draw segments of unselected/selected data:
 | |
|   QList<QCPDataRange> selectedSegments, unselectedSegments, allSegments;
 | |
|   getDataSegments(selectedSegments, unselectedSegments);
 | |
|   allSegments << unselectedSegments << selectedSegments;
 | |
|   QVector<QLineF> backbones, whiskers;
 | |
|   for (int i=0; i<allSegments.size(); ++i)
 | |
|   {
 | |
|     QCPErrorBarsDataContainer::const_iterator begin, end;
 | |
|     getVisibleDataBounds(begin, end, allSegments.at(i));
 | |
|     if (begin == end)
 | |
|       continue;
 | |
|     
 | |
|     bool isSelectedSegment = i >= unselectedSegments.size();
 | |
|     if (isSelectedSegment && mSelectionDecorator)
 | |
|       mSelectionDecorator->applyPen(painter);
 | |
|     else
 | |
|       painter->setPen(mPen);
 | |
|     if (painter->pen().capStyle() == Qt::SquareCap)
 | |
|     {
 | |
|       QPen capFixPen(painter->pen());
 | |
|       capFixPen.setCapStyle(Qt::FlatCap);
 | |
|       painter->setPen(capFixPen);
 | |
|     }
 | |
|     backbones.clear();
 | |
|     whiskers.clear();
 | |
|     for (QCPErrorBarsDataContainer::const_iterator it=begin; it!=end; ++it)
 | |
|     {
 | |
|       if (!checkPointVisibility || errorBarVisible(it-mDataContainer->constBegin()))
 | |
|         getErrorBarLines(it, backbones, whiskers);
 | |
|     }
 | |
|     painter->drawLines(backbones);
 | |
|     painter->drawLines(whiskers);
 | |
|   }
 | |
|   
 | |
|   // draw other selection decoration that isn't just line/scatter pens and brushes:
 | |
|   if (mSelectionDecorator)
 | |
|     mSelectionDecorator->drawDecoration(painter, selection());
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPErrorBars::drawLegendIcon(QCPPainter *painter, const QRectF &rect) const
 | |
| {
 | |
|   applyDefaultAntialiasingHint(painter);
 | |
|   painter->setPen(mPen);
 | |
|   if (mErrorType == etValueError && mValueAxis && mValueAxis->orientation() == Qt::Vertical)
 | |
|   {
 | |
|     painter->drawLine(QLineF(rect.center().x(), rect.top()+2, rect.center().x(), rect.bottom()-1));
 | |
|     painter->drawLine(QLineF(rect.center().x()-4, rect.top()+2, rect.center().x()+4, rect.top()+2));
 | |
|     painter->drawLine(QLineF(rect.center().x()-4, rect.bottom()-1, rect.center().x()+4, rect.bottom()-1));
 | |
|   } else
 | |
|   {
 | |
|     painter->drawLine(QLineF(rect.left()+2, rect.center().y(), rect.right()-2, rect.center().y()));
 | |
|     painter->drawLine(QLineF(rect.left()+2, rect.center().y()-4, rect.left()+2, rect.center().y()+4));
 | |
|     painter->drawLine(QLineF(rect.right()-2, rect.center().y()-4, rect.right()-2, rect.center().y()+4));
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| QCPRange QCPErrorBars::getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain) const
 | |
| {
 | |
|   if (!mDataPlottable)
 | |
|   {
 | |
|     foundRange = false;
 | |
|     return QCPRange();
 | |
|   }
 | |
|   
 | |
|   QCPRange range;
 | |
|   bool haveLower = false;
 | |
|   bool haveUpper = false;
 | |
|   QCPErrorBarsDataContainer::const_iterator it;
 | |
|   for (it = mDataContainer->constBegin(); it != mDataContainer->constEnd(); ++it)
 | |
|   {
 | |
|     if (mErrorType == etValueError)
 | |
|     {
 | |
|       // error bar doesn't extend in key dimension (except whisker but we ignore that here), so only use data point center
 | |
|       const double current = mDataPlottable->interface1D()->dataMainKey(it-mDataContainer->constBegin());
 | |
|       if (qIsNaN(current)) continue;
 | |
|       if (inSignDomain == QCP::sdBoth || (inSignDomain == QCP::sdNegative && current < 0) || (inSignDomain == QCP::sdPositive && current > 0))
 | |
|       {
 | |
|         if (current < range.lower || !haveLower)
 | |
|         {
 | |
|           range.lower = current;
 | |
|           haveLower = true;
 | |
|         }
 | |
|         if (current > range.upper || !haveUpper)
 | |
|         {
 | |
|           range.upper = current;
 | |
|           haveUpper = true;
 | |
|         }
 | |
|       }
 | |
|     } else // mErrorType == etKeyError
 | |
|     {
 | |
|       const double dataKey = mDataPlottable->interface1D()->dataMainKey(it-mDataContainer->constBegin());
 | |
|       if (qIsNaN(dataKey)) continue;
 | |
|       // plus error:
 | |
|       double current = dataKey + (qIsNaN(it->errorPlus) ? 0 : it->errorPlus);
 | |
|       if (inSignDomain == QCP::sdBoth || (inSignDomain == QCP::sdNegative && current < 0) || (inSignDomain == QCP::sdPositive && current > 0))
 | |
|       {
 | |
|         if (current > range.upper || !haveUpper)
 | |
|         {
 | |
|           range.upper = current;
 | |
|           haveUpper = true;
 | |
|         }
 | |
|       }
 | |
|       // minus error:
 | |
|       current = dataKey - (qIsNaN(it->errorMinus) ? 0 : it->errorMinus);
 | |
|       if (inSignDomain == QCP::sdBoth || (inSignDomain == QCP::sdNegative && current < 0) || (inSignDomain == QCP::sdPositive && current > 0))
 | |
|       {
 | |
|         if (current < range.lower || !haveLower)
 | |
|         {
 | |
|           range.lower = current;
 | |
|           haveLower = true;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   if (haveUpper && !haveLower)
 | |
|   {
 | |
|     range.lower = range.upper;
 | |
|     haveLower = true;
 | |
|   } else if (haveLower && !haveUpper)
 | |
|   {
 | |
|     range.upper = range.lower;
 | |
|     haveUpper = true;
 | |
|   }
 | |
|   
 | |
|   foundRange = haveLower && haveUpper;
 | |
|   return range;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| QCPRange QCPErrorBars::getValueRange(bool &foundRange, QCP::SignDomain inSignDomain, const QCPRange &inKeyRange) const
 | |
| {
 | |
|   if (!mDataPlottable)
 | |
|   {
 | |
|     foundRange = false;
 | |
|     return QCPRange();
 | |
|   }
 | |
|   
 | |
|   QCPRange range;
 | |
|   const bool restrictKeyRange = inKeyRange != QCPRange();
 | |
|   bool haveLower = false;
 | |
|   bool haveUpper = false;
 | |
|   QCPErrorBarsDataContainer::const_iterator itBegin = mDataContainer->constBegin();
 | |
|   QCPErrorBarsDataContainer::const_iterator itEnd = mDataContainer->constEnd();
 | |
|   if (mDataPlottable->interface1D()->sortKeyIsMainKey() && restrictKeyRange)
 | |
|   {
 | |
|     itBegin = mDataContainer->constBegin()+findBegin(inKeyRange.lower);
 | |
|     itEnd = mDataContainer->constBegin()+findEnd(inKeyRange.upper);
 | |
|   }
 | |
|   for (QCPErrorBarsDataContainer::const_iterator it = itBegin; it != itEnd; ++it)
 | |
|   {
 | |
|     if (restrictKeyRange)
 | |
|     {
 | |
|       const double dataKey = mDataPlottable->interface1D()->dataMainKey(it-mDataContainer->constBegin());
 | |
|       if (dataKey < inKeyRange.lower || dataKey > inKeyRange.upper)
 | |
|         continue;
 | |
|     }
 | |
|     if (mErrorType == etValueError)
 | |
|     {
 | |
|       const double dataValue = mDataPlottable->interface1D()->dataMainValue(it-mDataContainer->constBegin());
 | |
|       if (qIsNaN(dataValue)) continue;
 | |
|       // plus error:
 | |
|       double current = dataValue + (qIsNaN(it->errorPlus) ? 0 : it->errorPlus);
 | |
|       if (inSignDomain == QCP::sdBoth || (inSignDomain == QCP::sdNegative && current < 0) || (inSignDomain == QCP::sdPositive && current > 0))
 | |
|       {
 | |
|         if (current > range.upper || !haveUpper)
 | |
|         {
 | |
|           range.upper = current;
 | |
|           haveUpper = true;
 | |
|         }
 | |
|       }
 | |
|       // minus error:
 | |
|       current = dataValue - (qIsNaN(it->errorMinus) ? 0 : it->errorMinus);
 | |
|       if (inSignDomain == QCP::sdBoth || (inSignDomain == QCP::sdNegative && current < 0) || (inSignDomain == QCP::sdPositive && current > 0))
 | |
|       {
 | |
|         if (current < range.lower || !haveLower)
 | |
|         {
 | |
|           range.lower = current;
 | |
|           haveLower = true;
 | |
|         }
 | |
|       }
 | |
|     } else // mErrorType == etKeyError
 | |
|     {
 | |
|       // error bar doesn't extend in value dimension (except whisker but we ignore that here), so only use data point center
 | |
|       const double current = mDataPlottable->interface1D()->dataMainValue(it-mDataContainer->constBegin());
 | |
|       if (qIsNaN(current)) continue;
 | |
|       if (inSignDomain == QCP::sdBoth || (inSignDomain == QCP::sdNegative && current < 0) || (inSignDomain == QCP::sdPositive && current > 0))
 | |
|       {
 | |
|         if (current < range.lower || !haveLower)
 | |
|         {
 | |
|           range.lower = current;
 | |
|           haveLower = true;
 | |
|         }
 | |
|         if (current > range.upper || !haveUpper)
 | |
|         {
 | |
|           range.upper = current;
 | |
|           haveUpper = true;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   if (haveUpper && !haveLower)
 | |
|   {
 | |
|     range.lower = range.upper;
 | |
|     haveLower = true;
 | |
|   } else if (haveLower && !haveUpper)
 | |
|   {
 | |
|     range.upper = range.lower;
 | |
|     haveUpper = true;
 | |
|   }
 | |
|   
 | |
|   foundRange = haveLower && haveUpper;
 | |
|   return range;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Calculates the lines that make up the error bar belonging to the data point \a it.
 | |
| 
 | |
|   The resulting lines are added to \a backbones and \a whiskers. The vectors are not cleared, so
 | |
|   calling this method with different \a it but the same \a backbones and \a whiskers allows to
 | |
|   accumulate lines for multiple data points.
 | |
| 
 | |
|   This method assumes that \a it is a valid iterator within the bounds of this \ref QCPErrorBars
 | |
|   instance and within the bounds of the associated data plottable.
 | |
| */
 | |
| void QCPErrorBars::getErrorBarLines(QCPErrorBarsDataContainer::const_iterator it, QVector<QLineF> &backbones, QVector<QLineF> &whiskers) const
 | |
| {
 | |
|   if (!mDataPlottable) return;
 | |
|   
 | |
|   int index = it-mDataContainer->constBegin();
 | |
|   QPointF centerPixel = mDataPlottable->interface1D()->dataPixelPosition(index);
 | |
|   if (qIsNaN(centerPixel.x()) || qIsNaN(centerPixel.y()))
 | |
|     return;
 | |
|   QCPAxis *errorAxis = mErrorType == etValueError ? mValueAxis : mKeyAxis;
 | |
|   QCPAxis *orthoAxis = mErrorType == etValueError ? mKeyAxis : mValueAxis;
 | |
|   const double centerErrorAxisPixel = errorAxis->orientation() == Qt::Horizontal ? centerPixel.x() : centerPixel.y();
 | |
|   const double centerOrthoAxisPixel = orthoAxis->orientation() == Qt::Horizontal ? centerPixel.x() : centerPixel.y();
 | |
|   const double centerErrorAxisCoord = errorAxis->pixelToCoord(centerErrorAxisPixel); // depending on plottable, this might be different from just mDataPlottable->interface1D()->dataMainKey/Value
 | |
|   const double symbolGap = mSymbolGap*0.5*errorAxis->pixelOrientation();
 | |
|   // plus error:
 | |
|   double errorStart, errorEnd;
 | |
|   if (!qIsNaN(it->errorPlus))
 | |
|   {
 | |
|     errorStart = centerErrorAxisPixel+symbolGap;
 | |
|     errorEnd = errorAxis->coordToPixel(centerErrorAxisCoord+it->errorPlus);
 | |
|     if (errorAxis->orientation() == Qt::Vertical)
 | |
|     {
 | |
|       if ((errorStart > errorEnd) != errorAxis->rangeReversed())
 | |
|         backbones.append(QLineF(centerOrthoAxisPixel, errorStart, centerOrthoAxisPixel, errorEnd));
 | |
|       whiskers.append(QLineF(centerOrthoAxisPixel-mWhiskerWidth*0.5, errorEnd, centerOrthoAxisPixel+mWhiskerWidth*0.5, errorEnd));
 | |
|     } else
 | |
|     {
 | |
|       if ((errorStart < errorEnd) != errorAxis->rangeReversed())
 | |
|         backbones.append(QLineF(errorStart, centerOrthoAxisPixel, errorEnd, centerOrthoAxisPixel));
 | |
|       whiskers.append(QLineF(errorEnd, centerOrthoAxisPixel-mWhiskerWidth*0.5, errorEnd, centerOrthoAxisPixel+mWhiskerWidth*0.5));
 | |
|     }
 | |
|   }
 | |
|   // minus error:
 | |
|   if (!qIsNaN(it->errorMinus))
 | |
|   {
 | |
|     errorStart = centerErrorAxisPixel-symbolGap;
 | |
|     errorEnd = errorAxis->coordToPixel(centerErrorAxisCoord-it->errorMinus);
 | |
|     if (errorAxis->orientation() == Qt::Vertical)
 | |
|     {
 | |
|       if ((errorStart < errorEnd) != errorAxis->rangeReversed())
 | |
|         backbones.append(QLineF(centerOrthoAxisPixel, errorStart, centerOrthoAxisPixel, errorEnd));
 | |
|       whiskers.append(QLineF(centerOrthoAxisPixel-mWhiskerWidth*0.5, errorEnd, centerOrthoAxisPixel+mWhiskerWidth*0.5, errorEnd));
 | |
|     } else
 | |
|     {
 | |
|       if ((errorStart > errorEnd) != errorAxis->rangeReversed())
 | |
|         backbones.append(QLineF(errorStart, centerOrthoAxisPixel, errorEnd, centerOrthoAxisPixel));
 | |
|       whiskers.append(QLineF(errorEnd, centerOrthoAxisPixel-mWhiskerWidth*0.5, errorEnd, centerOrthoAxisPixel+mWhiskerWidth*0.5));
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   This method outputs the currently visible data range via \a begin and \a end. The returned range
 | |
|   will also never exceed \a rangeRestriction.
 | |
| 
 | |
|   Since error bars with type \ref etKeyError may extend to arbitrarily positive and negative key
 | |
|   coordinates relative to their data point key, this method checks all outer error bars whether
 | |
|   they truly don't reach into the visible portion of the axis rect, by calling \ref
 | |
|   errorBarVisible. On the other hand error bars with type \ref etValueError that are associated
 | |
|   with data plottables whose sort key is equal to the main key (see \ref qcpdatacontainer-datatype
 | |
|   "QCPDataContainer DataType") can be handled very efficiently by finding the visible range of
 | |
|   error bars through binary search (\ref QCPPlottableInterface1D::findBegin and \ref
 | |
|   QCPPlottableInterface1D::findEnd).
 | |
| 
 | |
|   If the plottable's sort key is not equal to the main key, this method returns the full data
 | |
|   range, only restricted by \a rangeRestriction. Drawing optimization then has to be done on a
 | |
|   point-by-point basis in the \ref draw method.
 | |
| */
 | |
| void QCPErrorBars::getVisibleDataBounds(QCPErrorBarsDataContainer::const_iterator &begin, QCPErrorBarsDataContainer::const_iterator &end, const QCPDataRange &rangeRestriction) const
 | |
| {
 | |
|   QCPAxis *keyAxis = mKeyAxis.data();
 | |
|   QCPAxis *valueAxis = mValueAxis.data();
 | |
|   if (!keyAxis || !valueAxis)
 | |
|   {
 | |
|     qDebug() << Q_FUNC_INFO << "invalid key or value axis";
 | |
|     end = mDataContainer->constEnd();
 | |
|     begin = end;
 | |
|     return;
 | |
|   }
 | |
|   if (!mDataPlottable || rangeRestriction.isEmpty())
 | |
|   {
 | |
|     end = mDataContainer->constEnd();
 | |
|     begin = end;
 | |
|     return;
 | |
|   }
 | |
|   if (!mDataPlottable->interface1D()->sortKeyIsMainKey())
 | |
|   {
 | |
|     // if the sort key isn't the main key, it's not possible to find a contiguous range of visible
 | |
|     // data points, so this method then only applies the range restriction and otherwise returns
 | |
|     // the full data range. Visibility checks must be done on a per-datapoin-basis during drawing
 | |
|     QCPDataRange dataRange(0, mDataContainer->size());
 | |
|     dataRange = dataRange.bounded(rangeRestriction);
 | |
|     begin = mDataContainer->constBegin()+dataRange.begin();
 | |
|     end = mDataContainer->constBegin()+dataRange.end();
 | |
|     return;
 | |
|   }
 | |
|   
 | |
|   // get visible data range via interface from data plottable, and then restrict to available error data points:
 | |
|   const int n = qMin(mDataContainer->size(), mDataPlottable->interface1D()->dataCount());
 | |
|   int beginIndex = mDataPlottable->interface1D()->findBegin(keyAxis->range().lower);
 | |
|   int endIndex = mDataPlottable->interface1D()->findEnd(keyAxis->range().upper);
 | |
|   int i = beginIndex;
 | |
|   while (i > 0 && i < n && i > rangeRestriction.begin())
 | |
|   {
 | |
|     if (errorBarVisible(i))
 | |
|       beginIndex = i;
 | |
|     --i;
 | |
|   }
 | |
|   i = endIndex;
 | |
|   while (i >= 0 && i < n && i < rangeRestriction.end())
 | |
|   {
 | |
|     if (errorBarVisible(i))
 | |
|       endIndex = i+1;
 | |
|     ++i;
 | |
|   }
 | |
|   QCPDataRange dataRange(beginIndex, endIndex);
 | |
|   dataRange = dataRange.bounded(rangeRestriction.bounded(QCPDataRange(0, mDataContainer->size())));
 | |
|   begin = mDataContainer->constBegin()+dataRange.begin();
 | |
|   end = mDataContainer->constBegin()+dataRange.end();
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Calculates the minimum distance in pixels the error bars' representation has from the given \a
 | |
|   pixelPoint. This is used to determine whether the error bar was clicked or not, e.g. in \ref
 | |
|   selectTest. The closest data point to \a pixelPoint is returned in \a closestData.
 | |
| */
 | |
| double QCPErrorBars::pointDistance(const QPointF &pixelPoint, QCPErrorBarsDataContainer::const_iterator &closestData) const
 | |
| {
 | |
|   closestData = mDataContainer->constEnd();
 | |
|   if (!mDataPlottable || mDataContainer->isEmpty())
 | |
|     return -1.0;
 | |
|   
 | |
|   QCPErrorBarsDataContainer::const_iterator begin, end;
 | |
|   getVisibleDataBounds(begin, end, QCPDataRange(0, dataCount()));
 | |
|   
 | |
|   // calculate minimum distances to error backbones (whiskers are ignored for speed) and find closestData iterator:
 | |
|   double minDistSqr = std::numeric_limits<double>::max();
 | |
|   QVector<QLineF> backbones, whiskers;
 | |
|   for (QCPErrorBarsDataContainer::const_iterator it=begin; it!=end; ++it)
 | |
|   {
 | |
|     getErrorBarLines(it, backbones, whiskers);
 | |
|     for (int i=0; i<backbones.size(); ++i)
 | |
|     {
 | |
|       const double currentDistSqr = QCPVector2D(pixelPoint).distanceSquaredToLine(backbones.at(i));
 | |
|       if (currentDistSqr < minDistSqr)
 | |
|       {
 | |
|         minDistSqr = currentDistSqr;
 | |
|         closestData = it;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   return qSqrt(minDistSqr);
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   \note This method is identical to \ref QCPAbstractPlottable1D::getDataSegments but needs to be
 | |
|   reproduced here since the \ref QCPErrorBars plottable, as a special case that doesn't have its
 | |
|   own key/value data coordinates, doesn't derive from \ref QCPAbstractPlottable1D. See the
 | |
|   documentation there for details.
 | |
| */
 | |
| void QCPErrorBars::getDataSegments(QList<QCPDataRange> &selectedSegments, QList<QCPDataRange> &unselectedSegments) const
 | |
| {
 | |
|   selectedSegments.clear();
 | |
|   unselectedSegments.clear();
 | |
|   if (mSelectable == QCP::stWhole) // stWhole selection type draws the entire plottable with selected style if mSelection isn't empty
 | |
|   {
 | |
|     if (selected())
 | |
|       selectedSegments << QCPDataRange(0, dataCount());
 | |
|     else
 | |
|       unselectedSegments << QCPDataRange(0, dataCount());
 | |
|   } else
 | |
|   {
 | |
|     QCPDataSelection sel(selection());
 | |
|     sel.simplify();
 | |
|     selectedSegments = sel.dataRanges();
 | |
|     unselectedSegments = sel.inverse(QCPDataRange(0, dataCount())).dataRanges();
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Returns whether the error bar at the specified \a index is visible within the current key axis
 | |
|   range.
 | |
| 
 | |
|   This method assumes for performance reasons without checking that the key axis, the value axis,
 | |
|   and the data plottable (\ref setDataPlottable) are not zero and that \a index is within valid
 | |
|   bounds of this \ref QCPErrorBars instance and the bounds of the data plottable.
 | |
| */
 | |
| bool QCPErrorBars::errorBarVisible(int index) const
 | |
| {
 | |
|   QPointF centerPixel = mDataPlottable->interface1D()->dataPixelPosition(index);
 | |
|   const double centerKeyPixel = mKeyAxis->orientation() == Qt::Horizontal ? centerPixel.x() : centerPixel.y();
 | |
|   if (qIsNaN(centerKeyPixel))
 | |
|     return false;
 | |
|   
 | |
|   double keyMin, keyMax;
 | |
|   if (mErrorType == etKeyError)
 | |
|   {
 | |
|     const double centerKey = mKeyAxis->pixelToCoord(centerKeyPixel);
 | |
|     const double errorPlus = mDataContainer->at(index).errorPlus;
 | |
|     const double errorMinus = mDataContainer->at(index).errorMinus;
 | |
|     keyMax = centerKey+(qIsNaN(errorPlus) ? 0 : errorPlus);
 | |
|     keyMin = centerKey-(qIsNaN(errorMinus) ? 0 : errorMinus);
 | |
|   } else // mErrorType == etValueError
 | |
|   {
 | |
|     keyMax = mKeyAxis->pixelToCoord(centerKeyPixel+mWhiskerWidth*0.5*mKeyAxis->pixelOrientation());
 | |
|     keyMin = mKeyAxis->pixelToCoord(centerKeyPixel-mWhiskerWidth*0.5*mKeyAxis->pixelOrientation());
 | |
|   }
 | |
|   return ((keyMax > mKeyAxis->range().lower) && (keyMin < mKeyAxis->range().upper));
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Returns whether \a line intersects (or is contained in) \a pixelRect.
 | |
| 
 | |
|   \a line is assumed to be either perfectly horizontal or perfectly vertical, as is the case for
 | |
|   error bar lines.
 | |
| */
 | |
| bool QCPErrorBars::rectIntersectsLine(const QRectF &pixelRect, const QLineF &line) const
 | |
| {
 | |
|   if (pixelRect.left() > line.x1() && pixelRect.left() > line.x2())
 | |
|     return false;
 | |
|   else if (pixelRect.right() < line.x1() && pixelRect.right() < line.x2())
 | |
|     return false;
 | |
|   else if (pixelRect.top() > line.y1() && pixelRect.top() > line.y2())
 | |
|     return false;
 | |
|   else if (pixelRect.bottom() < line.y1() && pixelRect.bottom() < line.y2())
 | |
|     return false;
 | |
|   else
 | |
|     return true;
 | |
| }
 | |
| /* end of 'src/plottables/plottable-errorbar.cpp' */
 | |
| 
 | |
| 
 | |
| /* including file 'src/items/item-straightline.cpp', size 7592               */
 | |
| /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPItemStraightLine
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPItemStraightLine
 | |
|   \brief A straight line that spans infinitely in both directions
 | |
| 
 | |
|   \image html QCPItemStraightLine.png "Straight line example. Blue dotted circles are anchors, solid blue discs are positions."
 | |
| 
 | |
|   It has two positions, \a point1 and \a point2, which define the straight line.
 | |
| */
 | |
| 
 | |
| /*!
 | |
|   Creates a straight line item and sets default values.
 | |
|   
 | |
|   The created item is automatically registered with \a parentPlot. This QCustomPlot instance takes
 | |
|   ownership of the item, so do not delete it manually but use QCustomPlot::removeItem() instead.
 | |
| */
 | |
| QCPItemStraightLine::QCPItemStraightLine(QCustomPlot *parentPlot) :
 | |
|   QCPAbstractItem(parentPlot),
 | |
|   point1(createPosition(QLatin1String("point1"))),
 | |
|   point2(createPosition(QLatin1String("point2")))
 | |
| {
 | |
|   point1->setCoords(0, 0);
 | |
|   point2->setCoords(1, 1);
 | |
|   
 | |
|   setPen(QPen(Qt::black));
 | |
|   setSelectedPen(QPen(Qt::blue,2));
 | |
| }
 | |
| 
 | |
| QCPItemStraightLine::~QCPItemStraightLine()
 | |
| {
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the pen that will be used to draw the line
 | |
|   
 | |
|   \see setSelectedPen
 | |
| */
 | |
| void QCPItemStraightLine::setPen(const QPen &pen)
 | |
| {
 | |
|   mPen = pen;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the pen that will be used to draw the line when selected
 | |
|   
 | |
|   \see setPen, setSelected
 | |
| */
 | |
| void QCPItemStraightLine::setSelectedPen(const QPen &pen)
 | |
| {
 | |
|   mSelectedPen = pen;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| double QCPItemStraightLine::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
 | |
| {
 | |
|   Q_UNUSED(details)
 | |
|   if (onlySelectable && !mSelectable)
 | |
|     return -1;
 | |
|   
 | |
|   return QCPVector2D(pos).distanceToStraightLine(point1->pixelPosition(), point2->pixelPosition()-point1->pixelPosition());
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPItemStraightLine::draw(QCPPainter *painter)
 | |
| {
 | |
|   QCPVector2D start(point1->pixelPosition());
 | |
|   QCPVector2D end(point2->pixelPosition());
 | |
|   // get visible segment of straight line inside clipRect:
 | |
|   double clipPad = mainPen().widthF();
 | |
|   QLineF line = getRectClippedStraightLine(start, end-start, clipRect().adjusted(-clipPad, -clipPad, clipPad, clipPad));
 | |
|   // paint visible segment, if existent:
 | |
|   if (!line.isNull())
 | |
|   {
 | |
|     painter->setPen(mainPen());
 | |
|     painter->drawLine(line);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Returns the section of the straight line defined by \a base and direction vector \a
 | |
|   vec, that is visible in the specified \a rect.
 | |
|   
 | |
|   This is a helper function for \ref draw.
 | |
| */
 | |
| QLineF QCPItemStraightLine::getRectClippedStraightLine(const QCPVector2D &base, const QCPVector2D &vec, const QRect &rect) const
 | |
| {
 | |
|   double bx, by;
 | |
|   double gamma;
 | |
|   QLineF result;
 | |
|   if (vec.x() == 0 && vec.y() == 0)
 | |
|     return result;
 | |
|   if (qFuzzyIsNull(vec.x())) // line is vertical
 | |
|   {
 | |
|     // check top of rect:
 | |
|     bx = rect.left();
 | |
|     by = rect.top();
 | |
|     gamma = base.x()-bx + (by-base.y())*vec.x()/vec.y();
 | |
|     if (gamma >= 0 && gamma <= rect.width())
 | |
|       result.setLine(bx+gamma, rect.top(), bx+gamma, rect.bottom()); // no need to check bottom because we know line is vertical
 | |
|   } else if (qFuzzyIsNull(vec.y())) // line is horizontal
 | |
|   {
 | |
|     // check left of rect:
 | |
|     bx = rect.left();
 | |
|     by = rect.top();
 | |
|     gamma = base.y()-by + (bx-base.x())*vec.y()/vec.x();
 | |
|     if (gamma >= 0 && gamma <= rect.height())
 | |
|       result.setLine(rect.left(), by+gamma, rect.right(), by+gamma); // no need to check right because we know line is horizontal
 | |
|   } else // line is skewed
 | |
|   {
 | |
|     QList<QCPVector2D> pointVectors;
 | |
|     // check top of rect:
 | |
|     bx = rect.left();
 | |
|     by = rect.top();
 | |
|     gamma = base.x()-bx + (by-base.y())*vec.x()/vec.y();
 | |
|     if (gamma >= 0 && gamma <= rect.width())
 | |
|       pointVectors.append(QCPVector2D(bx+gamma, by));
 | |
|     // check bottom of rect:
 | |
|     bx = rect.left();
 | |
|     by = rect.bottom();
 | |
|     gamma = base.x()-bx + (by-base.y())*vec.x()/vec.y();
 | |
|     if (gamma >= 0 && gamma <= rect.width())
 | |
|       pointVectors.append(QCPVector2D(bx+gamma, by));
 | |
|     // check left of rect:
 | |
|     bx = rect.left();
 | |
|     by = rect.top();
 | |
|     gamma = base.y()-by + (bx-base.x())*vec.y()/vec.x();
 | |
|     if (gamma >= 0 && gamma <= rect.height())
 | |
|       pointVectors.append(QCPVector2D(bx, by+gamma));
 | |
|     // check right of rect:
 | |
|     bx = rect.right();
 | |
|     by = rect.top();
 | |
|     gamma = base.y()-by + (bx-base.x())*vec.y()/vec.x();
 | |
|     if (gamma >= 0 && gamma <= rect.height())
 | |
|       pointVectors.append(QCPVector2D(bx, by+gamma));
 | |
|     
 | |
|     // evaluate points:
 | |
|     if (pointVectors.size() == 2)
 | |
|     {
 | |
|       result.setPoints(pointVectors.at(0).toPointF(), pointVectors.at(1).toPointF());
 | |
|     } else if (pointVectors.size() > 2)
 | |
|     {
 | |
|       // line probably goes through corner of rect, and we got two points there. single out the point pair with greatest distance:
 | |
|       double distSqrMax = 0;
 | |
|       QCPVector2D pv1, pv2;
 | |
|       for (int i=0; i<pointVectors.size()-1; ++i)
 | |
|       {
 | |
|         for (int k=i+1; k<pointVectors.size(); ++k)
 | |
|         {
 | |
|           double distSqr = (pointVectors.at(i)-pointVectors.at(k)).lengthSquared();
 | |
|           if (distSqr > distSqrMax)
 | |
|           {
 | |
|             pv1 = pointVectors.at(i);
 | |
|             pv2 = pointVectors.at(k);
 | |
|             distSqrMax = distSqr;
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|       result.setPoints(pv1.toPointF(), pv2.toPointF());
 | |
|     }
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Returns the pen that should be used for drawing lines. Returns mPen when the
 | |
|   item is not selected and mSelectedPen when it is.
 | |
| */
 | |
| QPen QCPItemStraightLine::mainPen() const
 | |
| {
 | |
|   return mSelected ? mSelectedPen : mPen;
 | |
| }
 | |
| /* end of 'src/items/item-straightline.cpp' */
 | |
| 
 | |
| 
 | |
| /* including file 'src/items/item-line.cpp', size 8498                       */
 | |
| /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPItemLine
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPItemLine
 | |
|   \brief A line from one point to another
 | |
| 
 | |
|   \image html QCPItemLine.png "Line example. Blue dotted circles are anchors, solid blue discs are positions."
 | |
| 
 | |
|   It has two positions, \a start and \a end, which define the end points of the line.
 | |
|   
 | |
|   With \ref setHead and \ref setTail you may set different line ending styles, e.g. to create an arrow.
 | |
| */
 | |
| 
 | |
| /*!
 | |
|   Creates a line item and sets default values.
 | |
|   
 | |
|   The created item is automatically registered with \a parentPlot. This QCustomPlot instance takes
 | |
|   ownership of the item, so do not delete it manually but use QCustomPlot::removeItem() instead.
 | |
| */
 | |
| QCPItemLine::QCPItemLine(QCustomPlot *parentPlot) :
 | |
|   QCPAbstractItem(parentPlot),
 | |
|   start(createPosition(QLatin1String("start"))),
 | |
|   end(createPosition(QLatin1String("end")))
 | |
| {
 | |
|   start->setCoords(0, 0);
 | |
|   end->setCoords(1, 1);
 | |
|   
 | |
|   setPen(QPen(Qt::black));
 | |
|   setSelectedPen(QPen(Qt::blue,2));
 | |
| }
 | |
| 
 | |
| QCPItemLine::~QCPItemLine()
 | |
| {
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the pen that will be used to draw the line
 | |
|   
 | |
|   \see setSelectedPen
 | |
| */
 | |
| void QCPItemLine::setPen(const QPen &pen)
 | |
| {
 | |
|   mPen = pen;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the pen that will be used to draw the line when selected
 | |
|   
 | |
|   \see setPen, setSelected
 | |
| */
 | |
| void QCPItemLine::setSelectedPen(const QPen &pen)
 | |
| {
 | |
|   mSelectedPen = pen;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the line ending style of the head. The head corresponds to the \a end position.
 | |
|   
 | |
|   Note that due to the overloaded QCPLineEnding constructor, you may directly specify
 | |
|   a QCPLineEnding::EndingStyle here, e.g. \code setHead(QCPLineEnding::esSpikeArrow) \endcode
 | |
|   
 | |
|   \see setTail
 | |
| */
 | |
| void QCPItemLine::setHead(const QCPLineEnding &head)
 | |
| {
 | |
|   mHead = head;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the line ending style of the tail. The tail corresponds to the \a start position.
 | |
|   
 | |
|   Note that due to the overloaded QCPLineEnding constructor, you may directly specify
 | |
|   a QCPLineEnding::EndingStyle here, e.g. \code setTail(QCPLineEnding::esSpikeArrow) \endcode
 | |
|   
 | |
|   \see setHead
 | |
| */
 | |
| void QCPItemLine::setTail(const QCPLineEnding &tail)
 | |
| {
 | |
|   mTail = tail;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| double QCPItemLine::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
 | |
| {
 | |
|   Q_UNUSED(details)
 | |
|   if (onlySelectable && !mSelectable)
 | |
|     return -1;
 | |
|   
 | |
|   return qSqrt(QCPVector2D(pos).distanceSquaredToLine(start->pixelPosition(), end->pixelPosition()));
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPItemLine::draw(QCPPainter *painter)
 | |
| {
 | |
|   QCPVector2D startVec(start->pixelPosition());
 | |
|   QCPVector2D endVec(end->pixelPosition());
 | |
|   if (qFuzzyIsNull((startVec-endVec).lengthSquared()))
 | |
|     return;
 | |
|   // get visible segment of straight line inside clipRect:
 | |
|   double clipPad = qMax(mHead.boundingDistance(), mTail.boundingDistance());
 | |
|   clipPad = qMax(clipPad, (double)mainPen().widthF());
 | |
|   QLineF line = getRectClippedLine(startVec, endVec, clipRect().adjusted(-clipPad, -clipPad, clipPad, clipPad));
 | |
|   // paint visible segment, if existent:
 | |
|   if (!line.isNull())
 | |
|   {
 | |
|     painter->setPen(mainPen());
 | |
|     painter->drawLine(line);
 | |
|     painter->setBrush(Qt::SolidPattern);
 | |
|     if (mTail.style() != QCPLineEnding::esNone)
 | |
|       mTail.draw(painter, startVec, startVec-endVec);
 | |
|     if (mHead.style() != QCPLineEnding::esNone)
 | |
|       mHead.draw(painter, endVec, endVec-startVec);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Returns the section of the line defined by \a start and \a end, that is visible in the specified
 | |
|   \a rect.
 | |
|   
 | |
|   This is a helper function for \ref draw.
 | |
| */
 | |
| QLineF QCPItemLine::getRectClippedLine(const QCPVector2D &start, const QCPVector2D &end, const QRect &rect) const
 | |
| {
 | |
|   bool containsStart = rect.contains(start.x(), start.y());
 | |
|   bool containsEnd = rect.contains(end.x(), end.y());
 | |
|   if (containsStart && containsEnd)
 | |
|     return QLineF(start.toPointF(), end.toPointF());
 | |
|   
 | |
|   QCPVector2D base = start;
 | |
|   QCPVector2D vec = end-start;
 | |
|   double bx, by;
 | |
|   double gamma, mu;
 | |
|   QLineF result;
 | |
|   QList<QCPVector2D> pointVectors;
 | |
| 
 | |
|   if (!qFuzzyIsNull(vec.y())) // line is not horizontal
 | |
|   {
 | |
|     // check top of rect:
 | |
|     bx = rect.left();
 | |
|     by = rect.top();
 | |
|     mu = (by-base.y())/vec.y();
 | |
|     if (mu >= 0 && mu <= 1)
 | |
|     {
 | |
|       gamma = base.x()-bx + mu*vec.x();
 | |
|       if (gamma >= 0 && gamma <= rect.width())
 | |
|         pointVectors.append(QCPVector2D(bx+gamma, by));
 | |
|     }
 | |
|     // check bottom of rect:
 | |
|     bx = rect.left();
 | |
|     by = rect.bottom();
 | |
|     mu = (by-base.y())/vec.y();
 | |
|     if (mu >= 0 && mu <= 1)
 | |
|     {
 | |
|       gamma = base.x()-bx + mu*vec.x();
 | |
|       if (gamma >= 0 && gamma <= rect.width())
 | |
|         pointVectors.append(QCPVector2D(bx+gamma, by));
 | |
|     }
 | |
|   }
 | |
|   if (!qFuzzyIsNull(vec.x())) // line is not vertical
 | |
|   {
 | |
|     // check left of rect:
 | |
|     bx = rect.left();
 | |
|     by = rect.top();
 | |
|     mu = (bx-base.x())/vec.x();
 | |
|     if (mu >= 0 && mu <= 1)
 | |
|     {
 | |
|       gamma = base.y()-by + mu*vec.y();
 | |
|       if (gamma >= 0 && gamma <= rect.height())
 | |
|         pointVectors.append(QCPVector2D(bx, by+gamma));
 | |
|     }
 | |
|     // check right of rect:
 | |
|     bx = rect.right();
 | |
|     by = rect.top();
 | |
|     mu = (bx-base.x())/vec.x();
 | |
|     if (mu >= 0 && mu <= 1)
 | |
|     {
 | |
|       gamma = base.y()-by + mu*vec.y();
 | |
|       if (gamma >= 0 && gamma <= rect.height())
 | |
|         pointVectors.append(QCPVector2D(bx, by+gamma));
 | |
|     }
 | |
|   }
 | |
|   
 | |
|   if (containsStart)
 | |
|     pointVectors.append(start);
 | |
|   if (containsEnd)
 | |
|     pointVectors.append(end);
 | |
|   
 | |
|   // evaluate points:
 | |
|   if (pointVectors.size() == 2)
 | |
|   {
 | |
|     result.setPoints(pointVectors.at(0).toPointF(), pointVectors.at(1).toPointF());
 | |
|   } else if (pointVectors.size() > 2)
 | |
|   {
 | |
|     // line probably goes through corner of rect, and we got two points there. single out the point pair with greatest distance:
 | |
|     double distSqrMax = 0;
 | |
|     QCPVector2D pv1, pv2;
 | |
|     for (int i=0; i<pointVectors.size()-1; ++i)
 | |
|     {
 | |
|       for (int k=i+1; k<pointVectors.size(); ++k)
 | |
|       {
 | |
|         double distSqr = (pointVectors.at(i)-pointVectors.at(k)).lengthSquared();
 | |
|         if (distSqr > distSqrMax)
 | |
|         {
 | |
|           pv1 = pointVectors.at(i);
 | |
|           pv2 = pointVectors.at(k);
 | |
|           distSqrMax = distSqr;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     result.setPoints(pv1.toPointF(), pv2.toPointF());
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Returns the pen that should be used for drawing lines. Returns mPen when the
 | |
|   item is not selected and mSelectedPen when it is.
 | |
| */
 | |
| QPen QCPItemLine::mainPen() const
 | |
| {
 | |
|   return mSelected ? mSelectedPen : mPen;
 | |
| }
 | |
| /* end of 'src/items/item-line.cpp' */
 | |
| 
 | |
| 
 | |
| /* including file 'src/items/item-curve.cpp', size 7159                      */
 | |
| /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPItemCurve
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPItemCurve
 | |
|   \brief A curved line from one point to another
 | |
| 
 | |
|   \image html QCPItemCurve.png "Curve example. Blue dotted circles are anchors, solid blue discs are positions."
 | |
| 
 | |
|   It has four positions, \a start and \a end, which define the end points of the line, and two
 | |
|   control points which define the direction the line exits from the start and the direction from
 | |
|   which it approaches the end: \a startDir and \a endDir.
 | |
|   
 | |
|   With \ref setHead and \ref setTail you may set different line ending styles, e.g. to create an
 | |
|   arrow.
 | |
|   
 | |
|   Often it is desirable for the control points to stay at fixed relative positions to the start/end
 | |
|   point. This can be achieved by setting the parent anchor e.g. of \a startDir simply to \a start,
 | |
|   and then specify the desired pixel offset with QCPItemPosition::setCoords on \a startDir.
 | |
| */
 | |
| 
 | |
| /*!
 | |
|   Creates a curve item and sets default values.
 | |
|   
 | |
|   The created item is automatically registered with \a parentPlot. This QCustomPlot instance takes
 | |
|   ownership of the item, so do not delete it manually but use QCustomPlot::removeItem() instead.
 | |
| */
 | |
| QCPItemCurve::QCPItemCurve(QCustomPlot *parentPlot) :
 | |
|   QCPAbstractItem(parentPlot),
 | |
|   start(createPosition(QLatin1String("start"))),
 | |
|   startDir(createPosition(QLatin1String("startDir"))),
 | |
|   endDir(createPosition(QLatin1String("endDir"))),
 | |
|   end(createPosition(QLatin1String("end")))
 | |
| {
 | |
|   start->setCoords(0, 0);
 | |
|   startDir->setCoords(0.5, 0);
 | |
|   endDir->setCoords(0, 0.5);
 | |
|   end->setCoords(1, 1);
 | |
|   
 | |
|   setPen(QPen(Qt::black));
 | |
|   setSelectedPen(QPen(Qt::blue,2));
 | |
| }
 | |
| 
 | |
| QCPItemCurve::~QCPItemCurve()
 | |
| {
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the pen that will be used to draw the line
 | |
|   
 | |
|   \see setSelectedPen
 | |
| */
 | |
| void QCPItemCurve::setPen(const QPen &pen)
 | |
| {
 | |
|   mPen = pen;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the pen that will be used to draw the line when selected
 | |
|   
 | |
|   \see setPen, setSelected
 | |
| */
 | |
| void QCPItemCurve::setSelectedPen(const QPen &pen)
 | |
| {
 | |
|   mSelectedPen = pen;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the line ending style of the head. The head corresponds to the \a end position.
 | |
|   
 | |
|   Note that due to the overloaded QCPLineEnding constructor, you may directly specify
 | |
|   a QCPLineEnding::EndingStyle here, e.g. \code setHead(QCPLineEnding::esSpikeArrow) \endcode
 | |
|   
 | |
|   \see setTail
 | |
| */
 | |
| void QCPItemCurve::setHead(const QCPLineEnding &head)
 | |
| {
 | |
|   mHead = head;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the line ending style of the tail. The tail corresponds to the \a start position.
 | |
|   
 | |
|   Note that due to the overloaded QCPLineEnding constructor, you may directly specify
 | |
|   a QCPLineEnding::EndingStyle here, e.g. \code setTail(QCPLineEnding::esSpikeArrow) \endcode
 | |
|   
 | |
|   \see setHead
 | |
| */
 | |
| void QCPItemCurve::setTail(const QCPLineEnding &tail)
 | |
| {
 | |
|   mTail = tail;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| double QCPItemCurve::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
 | |
| {
 | |
|   Q_UNUSED(details)
 | |
|   if (onlySelectable && !mSelectable)
 | |
|     return -1;
 | |
|   
 | |
|   QPointF startVec(start->pixelPosition());
 | |
|   QPointF startDirVec(startDir->pixelPosition());
 | |
|   QPointF endDirVec(endDir->pixelPosition());
 | |
|   QPointF endVec(end->pixelPosition());
 | |
| 
 | |
|   QPainterPath cubicPath(startVec);
 | |
|   cubicPath.cubicTo(startDirVec, endDirVec, endVec);
 | |
|   
 | |
|   QPolygonF polygon = cubicPath.toSubpathPolygons().first();
 | |
|   QCPVector2D p(pos);
 | |
|   double minDistSqr = std::numeric_limits<double>::max();
 | |
|   for (int i=1; i<polygon.size(); ++i)
 | |
|   {
 | |
|     double distSqr = p.distanceSquaredToLine(polygon.at(i-1), polygon.at(i));
 | |
|     if (distSqr < minDistSqr)
 | |
|       minDistSqr = distSqr;
 | |
|   }
 | |
|   return qSqrt(minDistSqr);
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPItemCurve::draw(QCPPainter *painter)
 | |
| {
 | |
|   QCPVector2D startVec(start->pixelPosition());
 | |
|   QCPVector2D startDirVec(startDir->pixelPosition());
 | |
|   QCPVector2D endDirVec(endDir->pixelPosition());
 | |
|   QCPVector2D endVec(end->pixelPosition());
 | |
|   if ((endVec-startVec).length() > 1e10) // too large curves cause crash
 | |
|     return;
 | |
| 
 | |
|   QPainterPath cubicPath(startVec.toPointF());
 | |
|   cubicPath.cubicTo(startDirVec.toPointF(), endDirVec.toPointF(), endVec.toPointF());
 | |
| 
 | |
|   // paint visible segment, if existent:
 | |
|   QRect clip = clipRect().adjusted(-mainPen().widthF(), -mainPen().widthF(), mainPen().widthF(), mainPen().widthF());
 | |
|   QRect cubicRect = cubicPath.controlPointRect().toRect();
 | |
|   if (cubicRect.isEmpty()) // may happen when start and end exactly on same x or y position
 | |
|     cubicRect.adjust(0, 0, 1, 1);
 | |
|   if (clip.intersects(cubicRect))
 | |
|   {
 | |
|     painter->setPen(mainPen());
 | |
|     painter->drawPath(cubicPath);
 | |
|     painter->setBrush(Qt::SolidPattern);
 | |
|     if (mTail.style() != QCPLineEnding::esNone)
 | |
|       mTail.draw(painter, startVec, M_PI-cubicPath.angleAtPercent(0)/180.0*M_PI);
 | |
|     if (mHead.style() != QCPLineEnding::esNone)
 | |
|       mHead.draw(painter, endVec, -cubicPath.angleAtPercent(1)/180.0*M_PI);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Returns the pen that should be used for drawing lines. Returns mPen when the
 | |
|   item is not selected and mSelectedPen when it is.
 | |
| */
 | |
| QPen QCPItemCurve::mainPen() const
 | |
| {
 | |
|   return mSelected ? mSelectedPen : mPen;
 | |
| }
 | |
| /* end of 'src/items/item-curve.cpp' */
 | |
| 
 | |
| 
 | |
| /* including file 'src/items/item-rect.cpp', size 6479                       */
 | |
| /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPItemRect
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPItemRect
 | |
|   \brief A rectangle
 | |
| 
 | |
|   \image html QCPItemRect.png "Rectangle example. Blue dotted circles are anchors, solid blue discs are positions."
 | |
| 
 | |
|   It has two positions, \a topLeft and \a bottomRight, which define the rectangle.
 | |
| */
 | |
| 
 | |
| /*!
 | |
|   Creates a rectangle item and sets default values.
 | |
|   
 | |
|   The created item is automatically registered with \a parentPlot. This QCustomPlot instance takes
 | |
|   ownership of the item, so do not delete it manually but use QCustomPlot::removeItem() instead.
 | |
| */
 | |
| QCPItemRect::QCPItemRect(QCustomPlot *parentPlot) :
 | |
|   QCPAbstractItem(parentPlot),
 | |
|   topLeft(createPosition(QLatin1String("topLeft"))),
 | |
|   bottomRight(createPosition(QLatin1String("bottomRight"))),
 | |
|   top(createAnchor(QLatin1String("top"), aiTop)),
 | |
|   topRight(createAnchor(QLatin1String("topRight"), aiTopRight)),
 | |
|   right(createAnchor(QLatin1String("right"), aiRight)),
 | |
|   bottom(createAnchor(QLatin1String("bottom"), aiBottom)),
 | |
|   bottomLeft(createAnchor(QLatin1String("bottomLeft"), aiBottomLeft)),
 | |
|   left(createAnchor(QLatin1String("left"), aiLeft))
 | |
| {
 | |
|   topLeft->setCoords(0, 1);
 | |
|   bottomRight->setCoords(1, 0);
 | |
|   
 | |
|   setPen(QPen(Qt::black));
 | |
|   setSelectedPen(QPen(Qt::blue,2));
 | |
|   setBrush(Qt::NoBrush);
 | |
|   setSelectedBrush(Qt::NoBrush);
 | |
| }
 | |
| 
 | |
| QCPItemRect::~QCPItemRect()
 | |
| {
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the pen that will be used to draw the line of the rectangle
 | |
|   
 | |
|   \see setSelectedPen, setBrush
 | |
| */
 | |
| void QCPItemRect::setPen(const QPen &pen)
 | |
| {
 | |
|   mPen = pen;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the pen that will be used to draw the line of the rectangle when selected
 | |
|   
 | |
|   \see setPen, setSelected
 | |
| */
 | |
| void QCPItemRect::setSelectedPen(const QPen &pen)
 | |
| {
 | |
|   mSelectedPen = pen;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the brush that will be used to fill the rectangle. To disable filling, set \a brush to
 | |
|   Qt::NoBrush.
 | |
|   
 | |
|   \see setSelectedBrush, setPen
 | |
| */
 | |
| void QCPItemRect::setBrush(const QBrush &brush)
 | |
| {
 | |
|   mBrush = brush;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the brush that will be used to fill the rectangle when selected. To disable filling, set \a
 | |
|   brush to Qt::NoBrush.
 | |
|   
 | |
|   \see setBrush
 | |
| */
 | |
| void QCPItemRect::setSelectedBrush(const QBrush &brush)
 | |
| {
 | |
|   mSelectedBrush = brush;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| double QCPItemRect::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
 | |
| {
 | |
|   Q_UNUSED(details)
 | |
|   if (onlySelectable && !mSelectable)
 | |
|     return -1;
 | |
|   
 | |
|   QRectF rect = QRectF(topLeft->pixelPosition(), bottomRight->pixelPosition()).normalized();
 | |
|   bool filledRect = mBrush.style() != Qt::NoBrush && mBrush.color().alpha() != 0;
 | |
|   return rectDistance(rect, pos, filledRect);
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPItemRect::draw(QCPPainter *painter)
 | |
| {
 | |
|   QPointF p1 = topLeft->pixelPosition();
 | |
|   QPointF p2 = bottomRight->pixelPosition();
 | |
|   if (p1.toPoint() == p2.toPoint())
 | |
|     return;
 | |
|   QRectF rect = QRectF(p1, p2).normalized();
 | |
|   double clipPad = mainPen().widthF();
 | |
|   QRectF boundingRect = rect.adjusted(-clipPad, -clipPad, clipPad, clipPad);
 | |
|   if (boundingRect.intersects(clipRect())) // only draw if bounding rect of rect item is visible in cliprect
 | |
|   {
 | |
|     painter->setPen(mainPen());
 | |
|     painter->setBrush(mainBrush());
 | |
|     painter->drawRect(rect);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| QPointF QCPItemRect::anchorPixelPosition(int anchorId) const
 | |
| {
 | |
|   QRectF rect = QRectF(topLeft->pixelPosition(), bottomRight->pixelPosition());
 | |
|   switch (anchorId)
 | |
|   {
 | |
|     case aiTop:         return (rect.topLeft()+rect.topRight())*0.5;
 | |
|     case aiTopRight:    return rect.topRight();
 | |
|     case aiRight:       return (rect.topRight()+rect.bottomRight())*0.5;
 | |
|     case aiBottom:      return (rect.bottomLeft()+rect.bottomRight())*0.5;
 | |
|     case aiBottomLeft:  return rect.bottomLeft();
 | |
|     case aiLeft:        return (rect.topLeft()+rect.bottomLeft())*0.5;
 | |
|   }
 | |
|   
 | |
|   qDebug() << Q_FUNC_INFO << "invalid anchorId" << anchorId;
 | |
|   return QPointF();
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Returns the pen that should be used for drawing lines. Returns mPen when the item is not selected
 | |
|   and mSelectedPen when it is.
 | |
| */
 | |
| QPen QCPItemRect::mainPen() const
 | |
| {
 | |
|   return mSelected ? mSelectedPen : mPen;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Returns the brush that should be used for drawing fills of the item. Returns mBrush when the item
 | |
|   is not selected and mSelectedBrush when it is.
 | |
| */
 | |
| QBrush QCPItemRect::mainBrush() const
 | |
| {
 | |
|   return mSelected ? mSelectedBrush : mBrush;
 | |
| }
 | |
| /* end of 'src/items/item-rect.cpp' */
 | |
| 
 | |
| 
 | |
| /* including file 'src/items/item-text.cpp', size 13338                      */
 | |
| /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPItemText
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPItemText
 | |
|   \brief A text label
 | |
| 
 | |
|   \image html QCPItemText.png "Text example. Blue dotted circles are anchors, solid blue discs are positions."
 | |
| 
 | |
|   Its position is defined by the member \a position and the setting of \ref setPositionAlignment.
 | |
|   The latter controls which part of the text rect shall be aligned with \a position.
 | |
|   
 | |
|   The text alignment itself (i.e. left, center, right) can be controlled with \ref
 | |
|   setTextAlignment.
 | |
|   
 | |
|   The text may be rotated around the \a position point with \ref setRotation.
 | |
| */
 | |
| 
 | |
| /*!
 | |
|   Creates a text item and sets default values.
 | |
|   
 | |
|   The created item is automatically registered with \a parentPlot. This QCustomPlot instance takes
 | |
|   ownership of the item, so do not delete it manually but use QCustomPlot::removeItem() instead.
 | |
| */
 | |
| QCPItemText::QCPItemText(QCustomPlot *parentPlot) :
 | |
|   QCPAbstractItem(parentPlot),
 | |
|   position(createPosition(QLatin1String("position"))),
 | |
|   topLeft(createAnchor(QLatin1String("topLeft"), aiTopLeft)),
 | |
|   top(createAnchor(QLatin1String("top"), aiTop)),
 | |
|   topRight(createAnchor(QLatin1String("topRight"), aiTopRight)),
 | |
|   right(createAnchor(QLatin1String("right"), aiRight)),
 | |
|   bottomRight(createAnchor(QLatin1String("bottomRight"), aiBottomRight)),
 | |
|   bottom(createAnchor(QLatin1String("bottom"), aiBottom)),
 | |
|   bottomLeft(createAnchor(QLatin1String("bottomLeft"), aiBottomLeft)),
 | |
|   left(createAnchor(QLatin1String("left"), aiLeft)),
 | |
|   mText(QLatin1String("text")),
 | |
|   mPositionAlignment(Qt::AlignCenter),
 | |
|   mTextAlignment(Qt::AlignTop|Qt::AlignHCenter),
 | |
|   mRotation(0)
 | |
| {
 | |
|   position->setCoords(0, 0);
 | |
|   
 | |
|   setPen(Qt::NoPen);
 | |
|   setSelectedPen(Qt::NoPen);
 | |
|   setBrush(Qt::NoBrush);
 | |
|   setSelectedBrush(Qt::NoBrush);
 | |
|   setColor(Qt::black);
 | |
|   setSelectedColor(Qt::blue);
 | |
| }
 | |
| 
 | |
| QCPItemText::~QCPItemText()
 | |
| {
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the color of the text.
 | |
| */
 | |
| void QCPItemText::setColor(const QColor &color)
 | |
| {
 | |
|   mColor = color;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the color of the text that will be used when the item is selected.
 | |
| */
 | |
| void QCPItemText::setSelectedColor(const QColor &color)
 | |
| {
 | |
|   mSelectedColor = color;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the pen that will be used do draw a rectangular border around the text. To disable the
 | |
|   border, set \a pen to Qt::NoPen.
 | |
|   
 | |
|   \see setSelectedPen, setBrush, setPadding
 | |
| */
 | |
| void QCPItemText::setPen(const QPen &pen)
 | |
| {
 | |
|   mPen = pen;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the pen that will be used do draw a rectangular border around the text, when the item is
 | |
|   selected. To disable the border, set \a pen to Qt::NoPen.
 | |
|   
 | |
|   \see setPen
 | |
| */
 | |
| void QCPItemText::setSelectedPen(const QPen &pen)
 | |
| {
 | |
|   mSelectedPen = pen;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the brush that will be used do fill the background of the text. To disable the
 | |
|   background, set \a brush to Qt::NoBrush.
 | |
|   
 | |
|   \see setSelectedBrush, setPen, setPadding
 | |
| */
 | |
| void QCPItemText::setBrush(const QBrush &brush)
 | |
| {
 | |
|   mBrush = brush;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the brush that will be used do fill the background of the text, when the item is selected. To disable the
 | |
|   background, set \a brush to Qt::NoBrush.
 | |
|   
 | |
|   \see setBrush
 | |
| */
 | |
| void QCPItemText::setSelectedBrush(const QBrush &brush)
 | |
| {
 | |
|   mSelectedBrush = brush;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the font of the text.
 | |
|   
 | |
|   \see setSelectedFont, setColor
 | |
| */
 | |
| void QCPItemText::setFont(const QFont &font)
 | |
| {
 | |
|   mFont = font;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the font of the text that will be used when the item is selected.
 | |
|   
 | |
|   \see setFont
 | |
| */
 | |
| void QCPItemText::setSelectedFont(const QFont &font)
 | |
| {
 | |
|   mSelectedFont = font;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the text that will be displayed. Multi-line texts are supported by inserting a line break
 | |
|   character, e.g. '\n'.
 | |
|   
 | |
|   \see setFont, setColor, setTextAlignment
 | |
| */
 | |
| void QCPItemText::setText(const QString &text)
 | |
| {
 | |
|   mText = text;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets which point of the text rect shall be aligned with \a position.
 | |
|   
 | |
|   Examples:
 | |
|   \li If \a alignment is <tt>Qt::AlignHCenter | Qt::AlignTop</tt>, the text will be positioned such
 | |
|   that the top of the text rect will be horizontally centered on \a position.
 | |
|   \li If \a alignment is <tt>Qt::AlignLeft | Qt::AlignBottom</tt>, \a position will indicate the
 | |
|   bottom left corner of the text rect.
 | |
|   
 | |
|   If you want to control the alignment of (multi-lined) text within the text rect, use \ref
 | |
|   setTextAlignment.
 | |
| */
 | |
| void QCPItemText::setPositionAlignment(Qt::Alignment alignment)
 | |
| {
 | |
|   mPositionAlignment = alignment;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Controls how (multi-lined) text is aligned inside the text rect (typically Qt::AlignLeft, Qt::AlignCenter or Qt::AlignRight).
 | |
| */
 | |
| void QCPItemText::setTextAlignment(Qt::Alignment alignment)
 | |
| {
 | |
|   mTextAlignment = alignment;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the angle in degrees by which the text (and the text rectangle, if visible) will be rotated
 | |
|   around \a position.
 | |
| */
 | |
| void QCPItemText::setRotation(double degrees)
 | |
| {
 | |
|   mRotation = degrees;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the distance between the border of the text rectangle and the text. The appearance (and
 | |
|   visibility) of the text rectangle can be controlled with \ref setPen and \ref setBrush.
 | |
| */
 | |
| void QCPItemText::setPadding(const QMargins &padding)
 | |
| {
 | |
|   mPadding = padding;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| double QCPItemText::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
 | |
| {
 | |
|   Q_UNUSED(details)
 | |
|   if (onlySelectable && !mSelectable)
 | |
|     return -1;
 | |
|   
 | |
|   // The rect may be rotated, so we transform the actual clicked pos to the rotated
 | |
|   // coordinate system, so we can use the normal rectDistance function for non-rotated rects:
 | |
|   QPointF positionPixels(position->pixelPosition());
 | |
|   QTransform inputTransform;
 | |
|   inputTransform.translate(positionPixels.x(), positionPixels.y());
 | |
|   inputTransform.rotate(-mRotation);
 | |
|   inputTransform.translate(-positionPixels.x(), -positionPixels.y());
 | |
|   QPointF rotatedPos = inputTransform.map(pos);
 | |
|   QFontMetrics fontMetrics(mFont);
 | |
|   QRect textRect = fontMetrics.boundingRect(0, 0, 0, 0, Qt::TextDontClip|mTextAlignment, mText);
 | |
|   QRect textBoxRect = textRect.adjusted(-mPadding.left(), -mPadding.top(), mPadding.right(), mPadding.bottom());
 | |
|   QPointF textPos = getTextDrawPoint(positionPixels, textBoxRect, mPositionAlignment);
 | |
|   textBoxRect.moveTopLeft(textPos.toPoint());
 | |
| 
 | |
|   return rectDistance(textBoxRect, rotatedPos, true);
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPItemText::draw(QCPPainter *painter)
 | |
| {
 | |
|   QPointF pos(position->pixelPosition());
 | |
|   QTransform transform = painter->transform();
 | |
|   transform.translate(pos.x(), pos.y());
 | |
|   if (!qFuzzyIsNull(mRotation))
 | |
|     transform.rotate(mRotation);
 | |
|   painter->setFont(mainFont());
 | |
|   QRect textRect = painter->fontMetrics().boundingRect(0, 0, 0, 0, Qt::TextDontClip|mTextAlignment, mText);
 | |
|   QRect textBoxRect = textRect.adjusted(-mPadding.left(), -mPadding.top(), mPadding.right(), mPadding.bottom());
 | |
|   QPointF textPos = getTextDrawPoint(QPointF(0, 0), textBoxRect, mPositionAlignment); // 0, 0 because the transform does the translation
 | |
|   textRect.moveTopLeft(textPos.toPoint()+QPoint(mPadding.left(), mPadding.top()));
 | |
|   textBoxRect.moveTopLeft(textPos.toPoint());
 | |
|   double clipPad = mainPen().widthF();
 | |
|   QRect boundingRect = textBoxRect.adjusted(-clipPad, -clipPad, clipPad, clipPad);
 | |
|   if (transform.mapRect(boundingRect).intersects(painter->transform().mapRect(clipRect())))
 | |
|   {
 | |
|     painter->setTransform(transform);
 | |
|     if ((mainBrush().style() != Qt::NoBrush && mainBrush().color().alpha() != 0) ||
 | |
|         (mainPen().style() != Qt::NoPen && mainPen().color().alpha() != 0))
 | |
|     {
 | |
|       painter->setPen(mainPen());
 | |
|       painter->setBrush(mainBrush());
 | |
|       painter->drawRect(textBoxRect);
 | |
|     }
 | |
|     painter->setBrush(Qt::NoBrush);
 | |
|     painter->setPen(QPen(mainColor()));
 | |
|     painter->drawText(textRect, Qt::TextDontClip|mTextAlignment, mText);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| QPointF QCPItemText::anchorPixelPosition(int anchorId) const
 | |
| {
 | |
|   // get actual rect points (pretty much copied from draw function):
 | |
|   QPointF pos(position->pixelPosition());
 | |
|   QTransform transform;
 | |
|   transform.translate(pos.x(), pos.y());
 | |
|   if (!qFuzzyIsNull(mRotation))
 | |
|     transform.rotate(mRotation);
 | |
|   QFontMetrics fontMetrics(mainFont());
 | |
|   QRect textRect = fontMetrics.boundingRect(0, 0, 0, 0, Qt::TextDontClip|mTextAlignment, mText);
 | |
|   QRectF textBoxRect = textRect.adjusted(-mPadding.left(), -mPadding.top(), mPadding.right(), mPadding.bottom());
 | |
|   QPointF textPos = getTextDrawPoint(QPointF(0, 0), textBoxRect, mPositionAlignment); // 0, 0 because the transform does the translation
 | |
|   textBoxRect.moveTopLeft(textPos.toPoint());
 | |
|   QPolygonF rectPoly = transform.map(QPolygonF(textBoxRect));
 | |
|   
 | |
|   switch (anchorId)
 | |
|   {
 | |
|     case aiTopLeft:     return rectPoly.at(0);
 | |
|     case aiTop:         return (rectPoly.at(0)+rectPoly.at(1))*0.5;
 | |
|     case aiTopRight:    return rectPoly.at(1);
 | |
|     case aiRight:       return (rectPoly.at(1)+rectPoly.at(2))*0.5;
 | |
|     case aiBottomRight: return rectPoly.at(2);
 | |
|     case aiBottom:      return (rectPoly.at(2)+rectPoly.at(3))*0.5;
 | |
|     case aiBottomLeft:  return rectPoly.at(3);
 | |
|     case aiLeft:        return (rectPoly.at(3)+rectPoly.at(0))*0.5;
 | |
|   }
 | |
|   
 | |
|   qDebug() << Q_FUNC_INFO << "invalid anchorId" << anchorId;
 | |
|   return QPointF();
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns the point that must be given to the QPainter::drawText function (which expects the top
 | |
|   left point of the text rect), according to the position \a pos, the text bounding box \a rect and
 | |
|   the requested \a positionAlignment.
 | |
|   
 | |
|   For example, if \a positionAlignment is <tt>Qt::AlignLeft | Qt::AlignBottom</tt> the returned point
 | |
|   will be shifted upward by the height of \a rect, starting from \a pos. So if the text is finally
 | |
|   drawn at that point, the lower left corner of the resulting text rect is at \a pos.
 | |
| */
 | |
| QPointF QCPItemText::getTextDrawPoint(const QPointF &pos, const QRectF &rect, Qt::Alignment positionAlignment) const
 | |
| {
 | |
|   if (positionAlignment == 0 || positionAlignment == (Qt::AlignLeft|Qt::AlignTop))
 | |
|     return pos;
 | |
|   
 | |
|   QPointF result = pos; // start at top left
 | |
|   if (positionAlignment.testFlag(Qt::AlignHCenter))
 | |
|     result.rx() -= rect.width()/2.0;
 | |
|   else if (positionAlignment.testFlag(Qt::AlignRight))
 | |
|     result.rx() -= rect.width();
 | |
|   if (positionAlignment.testFlag(Qt::AlignVCenter))
 | |
|     result.ry() -= rect.height()/2.0;
 | |
|   else if (positionAlignment.testFlag(Qt::AlignBottom))
 | |
|     result.ry() -= rect.height();
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Returns the font that should be used for drawing text. Returns mFont when the item is not selected
 | |
|   and mSelectedFont when it is.
 | |
| */
 | |
| QFont QCPItemText::mainFont() const
 | |
| {
 | |
|   return mSelected ? mSelectedFont : mFont;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Returns the color that should be used for drawing text. Returns mColor when the item is not
 | |
|   selected and mSelectedColor when it is.
 | |
| */
 | |
| QColor QCPItemText::mainColor() const
 | |
| {
 | |
|   return mSelected ? mSelectedColor : mColor;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Returns the pen that should be used for drawing lines. Returns mPen when the item is not selected
 | |
|   and mSelectedPen when it is.
 | |
| */
 | |
| QPen QCPItemText::mainPen() const
 | |
| {
 | |
|   return mSelected ? mSelectedPen : mPen;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Returns the brush that should be used for drawing fills of the item. Returns mBrush when the item
 | |
|   is not selected and mSelectedBrush when it is.
 | |
| */
 | |
| QBrush QCPItemText::mainBrush() const
 | |
| {
 | |
|   return mSelected ? mSelectedBrush : mBrush;
 | |
| }
 | |
| /* end of 'src/items/item-text.cpp' */
 | |
| 
 | |
| 
 | |
| /* including file 'src/items/item-ellipse.cpp', size 7863                    */
 | |
| /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPItemEllipse
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPItemEllipse
 | |
|   \brief An ellipse
 | |
| 
 | |
|   \image html QCPItemEllipse.png "Ellipse example. Blue dotted circles are anchors, solid blue discs are positions."
 | |
| 
 | |
|   It has two positions, \a topLeft and \a bottomRight, which define the rect the ellipse will be drawn in.
 | |
| */
 | |
| 
 | |
| /*!
 | |
|   Creates an ellipse item and sets default values.
 | |
|   
 | |
|   The created item is automatically registered with \a parentPlot. This QCustomPlot instance takes
 | |
|   ownership of the item, so do not delete it manually but use QCustomPlot::removeItem() instead.
 | |
| */
 | |
| QCPItemEllipse::QCPItemEllipse(QCustomPlot *parentPlot) :
 | |
|   QCPAbstractItem(parentPlot),
 | |
|   topLeft(createPosition(QLatin1String("topLeft"))),
 | |
|   bottomRight(createPosition(QLatin1String("bottomRight"))),
 | |
|   topLeftRim(createAnchor(QLatin1String("topLeftRim"), aiTopLeftRim)),
 | |
|   top(createAnchor(QLatin1String("top"), aiTop)),
 | |
|   topRightRim(createAnchor(QLatin1String("topRightRim"), aiTopRightRim)),
 | |
|   right(createAnchor(QLatin1String("right"), aiRight)),
 | |
|   bottomRightRim(createAnchor(QLatin1String("bottomRightRim"), aiBottomRightRim)),
 | |
|   bottom(createAnchor(QLatin1String("bottom"), aiBottom)),
 | |
|   bottomLeftRim(createAnchor(QLatin1String("bottomLeftRim"), aiBottomLeftRim)),
 | |
|   left(createAnchor(QLatin1String("left"), aiLeft)),
 | |
|   center(createAnchor(QLatin1String("center"), aiCenter))
 | |
| {
 | |
|   topLeft->setCoords(0, 1);
 | |
|   bottomRight->setCoords(1, 0);
 | |
|   
 | |
|   setPen(QPen(Qt::black));
 | |
|   setSelectedPen(QPen(Qt::blue, 2));
 | |
|   setBrush(Qt::NoBrush);
 | |
|   setSelectedBrush(Qt::NoBrush);
 | |
| }
 | |
| 
 | |
| QCPItemEllipse::~QCPItemEllipse()
 | |
| {
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the pen that will be used to draw the line of the ellipse
 | |
|   
 | |
|   \see setSelectedPen, setBrush
 | |
| */
 | |
| void QCPItemEllipse::setPen(const QPen &pen)
 | |
| {
 | |
|   mPen = pen;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the pen that will be used to draw the line of the ellipse when selected
 | |
|   
 | |
|   \see setPen, setSelected
 | |
| */
 | |
| void QCPItemEllipse::setSelectedPen(const QPen &pen)
 | |
| {
 | |
|   mSelectedPen = pen;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the brush that will be used to fill the ellipse. To disable filling, set \a brush to
 | |
|   Qt::NoBrush.
 | |
|   
 | |
|   \see setSelectedBrush, setPen
 | |
| */
 | |
| void QCPItemEllipse::setBrush(const QBrush &brush)
 | |
| {
 | |
|   mBrush = brush;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the brush that will be used to fill the ellipse when selected. To disable filling, set \a
 | |
|   brush to Qt::NoBrush.
 | |
|   
 | |
|   \see setBrush
 | |
| */
 | |
| void QCPItemEllipse::setSelectedBrush(const QBrush &brush)
 | |
| {
 | |
|   mSelectedBrush = brush;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| double QCPItemEllipse::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
 | |
| {
 | |
|   Q_UNUSED(details)
 | |
|   if (onlySelectable && !mSelectable)
 | |
|     return -1;
 | |
|   
 | |
|   QPointF p1 = topLeft->pixelPosition();
 | |
|   QPointF p2 = bottomRight->pixelPosition();
 | |
|   QPointF center((p1+p2)/2.0);
 | |
|   double a = qAbs(p1.x()-p2.x())/2.0;
 | |
|   double b = qAbs(p1.y()-p2.y())/2.0;
 | |
|   double x = pos.x()-center.x();
 | |
|   double y = pos.y()-center.y();
 | |
|   
 | |
|   // distance to border:
 | |
|   double c = 1.0/qSqrt(x*x/(a*a)+y*y/(b*b));
 | |
|   double result = qAbs(c-1)*qSqrt(x*x+y*y);
 | |
|   // filled ellipse, allow click inside to count as hit:
 | |
|   if (result > mParentPlot->selectionTolerance()*0.99 && mBrush.style() != Qt::NoBrush && mBrush.color().alpha() != 0)
 | |
|   {
 | |
|     if (x*x/(a*a) + y*y/(b*b) <= 1)
 | |
|       result = mParentPlot->selectionTolerance()*0.99;
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPItemEllipse::draw(QCPPainter *painter)
 | |
| {
 | |
|   QPointF p1 = topLeft->pixelPosition();
 | |
|   QPointF p2 = bottomRight->pixelPosition();
 | |
|   if (p1.toPoint() == p2.toPoint())
 | |
|     return;
 | |
|   QRectF ellipseRect = QRectF(p1, p2).normalized();
 | |
|   QRect clip = clipRect().adjusted(-mainPen().widthF(), -mainPen().widthF(), mainPen().widthF(), mainPen().widthF());
 | |
|   if (ellipseRect.intersects(clip)) // only draw if bounding rect of ellipse is visible in cliprect
 | |
|   {
 | |
|     painter->setPen(mainPen());
 | |
|     painter->setBrush(mainBrush());
 | |
| #ifdef __EXCEPTIONS
 | |
|     try // drawEllipse sometimes throws exceptions if ellipse is too big
 | |
|     {
 | |
| #endif
 | |
|       painter->drawEllipse(ellipseRect);
 | |
| #ifdef __EXCEPTIONS
 | |
|     } catch (...)
 | |
|     {
 | |
|       qDebug() << Q_FUNC_INFO << "Item too large for memory, setting invisible";
 | |
|       setVisible(false);
 | |
|     }
 | |
| #endif
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| QPointF QCPItemEllipse::anchorPixelPosition(int anchorId) const
 | |
| {
 | |
|   QRectF rect = QRectF(topLeft->pixelPosition(), bottomRight->pixelPosition());
 | |
|   switch (anchorId)
 | |
|   {
 | |
|     case aiTopLeftRim:     return rect.center()+(rect.topLeft()-rect.center())*1/qSqrt(2);
 | |
|     case aiTop:            return (rect.topLeft()+rect.topRight())*0.5;
 | |
|     case aiTopRightRim:    return rect.center()+(rect.topRight()-rect.center())*1/qSqrt(2);
 | |
|     case aiRight:          return (rect.topRight()+rect.bottomRight())*0.5;
 | |
|     case aiBottomRightRim: return rect.center()+(rect.bottomRight()-rect.center())*1/qSqrt(2);
 | |
|     case aiBottom:         return (rect.bottomLeft()+rect.bottomRight())*0.5;
 | |
|     case aiBottomLeftRim:  return rect.center()+(rect.bottomLeft()-rect.center())*1/qSqrt(2);
 | |
|     case aiLeft:           return (rect.topLeft()+rect.bottomLeft())*0.5;
 | |
|     case aiCenter:         return (rect.topLeft()+rect.bottomRight())*0.5;
 | |
|   }
 | |
|   
 | |
|   qDebug() << Q_FUNC_INFO << "invalid anchorId" << anchorId;
 | |
|   return QPointF();
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Returns the pen that should be used for drawing lines. Returns mPen when the item is not selected
 | |
|   and mSelectedPen when it is.
 | |
| */
 | |
| QPen QCPItemEllipse::mainPen() const
 | |
| {
 | |
|   return mSelected ? mSelectedPen : mPen;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Returns the brush that should be used for drawing fills of the item. Returns mBrush when the item
 | |
|   is not selected and mSelectedBrush when it is.
 | |
| */
 | |
| QBrush QCPItemEllipse::mainBrush() const
 | |
| {
 | |
|   return mSelected ? mSelectedBrush : mBrush;
 | |
| }
 | |
| /* end of 'src/items/item-ellipse.cpp' */
 | |
| 
 | |
| 
 | |
| /* including file 'src/items/item-pixmap.cpp', size 10615                    */
 | |
| /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPItemPixmap
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPItemPixmap
 | |
|   \brief An arbitrary pixmap
 | |
| 
 | |
|   \image html QCPItemPixmap.png "Pixmap example. Blue dotted circles are anchors, solid blue discs are positions."
 | |
| 
 | |
|   It has two positions, \a topLeft and \a bottomRight, which define the rectangle the pixmap will
 | |
|   be drawn in. Depending on the scale setting (\ref setScaled), the pixmap will be either scaled to
 | |
|   fit the rectangle or be drawn aligned to the topLeft position.
 | |
|   
 | |
|   If scaling is enabled and \a topLeft is further to the bottom/right than \a bottomRight (as shown
 | |
|   on the right side of the example image), the pixmap will be flipped in the respective
 | |
|   orientations.
 | |
| */
 | |
| 
 | |
| /*!
 | |
|   Creates a rectangle item and sets default values.
 | |
|   
 | |
|   The created item is automatically registered with \a parentPlot. This QCustomPlot instance takes
 | |
|   ownership of the item, so do not delete it manually but use QCustomPlot::removeItem() instead.
 | |
| */
 | |
| QCPItemPixmap::QCPItemPixmap(QCustomPlot *parentPlot) :
 | |
|   QCPAbstractItem(parentPlot),
 | |
|   topLeft(createPosition(QLatin1String("topLeft"))),
 | |
|   bottomRight(createPosition(QLatin1String("bottomRight"))),
 | |
|   top(createAnchor(QLatin1String("top"), aiTop)),
 | |
|   topRight(createAnchor(QLatin1String("topRight"), aiTopRight)),
 | |
|   right(createAnchor(QLatin1String("right"), aiRight)),
 | |
|   bottom(createAnchor(QLatin1String("bottom"), aiBottom)),
 | |
|   bottomLeft(createAnchor(QLatin1String("bottomLeft"), aiBottomLeft)),
 | |
|   left(createAnchor(QLatin1String("left"), aiLeft)),
 | |
|   mScaled(false),
 | |
|   mScaledPixmapInvalidated(true),
 | |
|   mAspectRatioMode(Qt::KeepAspectRatio),
 | |
|   mTransformationMode(Qt::SmoothTransformation)
 | |
| {
 | |
|   topLeft->setCoords(0, 1);
 | |
|   bottomRight->setCoords(1, 0);
 | |
|   
 | |
|   setPen(Qt::NoPen);
 | |
|   setSelectedPen(QPen(Qt::blue));
 | |
| }
 | |
| 
 | |
| QCPItemPixmap::~QCPItemPixmap()
 | |
| {
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the pixmap that will be displayed.
 | |
| */
 | |
| void QCPItemPixmap::setPixmap(const QPixmap &pixmap)
 | |
| {
 | |
|   mPixmap = pixmap;
 | |
|   mScaledPixmapInvalidated = true;
 | |
|   if (mPixmap.isNull())
 | |
|     qDebug() << Q_FUNC_INFO << "pixmap is null";
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets whether the pixmap will be scaled to fit the rectangle defined by the \a topLeft and \a
 | |
|   bottomRight positions.
 | |
| */
 | |
| void QCPItemPixmap::setScaled(bool scaled, Qt::AspectRatioMode aspectRatioMode, Qt::TransformationMode transformationMode)
 | |
| {
 | |
|   mScaled = scaled;
 | |
|   mAspectRatioMode = aspectRatioMode;
 | |
|   mTransformationMode = transformationMode;
 | |
|   mScaledPixmapInvalidated = true;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the pen that will be used to draw a border around the pixmap.
 | |
|   
 | |
|   \see setSelectedPen, setBrush
 | |
| */
 | |
| void QCPItemPixmap::setPen(const QPen &pen)
 | |
| {
 | |
|   mPen = pen;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the pen that will be used to draw a border around the pixmap when selected
 | |
|   
 | |
|   \see setPen, setSelected
 | |
| */
 | |
| void QCPItemPixmap::setSelectedPen(const QPen &pen)
 | |
| {
 | |
|   mSelectedPen = pen;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| double QCPItemPixmap::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
 | |
| {
 | |
|   Q_UNUSED(details)
 | |
|   if (onlySelectable && !mSelectable)
 | |
|     return -1;
 | |
|   
 | |
|   return rectDistance(getFinalRect(), pos, true);
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPItemPixmap::draw(QCPPainter *painter)
 | |
| {
 | |
|   bool flipHorz = false;
 | |
|   bool flipVert = false;
 | |
|   QRect rect = getFinalRect(&flipHorz, &flipVert);
 | |
|   double clipPad = mainPen().style() == Qt::NoPen ? 0 : mainPen().widthF();
 | |
|   QRect boundingRect = rect.adjusted(-clipPad, -clipPad, clipPad, clipPad);
 | |
|   if (boundingRect.intersects(clipRect()))
 | |
|   {
 | |
|     updateScaledPixmap(rect, flipHorz, flipVert);
 | |
|     painter->drawPixmap(rect.topLeft(), mScaled ? mScaledPixmap : mPixmap);
 | |
|     QPen pen = mainPen();
 | |
|     if (pen.style() != Qt::NoPen)
 | |
|     {
 | |
|       painter->setPen(pen);
 | |
|       painter->setBrush(Qt::NoBrush);
 | |
|       painter->drawRect(rect);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| QPointF QCPItemPixmap::anchorPixelPosition(int anchorId) const
 | |
| {
 | |
|   bool flipHorz;
 | |
|   bool flipVert;
 | |
|   QRect rect = getFinalRect(&flipHorz, &flipVert);
 | |
|   // we actually want denormal rects (negative width/height) here, so restore
 | |
|   // the flipped state:
 | |
|   if (flipHorz)
 | |
|     rect.adjust(rect.width(), 0, -rect.width(), 0);
 | |
|   if (flipVert)
 | |
|     rect.adjust(0, rect.height(), 0, -rect.height());
 | |
|   
 | |
|   switch (anchorId)
 | |
|   {
 | |
|     case aiTop:         return (rect.topLeft()+rect.topRight())*0.5;
 | |
|     case aiTopRight:    return rect.topRight();
 | |
|     case aiRight:       return (rect.topRight()+rect.bottomRight())*0.5;
 | |
|     case aiBottom:      return (rect.bottomLeft()+rect.bottomRight())*0.5;
 | |
|     case aiBottomLeft:  return rect.bottomLeft();
 | |
|     case aiLeft:        return (rect.topLeft()+rect.bottomLeft())*0.5;;
 | |
|   }
 | |
|   
 | |
|   qDebug() << Q_FUNC_INFO << "invalid anchorId" << anchorId;
 | |
|   return QPointF();
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Creates the buffered scaled image (\a mScaledPixmap) to fit the specified \a finalRect. The
 | |
|   parameters \a flipHorz and \a flipVert control whether the resulting image shall be flipped
 | |
|   horizontally or vertically. (This is used when \a topLeft is further to the bottom/right than \a
 | |
|   bottomRight.)
 | |
|   
 | |
|   This function only creates the scaled pixmap when the buffered pixmap has a different size than
 | |
|   the expected result, so calling this function repeatedly, e.g. in the \ref draw function, does
 | |
|   not cause expensive rescaling every time.
 | |
|   
 | |
|   If scaling is disabled, sets mScaledPixmap to a null QPixmap.
 | |
| */
 | |
| void QCPItemPixmap::updateScaledPixmap(QRect finalRect, bool flipHorz, bool flipVert)
 | |
| {
 | |
|   if (mPixmap.isNull())
 | |
|     return;
 | |
|   
 | |
|   if (mScaled)
 | |
|   {
 | |
| #ifdef QCP_DEVICEPIXELRATIO_SUPPORTED
 | |
|     double devicePixelRatio = mPixmap.devicePixelRatio();
 | |
| #else
 | |
|     double devicePixelRatio = 1.0;
 | |
| #endif
 | |
|     if (finalRect.isNull())
 | |
|       finalRect = getFinalRect(&flipHorz, &flipVert);
 | |
|     if (mScaledPixmapInvalidated || finalRect.size() != mScaledPixmap.size()/devicePixelRatio)
 | |
|     {
 | |
|       mScaledPixmap = mPixmap.scaled(finalRect.size()*devicePixelRatio, mAspectRatioMode, mTransformationMode);
 | |
|       if (flipHorz || flipVert)
 | |
|         mScaledPixmap = QPixmap::fromImage(mScaledPixmap.toImage().mirrored(flipHorz, flipVert));
 | |
| #ifdef QCP_DEVICEPIXELRATIO_SUPPORTED
 | |
|       mScaledPixmap.setDevicePixelRatio(devicePixelRatio);
 | |
| #endif
 | |
|     }
 | |
|   } else if (!mScaledPixmap.isNull())
 | |
|     mScaledPixmap = QPixmap();
 | |
|   mScaledPixmapInvalidated = false;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
|   
 | |
|   Returns the final (tight) rect the pixmap is drawn in, depending on the current item positions
 | |
|   and scaling settings.
 | |
|   
 | |
|   The output parameters \a flippedHorz and \a flippedVert return whether the pixmap should be drawn
 | |
|   flipped horizontally or vertically in the returned rect. (The returned rect itself is always
 | |
|   normalized, i.e. the top left corner of the rect is actually further to the top/left than the
 | |
|   bottom right corner). This is the case when the item position \a topLeft is further to the
 | |
|   bottom/right than \a bottomRight.
 | |
|   
 | |
|   If scaling is disabled, returns a rect with size of the original pixmap and the top left corner
 | |
|   aligned with the item position \a topLeft. The position \a bottomRight is ignored.
 | |
| */
 | |
| QRect QCPItemPixmap::getFinalRect(bool *flippedHorz, bool *flippedVert) const
 | |
| {
 | |
|   QRect result;
 | |
|   bool flipHorz = false;
 | |
|   bool flipVert = false;
 | |
|   QPoint p1 = topLeft->pixelPosition().toPoint();
 | |
|   QPoint p2 = bottomRight->pixelPosition().toPoint();
 | |
|   if (p1 == p2)
 | |
|     return QRect(p1, QSize(0, 0));
 | |
|   if (mScaled)
 | |
|   {
 | |
|     QSize newSize = QSize(p2.x()-p1.x(), p2.y()-p1.y());
 | |
|     QPoint topLeft = p1;
 | |
|     if (newSize.width() < 0)
 | |
|     {
 | |
|       flipHorz = true;
 | |
|       newSize.rwidth() *= -1;
 | |
|       topLeft.setX(p2.x());
 | |
|     }
 | |
|     if (newSize.height() < 0)
 | |
|     {
 | |
|       flipVert = true;
 | |
|       newSize.rheight() *= -1;
 | |
|       topLeft.setY(p2.y());
 | |
|     }
 | |
|     QSize scaledSize = mPixmap.size();
 | |
| #ifdef QCP_DEVICEPIXELRATIO_SUPPORTED
 | |
|     scaledSize /= mPixmap.devicePixelRatio();
 | |
|     scaledSize.scale(newSize*mPixmap.devicePixelRatio(), mAspectRatioMode);
 | |
| #else
 | |
|     scaledSize.scale(newSize, mAspectRatioMode);
 | |
| #endif
 | |
|     result = QRect(topLeft, scaledSize);
 | |
|   } else
 | |
|   {
 | |
| #ifdef QCP_DEVICEPIXELRATIO_SUPPORTED
 | |
|     result = QRect(p1, mPixmap.size()/mPixmap.devicePixelRatio());
 | |
| #else
 | |
|     result = QRect(p1, mPixmap.size());
 | |
| #endif
 | |
|   }
 | |
|   if (flippedHorz)
 | |
|     *flippedHorz = flipHorz;
 | |
|   if (flippedVert)
 | |
|     *flippedVert = flipVert;
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Returns the pen that should be used for drawing lines. Returns mPen when the item is not selected
 | |
|   and mSelectedPen when it is.
 | |
| */
 | |
| QPen QCPItemPixmap::mainPen() const
 | |
| {
 | |
|   return mSelected ? mSelectedPen : mPen;
 | |
| }
 | |
| /* end of 'src/items/item-pixmap.cpp' */
 | |
| 
 | |
| 
 | |
| /* including file 'src/items/item-tracer.cpp', size 14624                    */
 | |
| /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPItemTracer
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPItemTracer
 | |
|   \brief Item that sticks to QCPGraph data points
 | |
| 
 | |
|   \image html QCPItemTracer.png "Tracer example. Blue dotted circles are anchors, solid blue discs are positions."
 | |
| 
 | |
|   The tracer can be connected with a QCPGraph via \ref setGraph. Then it will automatically adopt
 | |
|   the coordinate axes of the graph and update its \a position to be on the graph's data. This means
 | |
|   the key stays controllable via \ref setGraphKey, but the value will follow the graph data. If a
 | |
|   QCPGraph is connected, note that setting the coordinates of the tracer item directly via \a
 | |
|   position will have no effect because they will be overriden in the next redraw (this is when the
 | |
|   coordinate update happens).
 | |
|   
 | |
|   If the specified key in \ref setGraphKey is outside the key bounds of the graph, the tracer will
 | |
|   stay at the corresponding end of the graph.
 | |
|   
 | |
|   With \ref setInterpolating you may specify whether the tracer may only stay exactly on data
 | |
|   points or whether it interpolates data points linearly, if given a key that lies between two data
 | |
|   points of the graph.
 | |
|   
 | |
|   The tracer has different visual styles, see \ref setStyle. It is also possible to make the tracer
 | |
|   have no own visual appearance (set the style to \ref tsNone), and just connect other item
 | |
|   positions to the tracer \a position (used as an anchor) via \ref
 | |
|   QCPItemPosition::setParentAnchor.
 | |
|   
 | |
|   \note The tracer position is only automatically updated upon redraws. So when the data of the
 | |
|   graph changes and immediately afterwards (without a redraw) the position coordinates of the
 | |
|   tracer are retrieved, they will not reflect the updated data of the graph. In this case \ref
 | |
|   updatePosition must be called manually, prior to reading the tracer coordinates.
 | |
| */
 | |
| 
 | |
| /*!
 | |
|   Creates a tracer item and sets default values.
 | |
|   
 | |
|   The created item is automatically registered with \a parentPlot. This QCustomPlot instance takes
 | |
|   ownership of the item, so do not delete it manually but use QCustomPlot::removeItem() instead.
 | |
| */
 | |
| QCPItemTracer::QCPItemTracer(QCustomPlot *parentPlot) :
 | |
|   QCPAbstractItem(parentPlot),
 | |
|   position(createPosition(QLatin1String("position"))),
 | |
|   mSize(6),
 | |
|   mStyle(tsCrosshair),
 | |
|   mGraph(0),
 | |
|   mGraphKey(0),
 | |
|   mInterpolating(false)
 | |
| {
 | |
|   position->setCoords(0, 0);
 | |
| 
 | |
|   setBrush(Qt::NoBrush);
 | |
|   setSelectedBrush(Qt::NoBrush);
 | |
|   setPen(QPen(Qt::black));
 | |
|   setSelectedPen(QPen(Qt::blue, 2));
 | |
| }
 | |
| 
 | |
| QCPItemTracer::~QCPItemTracer()
 | |
| {
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the pen that will be used to draw the line of the tracer
 | |
|   
 | |
|   \see setSelectedPen, setBrush
 | |
| */
 | |
| void QCPItemTracer::setPen(const QPen &pen)
 | |
| {
 | |
|   mPen = pen;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the pen that will be used to draw the line of the tracer when selected
 | |
|   
 | |
|   \see setPen, setSelected
 | |
| */
 | |
| void QCPItemTracer::setSelectedPen(const QPen &pen)
 | |
| {
 | |
|   mSelectedPen = pen;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the brush that will be used to draw any fills of the tracer
 | |
|   
 | |
|   \see setSelectedBrush, setPen
 | |
| */
 | |
| void QCPItemTracer::setBrush(const QBrush &brush)
 | |
| {
 | |
|   mBrush = brush;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the brush that will be used to draw any fills of the tracer, when selected.
 | |
|   
 | |
|   \see setBrush, setSelected
 | |
| */
 | |
| void QCPItemTracer::setSelectedBrush(const QBrush &brush)
 | |
| {
 | |
|   mSelectedBrush = brush;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the size of the tracer in pixels, if the style supports setting a size (e.g. \ref tsSquare
 | |
|   does, \ref tsCrosshair does not).
 | |
| */
 | |
| void QCPItemTracer::setSize(double size)
 | |
| {
 | |
|   mSize = size;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the style/visual appearance of the tracer.
 | |
|   
 | |
|   If you only want to use the tracer \a position as an anchor for other items, set \a style to
 | |
|   \ref tsNone.
 | |
| */
 | |
| void QCPItemTracer::setStyle(QCPItemTracer::TracerStyle style)
 | |
| {
 | |
|   mStyle = style;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the QCPGraph this tracer sticks to. The tracer \a position will be set to type
 | |
|   QCPItemPosition::ptPlotCoords and the axes will be set to the axes of \a graph.
 | |
|   
 | |
|   To free the tracer from any graph, set \a graph to 0. The tracer \a position can then be placed
 | |
|   freely like any other item position. This is the state the tracer will assume when its graph gets
 | |
|   deleted while still attached to it.
 | |
|   
 | |
|   \see setGraphKey
 | |
| */
 | |
| void QCPItemTracer::setGraph(QCPGraph *graph)
 | |
| {
 | |
|   if (graph)
 | |
|   {
 | |
|     if (graph->parentPlot() == mParentPlot)
 | |
|     {
 | |
|       position->setType(QCPItemPosition::ptPlotCoords);
 | |
|       position->setAxes(graph->keyAxis(), graph->valueAxis());
 | |
|       mGraph = graph;
 | |
|       updatePosition();
 | |
|     } else
 | |
|       qDebug() << Q_FUNC_INFO << "graph isn't in same QCustomPlot instance as this item";
 | |
|   } else
 | |
|   {
 | |
|     mGraph = 0;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the key of the graph's data point the tracer will be positioned at. This is the only free
 | |
|   coordinate of a tracer when attached to a graph.
 | |
|   
 | |
|   Depending on \ref setInterpolating, the tracer will be either positioned on the data point
 | |
|   closest to \a key, or will stay exactly at \a key and interpolate the value linearly.
 | |
|   
 | |
|   \see setGraph, setInterpolating
 | |
| */
 | |
| void QCPItemTracer::setGraphKey(double key)
 | |
| {
 | |
|   mGraphKey = key;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets whether the value of the graph's data points shall be interpolated, when positioning the
 | |
|   tracer.
 | |
|   
 | |
|   If \a enabled is set to false and a key is given with \ref setGraphKey, the tracer is placed on
 | |
|   the data point of the graph which is closest to the key, but which is not necessarily exactly
 | |
|   there. If \a enabled is true, the tracer will be positioned exactly at the specified key, and
 | |
|   the appropriate value will be interpolated from the graph's data points linearly.
 | |
|   
 | |
|   \see setGraph, setGraphKey
 | |
| */
 | |
| void QCPItemTracer::setInterpolating(bool enabled)
 | |
| {
 | |
|   mInterpolating = enabled;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| double QCPItemTracer::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
 | |
| {
 | |
|   Q_UNUSED(details)
 | |
|   if (onlySelectable && !mSelectable)
 | |
|     return -1;
 | |
| 
 | |
|   QPointF center(position->pixelPosition());
 | |
|   double w = mSize/2.0;
 | |
|   QRect clip = clipRect();
 | |
|   switch (mStyle)
 | |
|   {
 | |
|     case tsNone: return -1;
 | |
|     case tsPlus:
 | |
|     {
 | |
|       if (clipRect().intersects(QRectF(center-QPointF(w, w), center+QPointF(w, w)).toRect()))
 | |
|         return qSqrt(qMin(QCPVector2D(pos).distanceSquaredToLine(center+QPointF(-w, 0), center+QPointF(w, 0)),
 | |
|                           QCPVector2D(pos).distanceSquaredToLine(center+QPointF(0, -w), center+QPointF(0, w))));
 | |
|       break;
 | |
|     }
 | |
|     case tsCrosshair:
 | |
|     {
 | |
|       return qSqrt(qMin(QCPVector2D(pos).distanceSquaredToLine(QCPVector2D(clip.left(), center.y()), QCPVector2D(clip.right(), center.y())),
 | |
|                         QCPVector2D(pos).distanceSquaredToLine(QCPVector2D(center.x(), clip.top()), QCPVector2D(center.x(), clip.bottom()))));
 | |
|     }
 | |
|     case tsCircle:
 | |
|     {
 | |
|       if (clip.intersects(QRectF(center-QPointF(w, w), center+QPointF(w, w)).toRect()))
 | |
|       {
 | |
|         // distance to border:
 | |
|         double centerDist = QCPVector2D(center-pos).length();
 | |
|         double circleLine = w;
 | |
|         double result = qAbs(centerDist-circleLine);
 | |
|         // filled ellipse, allow click inside to count as hit:
 | |
|         if (result > mParentPlot->selectionTolerance()*0.99 && mBrush.style() != Qt::NoBrush && mBrush.color().alpha() != 0)
 | |
|         {
 | |
|           if (centerDist <= circleLine)
 | |
|             result = mParentPlot->selectionTolerance()*0.99;
 | |
|         }
 | |
|         return result;
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     case tsSquare:
 | |
|     {
 | |
|       if (clip.intersects(QRectF(center-QPointF(w, w), center+QPointF(w, w)).toRect()))
 | |
|       {
 | |
|         QRectF rect = QRectF(center-QPointF(w, w), center+QPointF(w, w));
 | |
|         bool filledRect = mBrush.style() != Qt::NoBrush && mBrush.color().alpha() != 0;
 | |
|         return rectDistance(rect, pos, filledRect);
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
|   return -1;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPItemTracer::draw(QCPPainter *painter)
 | |
| {
 | |
|   updatePosition();
 | |
|   if (mStyle == tsNone)
 | |
|     return;
 | |
| 
 | |
|   painter->setPen(mainPen());
 | |
|   painter->setBrush(mainBrush());
 | |
|   QPointF center(position->pixelPosition());
 | |
|   double w = mSize/2.0;
 | |
|   QRect clip = clipRect();
 | |
|   switch (mStyle)
 | |
|   {
 | |
|     case tsNone: return;
 | |
|     case tsPlus:
 | |
|     {
 | |
|       if (clip.intersects(QRectF(center-QPointF(w, w), center+QPointF(w, w)).toRect()))
 | |
|       {
 | |
|         painter->drawLine(QLineF(center+QPointF(-w, 0), center+QPointF(w, 0)));
 | |
|         painter->drawLine(QLineF(center+QPointF(0, -w), center+QPointF(0, w)));
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|     case tsCrosshair:
 | |
|     {
 | |
|       if (center.y() > clip.top() && center.y() < clip.bottom())
 | |
|         painter->drawLine(QLineF(clip.left(), center.y(), clip.right(), center.y()));
 | |
|       if (center.x() > clip.left() && center.x() < clip.right())
 | |
|         painter->drawLine(QLineF(center.x(), clip.top(), center.x(), clip.bottom()));
 | |
|       break;
 | |
|     }
 | |
|     case tsCircle:
 | |
|     {
 | |
|       if (clip.intersects(QRectF(center-QPointF(w, w), center+QPointF(w, w)).toRect()))
 | |
|         painter->drawEllipse(center, w, w);
 | |
|       break;
 | |
|     }
 | |
|     case tsSquare:
 | |
|     {
 | |
|       if (clip.intersects(QRectF(center-QPointF(w, w), center+QPointF(w, w)).toRect()))
 | |
|         painter->drawRect(QRectF(center-QPointF(w, w), center+QPointF(w, w)));
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   If the tracer is connected with a graph (\ref setGraph), this function updates the tracer's \a
 | |
|   position to reside on the graph data, depending on the configured key (\ref setGraphKey).
 | |
|   
 | |
|   It is called automatically on every redraw and normally doesn't need to be called manually. One
 | |
|   exception is when you want to read the tracer coordinates via \a position and are not sure that
 | |
|   the graph's data (or the tracer key with \ref setGraphKey) hasn't changed since the last redraw.
 | |
|   In that situation, call this function before accessing \a position, to make sure you don't get
 | |
|   out-of-date coordinates.
 | |
|   
 | |
|   If there is no graph set on this tracer, this function does nothing.
 | |
| */
 | |
| void QCPItemTracer::updatePosition()
 | |
| {
 | |
|   if (mGraph)
 | |
|   {
 | |
|     if (mParentPlot->hasPlottable(mGraph))
 | |
|     {
 | |
|       if (mGraph->data()->size() > 1)
 | |
|       {
 | |
|         QCPGraphDataContainer::const_iterator first = mGraph->data()->constBegin();
 | |
|         QCPGraphDataContainer::const_iterator last = mGraph->data()->constEnd()-1;
 | |
|         if (mGraphKey <= first->key)
 | |
|           position->setCoords(first->key, first->value);
 | |
|         else if (mGraphKey >= last->key)
 | |
|           position->setCoords(last->key, last->value);
 | |
|         else
 | |
|         {
 | |
|           QCPGraphDataContainer::const_iterator it = mGraph->data()->findBegin(mGraphKey);
 | |
|           if (it != mGraph->data()->constEnd()) // mGraphKey is not exactly on last iterator, but somewhere between iterators
 | |
|           {
 | |
|             QCPGraphDataContainer::const_iterator prevIt = it;
 | |
|             ++it; // won't advance to constEnd because we handled that case (mGraphKey >= last->key) before
 | |
|             if (mInterpolating)
 | |
|             {
 | |
|               // interpolate between iterators around mGraphKey:
 | |
|               double slope = 0;
 | |
|               if (!qFuzzyCompare((double)it->key, (double)prevIt->key))
 | |
|                 slope = (it->value-prevIt->value)/(it->key-prevIt->key);
 | |
|               position->setCoords(mGraphKey, (mGraphKey-prevIt->key)*slope+prevIt->value);
 | |
|             } else
 | |
|             {
 | |
|               // find iterator with key closest to mGraphKey:
 | |
|               if (mGraphKey < (prevIt->key+it->key)*0.5)
 | |
|                 position->setCoords(prevIt->key, prevIt->value);
 | |
|               else
 | |
|                 position->setCoords(it->key, it->value);
 | |
|             }
 | |
|           } else // mGraphKey is exactly on last iterator (should actually be caught when comparing first/last keys, but this is a failsafe for fp uncertainty)
 | |
|             position->setCoords(it->key, it->value);
 | |
|         }
 | |
|       } else if (mGraph->data()->size() == 1)
 | |
|       {
 | |
|         QCPGraphDataContainer::const_iterator it = mGraph->data()->constBegin();
 | |
|         position->setCoords(it->key, it->value);
 | |
|       } else
 | |
|         qDebug() << Q_FUNC_INFO << "graph has no data";
 | |
|     } else
 | |
|       qDebug() << Q_FUNC_INFO << "graph not contained in QCustomPlot instance (anymore)";
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Returns the pen that should be used for drawing lines. Returns mPen when the item is not selected
 | |
|   and mSelectedPen when it is.
 | |
| */
 | |
| QPen QCPItemTracer::mainPen() const
 | |
| {
 | |
|   return mSelected ? mSelectedPen : mPen;
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Returns the brush that should be used for drawing fills of the item. Returns mBrush when the item
 | |
|   is not selected and mSelectedBrush when it is.
 | |
| */
 | |
| QBrush QCPItemTracer::mainBrush() const
 | |
| {
 | |
|   return mSelected ? mSelectedBrush : mBrush;
 | |
| }
 | |
| /* end of 'src/items/item-tracer.cpp' */
 | |
| 
 | |
| 
 | |
| /* including file 'src/items/item-bracket.cpp', size 10687                   */
 | |
| /* commit 633339dadc92cb10c58ef3556b55570685fafb99 2016-09-13 23:54:56 +0200 */
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| //////////////////// QCPItemBracket
 | |
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /*! \class QCPItemBracket
 | |
|   \brief A bracket for referencing/highlighting certain parts in the plot.
 | |
| 
 | |
|   \image html QCPItemBracket.png "Bracket example. Blue dotted circles are anchors, solid blue discs are positions."
 | |
| 
 | |
|   It has two positions, \a left and \a right, which define the span of the bracket. If \a left is
 | |
|   actually farther to the left than \a right, the bracket is opened to the bottom, as shown in the
 | |
|   example image.
 | |
|   
 | |
|   The bracket supports multiple styles via \ref setStyle. The length, i.e. how far the bracket
 | |
|   stretches away from the embraced span, can be controlled with \ref setLength.
 | |
|   
 | |
|   \image html QCPItemBracket-length.png
 | |
|   <center>Demonstrating the effect of different values for \ref setLength, for styles \ref
 | |
|   bsCalligraphic and \ref bsSquare. Anchors and positions are displayed for reference.</center>
 | |
|   
 | |
|   It provides an anchor \a center, to allow connection of other items, e.g. an arrow (QCPItemLine
 | |
|   or QCPItemCurve) or a text label (QCPItemText), to the bracket.
 | |
| */
 | |
| 
 | |
| /*!
 | |
|   Creates a bracket item and sets default values.
 | |
|   
 | |
|   The created item is automatically registered with \a parentPlot. This QCustomPlot instance takes
 | |
|   ownership of the item, so do not delete it manually but use QCustomPlot::removeItem() instead.
 | |
| */
 | |
| QCPItemBracket::QCPItemBracket(QCustomPlot *parentPlot) :
 | |
|   QCPAbstractItem(parentPlot),
 | |
|   left(createPosition(QLatin1String("left"))),
 | |
|   right(createPosition(QLatin1String("right"))),
 | |
|   center(createAnchor(QLatin1String("center"), aiCenter)),
 | |
|   mLength(8),
 | |
|   mStyle(bsCalligraphic)
 | |
| {
 | |
|   left->setCoords(0, 0);
 | |
|   right->setCoords(1, 1);
 | |
|   
 | |
|   setPen(QPen(Qt::black));
 | |
|   setSelectedPen(QPen(Qt::blue, 2));
 | |
| }
 | |
| 
 | |
| QCPItemBracket::~QCPItemBracket()
 | |
| {
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the pen that will be used to draw the bracket.
 | |
|   
 | |
|   Note that when the style is \ref bsCalligraphic, only the color will be taken from the pen, the
 | |
|   stroke and width are ignored. To change the apparent stroke width of a calligraphic bracket, use
 | |
|   \ref setLength, which has a similar effect.
 | |
|   
 | |
|   \see setSelectedPen
 | |
| */
 | |
| void QCPItemBracket::setPen(const QPen &pen)
 | |
| {
 | |
|   mPen = pen;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the pen that will be used to draw the bracket when selected
 | |
|   
 | |
|   \see setPen, setSelected
 | |
| */
 | |
| void QCPItemBracket::setSelectedPen(const QPen &pen)
 | |
| {
 | |
|   mSelectedPen = pen;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the \a length in pixels how far the bracket extends in the direction towards the embraced
 | |
|   span of the bracket (i.e. perpendicular to the <i>left</i>-<i>right</i>-direction)
 | |
|   
 | |
|   \image html QCPItemBracket-length.png
 | |
|   <center>Demonstrating the effect of different values for \ref setLength, for styles \ref
 | |
|   bsCalligraphic and \ref bsSquare. Anchors and positions are displayed for reference.</center>
 | |
| */
 | |
| void QCPItemBracket::setLength(double length)
 | |
| {
 | |
|   mLength = length;
 | |
| }
 | |
| 
 | |
| /*!
 | |
|   Sets the style of the bracket, i.e. the shape/visual appearance.
 | |
|   
 | |
|   \see setPen
 | |
| */
 | |
| void QCPItemBracket::setStyle(QCPItemBracket::BracketStyle style)
 | |
| {
 | |
|   mStyle = style;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| double QCPItemBracket::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
 | |
| {
 | |
|   Q_UNUSED(details)
 | |
|   if (onlySelectable && !mSelectable)
 | |
|     return -1;
 | |
|   
 | |
|   QCPVector2D p(pos);
 | |
|   QCPVector2D leftVec(left->pixelPosition());
 | |
|   QCPVector2D rightVec(right->pixelPosition());
 | |
|   if (leftVec.toPoint() == rightVec.toPoint())
 | |
|     return -1;
 | |
|   
 | |
|   QCPVector2D widthVec = (rightVec-leftVec)*0.5;
 | |
|   QCPVector2D lengthVec = widthVec.perpendicular().normalized()*mLength;
 | |
|   QCPVector2D centerVec = (rightVec+leftVec)*0.5-lengthVec;
 | |
|   
 | |
|   switch (mStyle)
 | |
|   {
 | |
|     case QCPItemBracket::bsSquare:
 | |
|     case QCPItemBracket::bsRound:
 | |
|     {
 | |
|       double a = p.distanceSquaredToLine(centerVec-widthVec, centerVec+widthVec);
 | |
|       double b = p.distanceSquaredToLine(centerVec-widthVec+lengthVec, centerVec-widthVec);
 | |
|       double c = p.distanceSquaredToLine(centerVec+widthVec+lengthVec, centerVec+widthVec);
 | |
|       return qSqrt(qMin(qMin(a, b), c));
 | |
|     }
 | |
|     case QCPItemBracket::bsCurly:
 | |
|     case QCPItemBracket::bsCalligraphic:
 | |
|     {
 | |
|       double a = p.distanceSquaredToLine(centerVec-widthVec*0.75+lengthVec*0.15, centerVec+lengthVec*0.3);
 | |
|       double b = p.distanceSquaredToLine(centerVec-widthVec+lengthVec*0.7, centerVec-widthVec*0.75+lengthVec*0.15);
 | |
|       double c = p.distanceSquaredToLine(centerVec+widthVec*0.75+lengthVec*0.15, centerVec+lengthVec*0.3);
 | |
|       double d = p.distanceSquaredToLine(centerVec+widthVec+lengthVec*0.7, centerVec+widthVec*0.75+lengthVec*0.15);
 | |
|       return qSqrt(qMin(qMin(a, b), qMin(c, d)));
 | |
|     }
 | |
|   }
 | |
|   return -1;
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| void QCPItemBracket::draw(QCPPainter *painter)
 | |
| {
 | |
|   QCPVector2D leftVec(left->pixelPosition());
 | |
|   QCPVector2D rightVec(right->pixelPosition());
 | |
|   if (leftVec.toPoint() == rightVec.toPoint())
 | |
|     return;
 | |
|   
 | |
|   QCPVector2D widthVec = (rightVec-leftVec)*0.5;
 | |
|   QCPVector2D lengthVec = widthVec.perpendicular().normalized()*mLength;
 | |
|   QCPVector2D centerVec = (rightVec+leftVec)*0.5-lengthVec;
 | |
| 
 | |
|   QPolygon boundingPoly;
 | |
|   boundingPoly << leftVec.toPoint() << rightVec.toPoint()
 | |
|                << (rightVec-lengthVec).toPoint() << (leftVec-lengthVec).toPoint();
 | |
|   QRect clip = clipRect().adjusted(-mainPen().widthF(), -mainPen().widthF(), mainPen().widthF(), mainPen().widthF());
 | |
|   if (clip.intersects(boundingPoly.boundingRect()))
 | |
|   {
 | |
|     painter->setPen(mainPen());
 | |
|     switch (mStyle)
 | |
|     {
 | |
|       case bsSquare:
 | |
|       {
 | |
|         painter->drawLine((centerVec+widthVec).toPointF(), (centerVec-widthVec).toPointF());
 | |
|         painter->drawLine((centerVec+widthVec).toPointF(), (centerVec+widthVec+lengthVec).toPointF());
 | |
|         painter->drawLine((centerVec-widthVec).toPointF(), (centerVec-widthVec+lengthVec).toPointF());
 | |
|         break;
 | |
|       }
 | |
|       case bsRound:
 | |
|       {
 | |
|         painter->setBrush(Qt::NoBrush);
 | |
|         QPainterPath path;
 | |
|         path.moveTo((centerVec+widthVec+lengthVec).toPointF());
 | |
|         path.cubicTo((centerVec+widthVec).toPointF(), (centerVec+widthVec).toPointF(), centerVec.toPointF());
 | |
|         path.cubicTo((centerVec-widthVec).toPointF(), (centerVec-widthVec).toPointF(), (centerVec-widthVec+lengthVec).toPointF());
 | |
|         painter->drawPath(path);
 | |
|         break;
 | |
|       }
 | |
|       case bsCurly:
 | |
|       {
 | |
|         painter->setBrush(Qt::NoBrush);
 | |
|         QPainterPath path;
 | |
|         path.moveTo((centerVec+widthVec+lengthVec).toPointF());
 | |
|         path.cubicTo((centerVec+widthVec-lengthVec*0.8).toPointF(), (centerVec+0.4*widthVec+lengthVec).toPointF(), centerVec.toPointF());
 | |
|         path.cubicTo((centerVec-0.4*widthVec+lengthVec).toPointF(), (centerVec-widthVec-lengthVec*0.8).toPointF(), (centerVec-widthVec+lengthVec).toPointF());
 | |
|         painter->drawPath(path);
 | |
|         break;
 | |
|       }
 | |
|       case bsCalligraphic:
 | |
|       {
 | |
|         painter->setPen(Qt::NoPen);
 | |
|         painter->setBrush(QBrush(mainPen().color()));
 | |
|         QPainterPath path;
 | |
|         path.moveTo((centerVec+widthVec+lengthVec).toPointF());
 | |
|         
 | |
|         path.cubicTo((centerVec+widthVec-lengthVec*0.8).toPointF(), (centerVec+0.4*widthVec+0.8*lengthVec).toPointF(), centerVec.toPointF());
 | |
|         path.cubicTo((centerVec-0.4*widthVec+0.8*lengthVec).toPointF(), (centerVec-widthVec-lengthVec*0.8).toPointF(), (centerVec-widthVec+lengthVec).toPointF());
 | |
|         
 | |
|         path.cubicTo((centerVec-widthVec-lengthVec*0.5).toPointF(), (centerVec-0.2*widthVec+1.2*lengthVec).toPointF(), (centerVec+lengthVec*0.2).toPointF());
 | |
|         path.cubicTo((centerVec+0.2*widthVec+1.2*lengthVec).toPointF(), (centerVec+widthVec-lengthVec*0.5).toPointF(), (centerVec+widthVec+lengthVec).toPointF());
 | |
|         
 | |
|         painter->drawPath(path);
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* inherits documentation from base class */
 | |
| QPointF QCPItemBracket::anchorPixelPosition(int anchorId) const
 | |
| {
 | |
|   QCPVector2D leftVec(left->pixelPosition());
 | |
|   QCPVector2D rightVec(right->pixelPosition());
 | |
|   if (leftVec.toPoint() == rightVec.toPoint())
 | |
|     return leftVec.toPointF();
 | |
|   
 | |
|   QCPVector2D widthVec = (rightVec-leftVec)*0.5;
 | |
|   QCPVector2D lengthVec = widthVec.perpendicular().normalized()*mLength;
 | |
|   QCPVector2D centerVec = (rightVec+leftVec)*0.5-lengthVec;
 | |
|   
 | |
|   switch (anchorId)
 | |
|   {
 | |
|     case aiCenter:
 | |
|       return centerVec.toPointF();
 | |
|   }
 | |
|   qDebug() << Q_FUNC_INFO << "invalid anchorId" << anchorId;
 | |
|   return QPointF();
 | |
| }
 | |
| 
 | |
| /*! \internal
 | |
| 
 | |
|   Returns the pen that should be used for drawing lines. Returns mPen when the
 | |
|   item is not selected and mSelectedPen when it is.
 | |
| */
 | |
| QPen QCPItemBracket::mainPen() const
 | |
| {
 | |
|     return mSelected ? mSelectedPen : mPen;
 | |
| }
 | |
| /* end of 'src/items/item-bracket.cpp' */
 | |
| 
 | |
| 
 |