|
- /***************************************************************************
- ** **
- ** QCustomPlot, an easy to use, modern plotting widget for Qt **
- ** Copyright (C) 2011-2015 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: 22.12.15 **
- ** Version: 1.3.2 **
- ****************************************************************************/
-
- #include "qcustomplot.h"
-
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// 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
- }
-
- QCPPainter::~QCPPainter()
- {}
-
- /*!
- 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);
- }
- }
-
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// 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
- left undefined, the scatter color will be inherited from the plottable that uses this scatter
- style.
-
- \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)
- {}
-
- /*!
- 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>.
-
- \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;
- }
-
- /*!
- 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, 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: {
- painter->drawPixmap(x - mPixmap.width() * 0.5, y - mPixmap.height() * 0.5, 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;
- }
- }
- }
-
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// 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.
-
- 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.
-
- Initially, QCustomPlot has five layers: "background", "grid", "main", "axes" and "legend" (in
- that order). The top two layers "axes" and "legend" contain the default axes and legend, so they
- will be drawn on top. 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).
-
- Controlling the ordering of objects is easy: Create a new layer in the position you want it to
- be, e.g. above "main", with QCustomPlot::addLayer. Then set the current layer with
- 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
- 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.
- */
-
- /* 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)
- {
- // 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;
- }
-
- /*! \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);
- } 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))
- 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 with 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, scatters and error bars. Those can be configured via QCPGraph::setAntialiased,
- QCPGraph::setAntialiasedFill, QCPGraph::setAntialiasedScatters etc. 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 the layer this layerable is on 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 QCPLayoutElement), this function returns true only if this
- layerable has its visibility set to true and the parent layerable's \ref realVisibility returns
- true.
-
- If this layerable doesn't have a direct layerable parent, returns the state of this layerable's
- visibility.
- */
- 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 selectEvent/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, 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)
- }
-
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// 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
- */
-
- /*!
- 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.
- \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.
- Since the number of planck-volumes in the entire visible universe is only ~1e183, this should
- be enough.
- \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.
- */
- QCPRange::QCPRange(double lower, double upper) : lower(lower), upper(upper)
- {
- normalize();
- }
-
- /*!
- Returns the size of the range, i.e. \a upper-\a lower
- */
- double QCPRange::size() const
- {
- return upper - lower;
- }
-
- /*!
- Returns the center of the range, i.e. (\a upper+\a lower)*0.5
- */
- double QCPRange::center() const
- {
- return (upper + lower) * 0.5;
- }
-
- /*!
- Makes sure \a lower is numerically smaller than \a upper. If this is not the case, the values
- are swapped.
- */
- void QCPRange::normalize()
- {
- if (lower > upper)
- qSwap(lower, upper);
- }
-
- /*!
- 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 \a otherRange is already inside the current range, this function does nothing.
-
- \see expanded
- */
- void QCPRange::expand(const QCPRange& otherRange)
- {
- if (lower > otherRange.lower)
- lower = otherRange.lower;
- if (upper < otherRange.upper)
- upper = otherRange.upper;
- }
-
- /*!
- 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).
-
- \see expand
- */
- QCPRange QCPRange::expanded(const QCPRange& otherRange) const
- {
- QCPRange result = *this;
- result.expand(otherRange);
- 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;
- }
-
- /*!
- Returns true when \a value lies within or exactly on the borders of the range.
- */
- bool QCPRange::contains(double value) const
- {
- return value >= lower && value <= upper;
- }
-
- /*!
- 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)));
- }
-
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// 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 QCPPlotTitle 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.
- */
-
- /*! \fn virtual void QCPLayoutElement::mousePressEvent(QMouseEvent *event)
-
- This event is called, if the mouse was pressed while being inside the outer rect of this layout
- element.
- */
-
- /*! \fn virtual void QCPLayoutElement::mouseMoveEvent(QMouseEvent *event)
-
- This event is called, if the mouse is moved inside the outer rect of this layout element.
- */
-
- /*! \fn virtual void QCPLayoutElement::mouseReleaseEvent(QMouseEvent *event)
-
- This event is called, if the mouse was previously pressed inside the outer rect of this layout
- element and is now released.
- */
-
- /*! \fn virtual void QCPLayoutElement::mouseDoubleClickEvent(QMouseEvent *event)
-
- This event is called, if the mouse is double-clicked inside the outer rect of this layout
- element.
- */
-
- /*! \fn virtual void QCPLayoutElement::wheelEvent(QWheelEvent *event)
-
- This event is called, if the mouse wheel is scrolled while the cursor is inside the rect of this
- layout element.
- */
-
- /* 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
- */
- 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).
- */
- 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));
- }
-
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// 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);
- } 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.
-
- Row and column insertion can be performed with \ref insertRow and \ref insertColumn.
- */
-
- /*!
- Creates an instance of QCPLayoutGrid and sets default values.
- */
- QCPLayoutGrid::QCPLayoutGrid() : mColumnSpacing(5), mRowSpacing(5)
- {}
-
- 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;
- }
-
- /*!
- Returns the number of rows in the layout.
-
- \see columnCount
- */
- int QCPLayoutGrid::rowCount() const
- {
- return mElements.size();
- }
-
- /*!
- Returns the number of columns in the layout.
-
- \see rowCount
- */
- int QCPLayoutGrid::columnCount() const
- {
- if (mElements.size() > 0)
- return mElements.first().size();
- else
- return 0;
- }
-
- /*!
- 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.
-
- \see element, hasElement, take, remove
- */
- bool QCPLayoutGrid::addElement(int row, int column, QCPLayoutElement* element)
- {
- if (element) {
- if (!hasElement(row, column)) {
- if (element->layout()) // remove from old layout first
- element->layout()->take(element);
- expandTo(row + 1, column + 1);
- mElements[row][column] = element;
- adoptElement(element);
- return true;
- } else
- qDebug() << Q_FUNC_INFO
- << "There is already an element in the specified row/column:" << row << column;
- } else
- qDebug() << Q_FUNC_INFO << "Can't add null element to row/column:" << row << column;
- return false;
- }
-
- /*!
- 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;
- }
-
- /*!
- 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);
- }
-
- /* 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)));
- }
- }
- }
-
- /* inherits documentation from base class */
- int QCPLayoutGrid::elementCount() const
- {
- return rowCount() * columnCount();
- }
-
- /* inherits documentation from base class */
- QCPLayoutElement* QCPLayoutGrid::elementAt(int index) const
- {
- if (index >= 0 && index < elementCount())
- return mElements.at(index / columnCount()).at(index % columnCount());
- else
- return 0;
- }
-
- /* inherits documentation from base class */
- QCPLayoutElement* QCPLayoutGrid::takeAt(int index)
- {
- if (QCPLayoutElement* el = elementAt(index)) {
- releaseElement(el);
- mElements[index / columnCount()][index % columnCount()] = 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;
- int colC = columnCount();
- int rowC = rowCount();
- #if QT_VERSION >= QT_VERSION_CHECK(4, 7, 0)
- result.reserve(colC * rowC);
- #endif
- for (int row = 0; row < rowC; ++row) {
- for (int col = 0; col < colC; ++col) {
- result.append(mElements.at(row).at(col));
- }
- }
- if (recursive) {
- int c = result.size();
- for (int i = 0; i < c; ++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";
- }
-
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// 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 QVector2D& pos, const QVector2D& dir) const
- {
- if (mStyle == esNone)
- return;
-
- QVector2D lengthVec(dir.normalized());
- if (lengthVec.isNull())
- lengthVec = QVector2D(1, 0);
- QVector2D widthVec(-lengthVec.y(), lengthVec.x());
- lengthVec *= (float)(mLength * (mInverted ? -1 : 1));
- widthVec *= (float)(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.8f).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: {
- QVector2D widthVecPerp(-widthVec.y(), widthVec.x());
- 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: {
- QVector2D widthVecPerp(-widthVec.y(), widthVec.x());
- 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.2f * (mInverted ? -1 : 1)).toPointF(),
- (pos - widthVec - lengthVec * 0.2f * (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.2f * (mInverted ? -1 : 1)
- + dir.normalized() * qMax(1.0f, (float)painter->pen().widthF()) * 0.5f)
- .toPointF(),
- (pos - widthVec - lengthVec * 0.2f * (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 QVector2D& pos, double angle) const
- {
- draw(painter, pos, QVector2D(qCos(angle), qSin(angle)));
- }
-
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// 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 (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;
- }
-
- int lowTick = mParentAxis->mLowestVisibleTick;
- int highTick = mParentAxis->mHighestVisibleTick;
- 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 = lowTick; i <= highTick; ++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 = lowTick; i <= highTick; ++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 = lowTick; i <= highTick; ++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 = lowTick; i <= highTick; ++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>
-
- */
-
- /* 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)
- */
-
- /*! \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()
- */
-
- /* end of documentation of inline functions */
- /* start of documentation of signals */
-
- /*! \fn void QCPAxis::ticksRequest()
-
- This signal is emitted when \ref setAutoTicks is false and the axis is about to generate tick
- labels for a replot.
-
- Modifying the tick positions can be done with \ref setTickVector. If you also want to control the
- tick labels, set \ref setAutoTickLabels to false and also provide the labels with \ref
- setTickVectorLabels.
-
- If you only want static ticks you probably don't need this signal, since you can just set the
- tick vector (and possibly tick label vector) once. However, if you want to provide ticks (and
- maybe labels) dynamically, e.g. depending on the current axis range, connect a slot to this
- signal and set the vector/vectors there.
- */
-
- /*! \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. For example, the following slot would limit the x axis
- to only positive ranges:
- \code
- if (newRange.lower < 0)
- plot->xAxis->setRange(0, newRange.size());
- \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)
- , mAutoTickLabels(true)
- , mTickLabelType(ltNumber)
- , mTickLabelFont(mParentPlot->font())
- , mSelectedTickLabelFont(
- QFont(mTickLabelFont.family(), mTickLabelFont.pointSize(), QFont::Bold))
- , mTickLabelColor(Qt::black)
- , mSelectedTickLabelColor(Qt::blue)
- , mDateTimeFormat(QLatin1String("hh:mm:ss\ndd.MM.yy"))
- , mDateTimeSpec(Qt::LocalTime)
- , mNumberPrecision(6)
- , mNumberFormatChar('g')
- , mNumberBeautifulPowers(true)
- ,
- // ticks and subticks:
- mTicks(true)
- , mTickStep(1)
- , mSubTickCount(4)
- , mAutoTickCount(6)
- , mAutoTicks(true)
- , mAutoTickStep(true)
- , mAutoSubTicks(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)
- , mScaleLogBase(10)
- , mScaleLogBaseLogInv(1.0 / qLn(mScaleLogBase))
- ,
- // internal members:
- mGrid(new QCPGrid(this))
- , mAxisPainter(new QCPAxisPainterPrivate(parent->parentPlot()))
- , mLowestVisibleTick(0)
- , mHighestVisibleTick(-1)
- , 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. If \a type is set to \ref
- stLogarithmic, the logarithm base can be set with \ref setScaleLogBase. In logarithmic axis
- scaling, major tick marks appear at all powers of the logarithm base. Properties like tick step
- (\ref setTickStep) don't apply in logarithmic scaling. If you wish a decimal base but less major
- ticks, consider choosing a logarithm base of 100, 1000 or even higher.
-
- If \a type is \ref stLogarithmic and the number format (\ref setNumberFormat) uses the 'b' option
- (beautifully typeset decimal powers), the display usually is "1 [multiplication sign] 10
- [superscript] n", which looks unnatural for logarithmic scaling (the "1 [multiplication sign]"
- part). To only display the decimal power, set the number precision to zero with
- \ref setNumberPrecision.
- */
- void QCPAxis::setScaleType(QCPAxis::ScaleType type)
- {
- if (mScaleType != type) {
- mScaleType = type;
- if (mScaleType == stLogarithmic)
- setRange(mRange.sanitizedForLogScale());
- mCachedMarginValid = false;
- emit scaleTypeChanged(mScaleType);
- }
- }
-
- /*!
- If \ref setScaleType is set to \ref stLogarithmic, \a base will be the logarithm base of the
- scaling. In logarithmic axis scaling, major tick marks appear at all powers of \a base.
-
- Properties like tick step (\ref setTickStep) don't apply in logarithmic scaling. If you wish a
- decimal base but less major ticks, consider choosing \a base 100, 1000 or even higher.
- */
- void QCPAxis::setScaleLogBase(double base)
- {
- if (base > 1) {
- mScaleLogBase = base;
- mScaleLogBaseLogInv = 1.0 / qLn(mScaleLogBase); // buffer for faster baseLog() calculation
- mCachedMarginValid = false;
- } else
- qDebug() << Q_FUNC_INFO << "Invalid logarithmic scale base (must be greater 1):" << base;
- }
-
- /*!
- 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();
- }
- mCachedMarginValid = false;
- 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();
- }
- mCachedMarginValid = false;
- 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();
- }
- mCachedMarginValid = false;
- 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();
- }
- mCachedMarginValid = false;
- 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)
- {
- if (mRangeReversed != reversed) {
- mRangeReversed = reversed;
- mCachedMarginValid = false;
- }
- }
-
- /*!
- Sets whether the tick positions should be calculated automatically (either from an automatically
- generated tick step or a tick step provided manually via \ref setTickStep, see \ref
- setAutoTickStep).
-
- If \a on is set to false, you must provide the tick positions manually via \ref setTickVector.
- For these manual ticks you may let QCPAxis generate the appropriate labels automatically by
- leaving \ref setAutoTickLabels set to true. If you also wish to control the displayed labels
- manually, set \ref setAutoTickLabels to false and provide the label strings with \ref
- setTickVectorLabels.
-
- If you need dynamically calculated tick vectors (and possibly tick label vectors), set the
- vectors in a slot connected to the \ref ticksRequest signal.
-
- \see setAutoTickLabels, setAutoSubTicks, setAutoTickCount, setAutoTickStep
- */
- void QCPAxis::setAutoTicks(bool on)
- {
- if (mAutoTicks != on) {
- mAutoTicks = on;
- mCachedMarginValid = false;
- }
- }
-
- /*!
- When \ref setAutoTickStep is true, \a approximateCount determines how many ticks should be
- generated in the visible range, approximately.
-
- It's not guaranteed that this number of ticks is met exactly, but approximately within a
- tolerance of about two.
-
- Only values greater than zero are accepted as \a approximateCount.
-
- \see setAutoTickStep, setAutoTicks, setAutoSubTicks
- */
- void QCPAxis::setAutoTickCount(int approximateCount)
- {
- if (mAutoTickCount != approximateCount) {
- if (approximateCount > 0) {
- mAutoTickCount = approximateCount;
- mCachedMarginValid = false;
- } else
- qDebug() << Q_FUNC_INFO
- << "approximateCount must be greater than zero:" << approximateCount;
- }
- }
-
- /*!
- Sets whether the tick labels are generated automatically. Depending on the tick label type (\ref
- ltNumber or \ref ltDateTime), the labels will either show the coordinate as floating point
- number (\ref setNumberFormat), or a date/time formatted according to \ref setDateTimeFormat.
-
- If \a on is set to false, you should provide the tick labels via \ref setTickVectorLabels. This
- is usually used in a combination with \ref setAutoTicks set to false for complete control over
- tick positions and labels, e.g. when the ticks should be at multiples of pi and show "2pi", "3pi"
- etc. as tick labels.
-
- If you need dynamically calculated tick vectors (and possibly tick label vectors), set the
- vectors in a slot connected to the \ref ticksRequest signal.
-
- \see setAutoTicks
- */
- void QCPAxis::setAutoTickLabels(bool on)
- {
- if (mAutoTickLabels != on) {
- mAutoTickLabels = on;
- mCachedMarginValid = false;
- }
- }
-
- /*!
- Sets whether the tick step, i.e. the interval between two (major) ticks, is calculated
- automatically. If \a on is set to true, the axis finds a tick step that is reasonable for human
- readable plots.
-
- The number of ticks the algorithm aims for within the visible range can be specified with \ref
- setAutoTickCount.
-
- If \a on is set to false, you may set the tick step manually with \ref setTickStep.
-
- \see setAutoTicks, setAutoSubTicks, setAutoTickCount
- */
- void QCPAxis::setAutoTickStep(bool on)
- {
- if (mAutoTickStep != on) {
- mAutoTickStep = on;
- mCachedMarginValid = false;
- }
- }
-
- /*!
- Sets whether the number of sub ticks in one tick interval is determined automatically. This
- works, as long as the tick step mantissa is a multiple of 0.5. When \ref setAutoTickStep is
- enabled, this is always the case.
-
- When \a on is set to false, you may set the sub tick count with \ref setSubTickCount manually.
-
- \see setAutoTickCount, setAutoTicks, setAutoTickStep
- */
- void QCPAxis::setAutoSubTicks(bool on)
- {
- if (mAutoSubTicks != on) {
- mAutoSubTicks = on;
- mCachedMarginValid = false;
- }
- }
-
- /*!
- 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.
- */
- 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;
- }
- }
-
- /*!
- 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 whether the tick labels display numbers or dates/times.
-
- If \a type is set to \ref ltNumber, the format specifications of \ref setNumberFormat apply.
-
- If \a type is set to \ref ltDateTime, the format specifications of \ref setDateTimeFormat apply.
-
- In QCustomPlot, date/time coordinates are <tt>double</tt> numbers representing the seconds since
- 1970-01-01T00:00:00 UTC. This format can be retrieved from QDateTime objects with the
- QDateTime::toTime_t() function. Since this only gives a resolution of one second, there is also
- the QDateTime::toMSecsSinceEpoch() function which returns the timespan described above in
- milliseconds. Divide its return value by 1000.0 to get a value with the format needed for
- date/time plotting, with a resolution of one millisecond.
-
- Using the toMSecsSinceEpoch function allows dates that go back to 2nd January 4713 B.C.
- (represented by a negative number), unlike the toTime_t function, which works with unsigned
- integers and thus only goes back to 1st January 1970. So both for range and accuracy, use of
- toMSecsSinceEpoch()/1000.0 should be preferred as key coordinate for date/time axes.
-
- \see setTickLabels
- */
- void QCPAxis::setTickLabelType(LabelType type)
- {
- if (mTickLabelType != type) {
- mTickLabelType = type;
- 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)
- {
- if (color != mTickLabelColor) {
- mTickLabelColor = color;
- mCachedMarginValid = false;
- }
- }
-
- /*!
- 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 format in which dates and times are displayed as tick labels, if \ref setTickLabelType is
- \ref ltDateTime. for details about the \a format string, see the documentation of
- QDateTime::toString().
-
- Newlines can be inserted with "\n".
-
- \see setDateTimeSpec
- */
- void QCPAxis::setDateTimeFormat(const QString& format)
- {
- if (mDateTimeFormat != format) {
- mDateTimeFormat = format;
- mCachedMarginValid = false;
- }
- }
-
- /*!
- Sets the time spec that is used for the date time values when \ref setTickLabelType is \ref
- ltDateTime.
-
- The default value of QDateTime objects (and also QCustomPlot) is <tt>Qt::LocalTime</tt>. However,
- if the date time values passed to QCustomPlot are given in the UTC spec, set \a
- timeSpec to <tt>Qt::UTC</tt> to get the correct axis labels.
-
- \see setDateTimeFormat
- */
- void QCPAxis::setDateTimeSpec(const Qt::TimeSpec& timeSpec)
- {
- mDateTimeSpec = timeSpec;
- }
-
- /*!
- Sets the number format for the numbers drawn as tick labels (if tick label type is \ref
- ltNumber). 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.
-
- If the scale type (\ref setScaleType) is \ref stLogarithmic and the \a formatCode uses the 'b'
- option (beautifully typeset decimal powers), the display usually is "1 [multiplication sign] 10
- [superscript] n", which looks unnatural for logarithmic scaling (the "1 [multiplication sign]"
- part). To only display the decimal power, set the number precision to zero with \ref
- setNumberPrecision.
-
- 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
-
- If the scale type (\ref setScaleType) is \ref stLogarithmic and the number format (\ref
- setNumberFormat) uses the 'b' format code (beautifully typeset decimal powers), the display
- usually is "1 [multiplication sign] 10 [superscript] n", which looks unnatural for logarithmic
- scaling (the redundant "1 [multiplication sign]" part). To only display the decimal power "10
- [superscript] n", set \a precision to zero.
- */
- void QCPAxis::setNumberPrecision(int precision)
- {
- if (mNumberPrecision != precision) {
- mNumberPrecision = precision;
- mCachedMarginValid = false;
- }
- }
-
- /*!
- If \ref setAutoTickStep is set to false, use this function to set the tick step manually.
- The tick step is the interval between (major) ticks, in plot coordinates.
- \see setSubTickCount
- */
- void QCPAxis::setTickStep(double step)
- {
- if (mTickStep != step) {
- mTickStep = step;
- mCachedMarginValid = false;
- }
- }
-
- /*!
- If you want full control over what ticks (and possibly labels) the axes show, this function is
- used to set the coordinates at which ticks will appear.\ref setAutoTicks must be disabled, else
- the provided tick vector will be overwritten with automatically generated tick coordinates upon
- replot. The labels of the ticks can be generated automatically when \ref setAutoTickLabels is
- left enabled. If it is disabled, you can set the labels manually with \ref setTickVectorLabels.
-
- \a vec is a vector containing the positions of the ticks, in plot coordinates.
-
- \warning \a vec must be sorted in ascending order, no additional checks are made to ensure this.
-
- \see setTickVectorLabels
- */
- void QCPAxis::setTickVector(const QVector<double>& vec)
- {
- // don't check whether mTickVector != vec here, because it takes longer than we would save
- mTickVector = vec;
- mCachedMarginValid = false;
- }
-
- /*!
- If you want full control over what ticks and labels the axes show, this function is used to set a
- number of QStrings that will be displayed at the tick positions which you need to provide with
- \ref setTickVector. These two vectors should have the same size. (Note that you need to disable
- \ref setAutoTicks and \ref setAutoTickLabels first.)
-
- \a vec is a vector containing the labels of the ticks. The entries correspond to the respective
- indices in the tick vector, passed via \ref setTickVector.
-
- \see setTickVector
- */
- void QCPAxis::setTickVectorLabels(const QVector<QString>& vec)
- {
- // don't check whether mTickVectorLabels != vec here, because it takes longer than we would save
- mTickVectorLabels = vec;
- 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 the number of sub ticks in one (major) tick step. A sub tick count of three for example,
- divides the tick intervals in four sub intervals.
-
- By default, the number of sub ticks is chosen automatically in a reasonable manner as long as the
- mantissa of the tick step is a multiple of 0.5. When \ref setAutoTickStep is enabled, this is
- always the case.
-
- If you want to disable automatic sub tick count and use this function to set the count manually,
- see \ref setAutoSubTicks.
- */
- void QCPAxis::setSubTickCount(int count)
- {
- mSubTickCount = count;
- }
-
- /*!
- 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;
- }
- mCachedMarginValid = false;
- emit rangeChanged(mRange);
- emit rangeChanged(mRange, oldRange);
- }
-
- /*!
- 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).
- */
- 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;
- }
- mCachedMarginValid = false;
- 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;
- QCPAbstractPlottable::SignDomain signDomain = QCPAbstractPlottable::sdBoth;
- if (mScaleType == stLogarithmic)
- signDomain = (mRange.upper < 0 ? QCPAbstractPlottable::sdNegative
- : QCPAbstractPlottable::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 baseLog(value / mRange.lower) / baseLog(mRange.upper / mRange.lower)
- * mAxisRect->width()
- + mAxisRect->left();
- else
- return baseLog(mRange.upper / value) / baseLog(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()
- - baseLog(value / mRange.lower) / baseLog(mRange.upper / mRange.lower)
- * mAxisRect->height();
- else
- return mAxisRect->bottom()
- - baseLog(mRange.upper / value) / baseLog(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;
- }
- }
-
- /*! \internal
-
- This function is called to prepare the tick vector, sub tick vector and tick label vector. If
- \ref setAutoTicks is set to true, appropriate tick values are determined automatically via \ref
- generateAutoTicks. If it's set to false, the signal ticksRequest is emitted, which can be used to
- provide external tick positions. Then the sub tick vectors and tick label vectors are created.
- */
- void QCPAxis::setupTickVectors()
- {
- if (!mParentPlot)
- return;
- if ((!mTicks && !mTickLabels && !mGrid->visible()) || mRange.size() <= 0)
- return;
-
- // fill tick vectors, either by auto generating or by notifying user to fill the vectors himself
- if (mAutoTicks) {
- generateAutoTicks();
- } else {
- emit ticksRequest();
- }
-
- visibleTickBounds(mLowestVisibleTick, mHighestVisibleTick);
- if (mTickVector.isEmpty()) {
- mSubTickVector.clear();
- return;
- }
-
- // generate subticks between ticks:
- mSubTickVector.resize((mTickVector.size() - 1) * mSubTickCount);
- if (mSubTickCount > 0) {
- double subTickStep = 0;
- double subTickPosition = 0;
- int subTickIndex = 0;
- bool done = false;
- int lowTick = mLowestVisibleTick > 0 ? mLowestVisibleTick - 1 : mLowestVisibleTick;
- int highTick = mHighestVisibleTick < mTickVector.size() - 1 ? mHighestVisibleTick + 1
- : mHighestVisibleTick;
- for (int i = lowTick + 1; i <= highTick; ++i) {
- subTickStep = (mTickVector.at(i) - mTickVector.at(i - 1)) / (double)(mSubTickCount + 1);
- for (int k = 1; k <= mSubTickCount; ++k) {
- subTickPosition = mTickVector.at(i - 1) + k * subTickStep;
- if (subTickPosition < mRange.lower)
- continue;
- if (subTickPosition > mRange.upper) {
- done = true;
- break;
- }
- mSubTickVector[subTickIndex] = subTickPosition;
- subTickIndex++;
- }
- if (done)
- break;
- }
- mSubTickVector.resize(subTickIndex);
- }
-
- // generate tick labels according to tick positions:
- if (mAutoTickLabels) {
- int vecsize = mTickVector.size();
- mTickVectorLabels.resize(vecsize);
- if (mTickLabelType == ltNumber) {
- for (int i = mLowestVisibleTick; i <= mHighestVisibleTick; ++i)
- mTickVectorLabels[i] = mParentPlot->locale().toString(
- mTickVector.at(i), mNumberFormatChar.toLatin1(), mNumberPrecision);
- } else if (mTickLabelType == ltDateTime) {
- for (int i = mLowestVisibleTick; i <= mHighestVisibleTick; ++i) {
- #if QT_VERSION \
- < QT_VERSION_CHECK(4, 7, 0) // use fromMSecsSinceEpoch function if available, to gain sub-second
- // accuracy on tick labels (e.g. for format "hh:mm:ss:zzz")
- mTickVectorLabels[i] = mParentPlot->locale().toString(
- QDateTime::fromTime_t(mTickVector.at(i)).toTimeSpec(mDateTimeSpec),
- mDateTimeFormat);
- #else
- mTickVectorLabels[i] = mParentPlot->locale().toString(
- QDateTime::fromMSecsSinceEpoch(mTickVector.at(i) * 1000)
- .toTimeSpec(mDateTimeSpec),
- mDateTimeFormat);
- #endif
- }
- }
- } else // mAutoTickLabels == false
- {
- if (mAutoTicks) // ticks generated automatically, but not ticklabels, so emit ticksRequest
- // here for labels
- {
- emit ticksRequest();
- }
- // make sure provided tick label vector has correct (minimal) length:
- if (mTickVectorLabels.size() < mTickVector.size())
- mTickVectorLabels.resize(mTickVector.size());
- }
- }
-
- /*! \internal
-
- If \ref setAutoTicks is set to true, this function is called by \ref setupTickVectors to
- generate reasonable tick positions (and subtick count). The algorithm tries to create
- approximately <tt>mAutoTickCount</tt> ticks (set via \ref setAutoTickCount).
-
- If the scale is logarithmic, \ref setAutoTickCount is ignored, and one tick is generated at every
- power of the current logarithm base, set via \ref setScaleLogBase.
- */
- void QCPAxis::generateAutoTicks()
- {
- if (mScaleType == stLinear) {
- if (mAutoTickStep) {
- // Generate tick positions according to linear scaling:
- mTickStep = mRange.size()
- / (double)(mAutoTickCount
- + 1e-10); // mAutoTickCount ticks on average, the small addition
- // is to prevent jitter on exact integers
- double magnitudeFactor =
- qPow(10.0, qFloor(qLn(mTickStep)
- / qLn(10.0))); // get magnitude factor e.g. 0.01, 1, 10, 1000 etc.
- double tickStepMantissa = mTickStep / magnitudeFactor;
- if (tickStepMantissa < 5) {
- // round digit after decimal point to 0.5
- mTickStep = (int)(tickStepMantissa * 2) / 2.0 * magnitudeFactor;
- } else {
- // round to first digit in multiples of 2
- mTickStep = (int)(tickStepMantissa / 2.0) * 2.0 * magnitudeFactor;
- }
- }
- if (mAutoSubTicks)
- mSubTickCount = calculateAutoSubTickCount(mTickStep);
- // Generate tick positions according to mTickStep:
- qint64 firstStep = floor(
- mRange.lower / mTickStep); // do not use qFloor here, or we'll lose 64 bit precision
- qint64 lastStep =
- ceil(mRange.upper / mTickStep); // do not use qCeil here, or we'll lose 64 bit precision
- int tickcount = lastStep - firstStep + 1;
- if (tickcount < 0)
- tickcount = 0;
- mTickVector.resize(tickcount);
- for (int i = 0; i < tickcount; ++i)
- mTickVector[i] = (firstStep + i) * mTickStep;
- } else // mScaleType == stLogarithmic
- {
- // Generate tick positions according to logbase scaling:
- if (mRange.lower > 0 && mRange.upper > 0) // positive range
- {
- double lowerMag = basePow(qFloor(baseLog(mRange.lower)));
- double currentMag = lowerMag;
- mTickVector.clear();
- mTickVector.append(currentMag);
- while (
- currentMag < mRange.upper
- && currentMag
- > 0) // currentMag might be zero for ranges ~1e-300, just cancel in that case
- {
- currentMag *= mScaleLogBase;
- mTickVector.append(currentMag);
- }
- } else if (mRange.lower < 0 && mRange.upper < 0) // negative range
- {
- double lowerMag = -basePow(qCeil(baseLog(-mRange.lower)));
- double currentMag = lowerMag;
- mTickVector.clear();
- mTickVector.append(currentMag);
- while (
- currentMag < mRange.upper
- && currentMag
- < 0) // currentMag might be zero for ranges ~1e-300, just cancel in that case
- {
- currentMag /= mScaleLogBase;
- mTickVector.append(currentMag);
- }
- } else // invalid range for logarithmic scale, because lower and upper have different sign
- {
- mTickVector.clear();
- qDebug() << Q_FUNC_INFO << "Invalid range for logarithmic plot: " << mRange.lower << "-"
- << mRange.upper;
- }
- }
- }
-
- /*! \internal
-
- Called by generateAutoTicks when \ref setAutoSubTicks is set to true. Depending on the \a
- tickStep between two major ticks on the axis, a different number of sub ticks is appropriate. For
- Example taking 4 sub ticks for a \a tickStep of 1 makes more sense than taking 5 sub ticks,
- because this corresponds to a sub tick step of 0.2, instead of the less intuitive 0.16667. Note
- that a subtick count of 4 means dividing the major tick step into 5 sections.
-
- This is implemented by a hand made lookup for integer tick steps as well as fractional tick steps
- with a fractional part of (approximately) 0.5. If a tick step is different (i.e. has no
- fractional part close to 0.5), the currently set sub tick count (\ref setSubTickCount) is
- returned.
- */
- int QCPAxis::calculateAutoSubTickCount(double tickStep) const
- {
- int result = mSubTickCount; // default to current setting, if no proper value can be found
-
- // get mantissa of tickstep:
- double magnitudeFactor =
- qPow(10.0,
- qFloor(qLn(tickStep) / qLn(10.0))); // get magnitude factor e.g. 0.01, 1, 10, 1000 etc.
- double tickStepMantissa = tickStep / magnitudeFactor;
-
- // separate integer and fractional part of mantissa:
- double epsilon = 0.01;
- double intPartf;
- int intPart;
- double fracPart = modf(tickStepMantissa, &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 autoTickStep 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 isnt 0.0 or 0.5, don't bother finding good sub tick marks, leave
- // default
- }
-
- return result;
- }
-
- /* 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.
-
- \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.
-
- */
- void QCPAxis::draw(QCPPainter* painter)
- {
- const int lowTick = mLowestVisibleTick;
- const int highTick = mHighestVisibleTick;
- 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(highTick - lowTick + 1);
- tickLabels.reserve(highTick - lowTick + 1);
- subTickPositions.reserve(mSubTickVector.size());
-
- if (mTicks) {
- for (int i = lowTick; i <= highTick; ++i) {
- tickPositions.append(coordToPixel(mTickVector.at(i)));
- if (mTickLabels)
- tickLabels.append(mTickVectorLabels.at(i));
- }
-
- if (mSubTickCount > 0) {
- const int subTickCount = mSubTickVector.size();
- for (int i = 0; i < subTickCount; ++i) // no need to check bounds because subticks are
- // always only created inside current mRange
- 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 =
- mAutoTickLabels && mNumberBeautifulPowers && mTickLabelType == ltNumber;
- 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
-
- Returns via \a lowIndex and \a highIndex, which ticks in the current tick vector are visible in
- the current range. The return values are indices of the tick vector, not the positions of the
- ticks themselves.
-
- The actual use of this function is when an external tick vector is provided, since it might
- exceed far beyond the currently displayed range, and would cause unnecessary calculations e.g. of
- subticks.
-
- If all ticks are outside the axis range, an inverted range is returned, i.e. highIndex will be
- smaller than lowIndex. There is one case, where this function returns indices that are not really
- visible in the current axis range: When the tick spacing is larger than the axis range size and
- one tick is below the axis range and the next tick is already above the axis range. Because in
- such cases it is usually desirable to know the tick pair, to draw proper subticks.
- */
- void QCPAxis::visibleTickBounds(int& lowIndex, int& highIndex) const
- {
- bool lowFound = false;
- bool highFound = false;
- lowIndex = 0;
- highIndex = -1;
-
- for (int i = 0; i < mTickVector.size(); ++i) {
- if (mTickVector.at(i) >= mRange.lower) {
- lowFound = true;
- lowIndex = i;
- break;
- }
- }
- for (int i = mTickVector.size() - 1; i >= 0; --i) {
- if (mTickVector.at(i) <= mRange.upper) {
- highFound = true;
- highIndex = i;
- break;
- }
- }
-
- if (!lowFound && highFound)
- lowIndex = highIndex + 1;
- else if (lowFound && !highFound)
- highIndex = lowIndex - 1;
- }
-
- /*! \internal
-
- A log function with the base mScaleLogBase, used mostly for coordinate transforms in logarithmic
- scales with arbitrary log base. Uses the buffered mScaleLogBaseLogInv for faster calculation.
- This is set to <tt>1.0/qLn(mScaleLogBase)</tt> in \ref setScaleLogBase.
-
- \see basePow, setScaleLogBase, setScaleType
- */
- double QCPAxis::baseLog(double value) const
- {
- return qLn(value) * mScaleLogBaseLogInv;
- }
-
- /*! \internal
-
- A power function with the base mScaleLogBase, used mostly for coordinate transforms in
- logarithmic scales with arbitrary log base.
-
- \see baseLog, setScaleLogBase, setScaleType
- */
- double QCPAxis::basePow(double value) const
- {
- return qPow(mScaleLogBase, value);
- }
-
- /*! \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 caluclate margin needed to fit axis and its
- // labels
- int margin = 0;
-
- int lowTick, highTick;
- visibleTickBounds(lowTick, highTick);
- QVector<double>
- tickPositions; // the final coordToPixel transformed vector passed to QCPAxisPainter
- QVector<QString> tickLabels; // the final vector passed to QCPAxisPainter
- tickPositions.reserve(highTick - lowTick + 1);
- tickLabels.reserve(highTick - lowTick + 1);
- if (mTicks) {
- for (int i = lowTick; i <= highTick; ++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()));
- QVector2D baseLineVector(baseLine.dx(), baseLine.dy());
- if (lowerEnding.style() != QCPLineEnding::esNone)
- lowerEnding.draw(painter,
- QVector2D(baseLine.p1())
- - baseLineVector.normalized() * lowerEnding.realLength()
- * (lowerEnding.inverted() ? -1 : 1),
- -baseLineVector);
- if (upperEnding.style() != QCPLineEnding::esNone)
- upperEnding.draw(painter,
- QVector2D(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(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();
- 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()
- > viewportRect.right()
- || labelAnchor.x() + cachedLabel->offset.x() < viewportRect.left();
- else
- labelClippedByBorder =
- labelAnchor.y() + cachedLabel->offset.y() + cachedLabel->pixmap.height()
- > viewportRect.bottom()
- || labelAnchor.y() + cachedLabel->offset.y() < viewportRect.top();
- }
- if (!labelClippedByBorder) {
- painter->drawPixmap(labelAnchor + cachedLabel->offset, cachedLabel->pixmap);
- finalSize = cachedLabel->pixmap.size();
- }
- 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);
- 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);
- // 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);
- // 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, 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);
- result.totalBounds = result.baseBounds.adjusted(
- 0, 0, result.expBounds.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();
- } 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());
- }
-
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// 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).
-
- All further specifics are in the subclasses, for example:
- \li A normal graph with possibly a line, scatter points and error bars: \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
-
- To create an own plottable, you implement a subclass of QCPAbstractPlottable. These are the pure
- virtual functions, you must implement:
- \li \ref clearData
- \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>QPen \b mSelectedPen</td>
- <td>The generic pen that should be used when the plottable is selected (hint: \ref mainPen gives
- you the right pen, depending on selection state).</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>QBrush \b mSelectedBrush</td>
- <td>The generic brush that should be used when the plottable is selected (hint: \ref mainBrush
- gives you the right brush, depending on selection state).</td>
- </tr><tr>
- <td>QPointer<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>bool \b mSelected</td>
- <td>indicates whether the plottable is selected or not.</td>
- </tr>
- </table>
- */
-
- /* start of documentation of pure virtual functions */
-
- /*! \fn void QCPAbstractPlottable::clearData() = 0
- Clears all data in the plottable.
- */
-
- /*! \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, SignDomain inSignDomain) const
- = 0 \internal
-
- called by rescaleAxes functions to get the full data key bounds. For logarithmic plots, one can
- set \a inSignDomain to either \ref sdNegative or \ref 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 sdNegative and all positive points will be ignored for range calculation. For no
- restriction, just set \a inSignDomain to \ref 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, which wouldn't count as a valid range.
-
- \see rescaleAxes, getValueRange
- */
-
- /*! \fn QCPRange QCPAbstractPlottable::getValueRange(bool &foundRange, SignDomain inSignDomain)
- const = 0 \internal
-
- called by rescaleAxes functions to get the full data value bounds. For logarithmic plots, one can
- set \a inSignDomain to either \ref sdNegative or \ref 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 sdNegative and all positive points will be ignored for range calculation. For no
- restriction, just set \a inSignDomain to \ref 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, which wouldn't count as a valid range.
-
- \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 setSelected.
- */
-
- /*! \fn void QCPAbstractPlottable::selectableChanged(bool 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)
- , mAntialiasedErrorBars(false)
- , mPen(Qt::black)
- , mSelectedPen(Qt::black)
- , mBrush(Qt::NoBrush)
- , mSelectedBrush(Qt::NoBrush)
- , mKeyAxis(keyAxis)
- , mValueAxis(valueAxis)
- , mSelectable(true)
- , mSelected(false)
- {
- 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.";
- }
-
- /*!
- 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;
- }
-
- /*!
- Sets whether the error bars 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::setAntialiasedErrorBars(bool enabled)
- {
- mAntialiasedErrorBars = 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;
- }
-
- /*!
- When the plottable is selected, this pen is used to draw basic lines instead of the normal
- pen set via \ref setPen.
-
- \see setSelected, setSelectable, setSelectedBrush, selectTest
- */
- void QCPAbstractPlottable::setSelectedPen(const QPen& pen)
- {
- mSelectedPen = 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;
- }
-
- /*!
- When the plottable is selected, this brush is used to draw fills instead of the normal
- brush set via \ref setBrush.
-
- \see setSelected, setSelectable, setSelectedPen, selectTest
- */
- void QCPAbstractPlottable::setSelectedBrush(const QBrush& brush)
- {
- mSelectedBrush = 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 whether the user can (de-)select this plottable by clicking on the QCustomPlot surface.
- (When \ref QCustomPlot::setInteractions contains iSelectPlottables.)
-
- However, even when \a selectable was set to false, it is possible to set the selection manually,
- by calling \ref setSelected directly.
-
- \see setSelected
- */
- void QCPAbstractPlottable::setSelectable(bool selectable)
- {
- if (mSelectable != selectable) {
- mSelectable = selectable;
- emit selectableChanged(mSelectable);
- }
- }
-
- /*!
- Sets whether this plottable is selected or not. When selected, it uses a different pen and brush
- to draw its lines and fills, see \ref setSelectedPen and \ref setSelectedBrush.
-
- 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 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 QCPAbstractPlottable::setSelected(bool selected)
- {
- if (mSelected != selected) {
- mSelected = selected;
- emit selectionChanged(mSelected);
- }
- }
-
- /*!
- 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;
- }
-
- SignDomain signDomain = sdBoth;
- if (keyAxis->scaleType() == QCPAxis::stLogarithmic)
- signDomain = (keyAxis->range().upper < 0 ? sdNegative : 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.
-
- 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) const
- {
- QCPAxis* valueAxis = mValueAxis.data();
- if (!valueAxis) {
- qDebug() << Q_FUNC_INFO << "invalid value axis";
- return;
- }
-
- SignDomain signDomain = sdBoth;
- if (valueAxis->scaleType() == QCPAxis::stLogarithmic)
- signDomain = (valueAxis->range().upper < 0 ? sdNegative : sdPositive);
-
- bool foundRange;
- QCPRange newRange = getValueRange(foundRange, signDomain);
- 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);
- }
- }
-
- /*!
- Adds this plottable to the legend of the parent QCustomPlot (QCustomPlot::legend).
-
- Normally, a QCPPlottableLegendItem is created and inserted into the legend. If the plottable
- needs a more specialized representation in the legend, this function will take this into account
- and instead create the specialized subclass of QCPAbstractLegendItem.
-
- Returns true on success, i.e. when the legend exists and a legend item associated with this
- plottable isn't already in the legend.
-
- \see removeFromLegend, QCPLegend::addItem
- */
- bool QCPAbstractPlottable::addToLegend()
- {
- if (!mParentPlot || !mParentPlot->legend)
- return false;
-
- if (!mParentPlot->legend->hasItemWithPlottable(this)) {
- mParentPlot->legend->addItem(new QCPPlottableLegendItem(mParentPlot->legend, this));
- return true;
- } else
- return false;
- }
-
- /*!
- Removes the plottable from the legend of the parent QCustomPlot. This means the
- QCPAbstractLegendItem (usually a 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() const
- {
- if (!mParentPlot->legend)
- return false;
-
- if (QCPPlottableLegendItem* lip = mParentPlot->legend->itemWithPlottable(this))
- return mParentPlot->legend->removeItem(lip);
- else
- return false;
- }
-
- /* 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
-
- 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);
- }
- }
-
- /*! \internal
- \overload
-
- Returns the input as pixel coordinates 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));
- }
-
- /*! \internal
-
- 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);
- }
- }
-
- /*! \internal
- \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);
- }
-
- /*! \internal
-
- Returns the pen that should be used for drawing lines of the plottable. Returns mPen when the
- graph is not selected and mSelectedPen when it is.
- */
- QPen QCPAbstractPlottable::mainPen() const
- {
- return mSelected ? mSelectedPen : mPen;
- }
-
- /*! \internal
-
- Returns the brush that should be used for drawing fills of the plottable. Returns mBrush when the
- graph is not selected and mSelectedBrush when it is.
- */
- QBrush QCPAbstractPlottable::mainBrush() const
- {
- return mSelected ? mSelectedBrush : mBrush;
- }
-
- /*! \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.
-
- \see setAntialiased, applyFillAntialiasingHint, applyScattersAntialiasingHint,
- applyErrorBarsAntialiasingHint
- */
- 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,
- applyErrorBarsAntialiasingHint
- */
- 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,
- applyErrorBarsAntialiasingHint
- */
- void QCPAbstractPlottable::applyScattersAntialiasingHint(QCPPainter* painter) const
- {
- applyAntialiasingHint(painter, mAntialiasedScatters, QCP::aeScatters);
- }
-
- /*! \internal
-
- A convenience function to easily set the QPainter::Antialiased hint on the provided \a painter
- before drawing plottable error bars.
-
- 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, applyScattersAntialiasingHint,
- applyDefaultAntialiasingHint
- */
- void QCPAbstractPlottable::applyErrorBarsAntialiasingHint(QCPPainter* painter) const
- {
- applyAntialiasingHint(painter, mAntialiasedErrorBars, QCP::aeErrorBars);
- }
-
- /*! \internal
-
- Finds the shortest squared distance of \a point to the line segment defined by \a start and \a
- end.
-
- This function may be used to help with the implementation of the \ref selectTest function for
- specific plottables.
-
- \note This function is identical to QCPAbstractItem::distSqrToLine
- */
- double QCPAbstractPlottable::distSqrToLine(const QPointF& start, const QPointF& end,
- const QPointF& point) const
- {
- QVector2D a(start);
- QVector2D b(end);
- QVector2D p(point);
- QVector2D v(b - a);
-
- double vLengthSqr = v.lengthSquared();
- if (!qFuzzyIsNull(vLengthSqr)) {
- double mu = QVector2D::dotProduct(p - a, v) / vLengthSqr;
- if (mu < 0)
- return (a - p).lengthSquared();
- else if (mu > 1)
- return (b - p).lengthSquared();
- else
- return ((a + mu * v) - p).lengthSquared();
- } else
- return (a - p).lengthSquared();
- }
-
- /* inherits documentation from base class */
- void QCPAbstractPlottable::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 QCPAbstractPlottable::deselectEvent(bool* selectionStateChanged)
- {
- if (mSelectable) {
- bool selBefore = mSelected;
- setSelected(false);
- if (selectionStateChanged)
- *selectionStateChanged = mSelected != selBefore;
- }
- }
-
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// 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::pixelPoint() const
- {
- if (mParentItem) {
- if (mAnchorId > -1) {
- return mParentItem->anchorPixelPoint(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 setPixelPoint.
- This works no matter what type this QCPItemPosition is or what parent-child situation it is in, as
- \ref setPixelPoint 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::pixelPoint function instead of
- // QCPItemAnchor::pixelPoint
- 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 pixelPoint() 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 = pixelPoint();
-
- mPositionTypeX = type;
-
- if (retainPixelPosition)
- setPixelPoint(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 pixelPoint() 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 = pixelPoint();
-
- mPositionTypeY = type;
-
- if (retainPixelPosition)
- setPixelPoint(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 = pixelPoint();
- // 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)
- setPixelPoint(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 = pixelPoint();
- // 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)
- setPixelPoint(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 setPixelPoint
- */
- 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 setPixelPoint
- */
- QPointF QCPItemPosition::pixelPoint() const
- {
- QPointF result;
-
- // determine X:
- switch (mPositionTypeX) {
- case ptAbsolute: {
- result.rx() = mKey;
- if (mParentAnchorX)
- result.rx() += mParentAnchorX->pixelPoint().x();
- break;
- }
- case ptViewportRatio: {
- result.rx() = mKey * mParentPlot->viewport().width();
- if (mParentAnchorX)
- result.rx() += mParentAnchorX->pixelPoint().x();
- else
- result.rx() += mParentPlot->viewport().left();
- break;
- }
- case ptAxisRectRatio: {
- if (mAxisRect) {
- result.rx() = mKey * mAxisRect.data()->width();
- if (mParentAnchorX)
- result.rx() += mParentAnchorX->pixelPoint().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->pixelPoint().y();
- break;
- }
- case ptViewportRatio: {
- result.ry() = mValue * mParentPlot->viewport().height();
- if (mParentAnchorY)
- result.ry() += mParentAnchorY->pixelPoint().y();
- else
- result.ry() += mParentPlot->viewport().top();
- break;
- }
- case ptAxisRectRatio: {
- if (mAxisRect) {
- result.ry() = mValue * mAxisRect.data()->height();
- if (mParentAnchorY)
- result.ry() += mParentAnchorY->pixelPoint().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 pixelPoint, setCoords
- */
- void QCPItemPosition::setPixelPoint(const QPointF& pixelPoint)
- {
- double x = pixelPoint.x();
- double y = pixelPoint.y();
-
- switch (mPositionTypeX) {
- case ptAbsolute: {
- if (mParentAnchorX)
- x -= mParentAnchorX->pixelPoint().x();
- break;
- }
- case ptViewportRatio: {
- if (mParentAnchorX)
- x -= mParentAnchorX->pixelPoint().x();
- else
- x -= mParentPlot->viewport().left();
- x /= (double)mParentPlot->viewport().width();
- break;
- }
- case ptAxisRectRatio: {
- if (mAxisRect) {
- if (mParentAnchorX)
- x -= mParentAnchorX->pixelPoint().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->pixelPoint().y();
- break;
- }
- case ptViewportRatio: {
- if (mParentAnchorY)
- y -= mParentAnchorY->pixelPoint().y();
- else
- y -= mParentPlot->viewport().top();
- y /= (double)mParentPlot->viewport().height();
- break;
- }
- case ptAxisRectRatio: {
- if (mAxisRect) {
- if (mParentAnchorY)
- y -= mParentAnchorY->pixelPoint().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::pixelPoint.
-
- 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 distSqrToLine and
- \ref rectSelectTest. 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 anchorPixelPoint(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)
- {
- 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, the \ref QCustomPlot::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
-
- Finds the shortest squared distance of \a point to the line segment defined by \a start and \a
- end.
-
- This function may be used to help with the implementation of the \ref selectTest function for
- specific items.
-
- \note This function is identical to QCPAbstractPlottable::distSqrToLine
-
- \see rectSelectTest
- */
- double QCPAbstractItem::distSqrToLine(const QPointF& start, const QPointF& end,
- const QPointF& point) const
- {
- QVector2D a(start);
- QVector2D b(end);
- QVector2D p(point);
- QVector2D v(b - a);
-
- double vLengthSqr = v.lengthSquared();
- if (!qFuzzyIsNull(vLengthSqr)) {
- double mu = QVector2D::dotProduct(p - a, v) / vLengthSqr;
- if (mu < 0)
- return (a - p).lengthSquared();
- else if (mu > 1)
- return (b - p).lengthSquared();
- else
- return ((a + mu * v) - p).lengthSquared();
- } else
- return (a - p).lengthSquared();
- }
-
- /*! \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 of all four returned
- values.
-
- \see distSqrToLine
- */
- double QCPAbstractItem::rectSelectTest(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 = distSqrToLine(lines.at(i).p1(), lines.at(i).p2(), pos);
- 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::anchorPixelPoint(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::anchorPixelPoint. 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;
- }
-
- /*! \file */
-
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// 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 QRect QCustomPlot::viewport() const
-
- Returns the viewport rect of this QCustomPlot instance. The viewport is the area the plot is
- drawn in, 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, the plot title etc.
-
- Only when saving to a file (see \ref savePng, \ref savePdf etc.) the viewport is temporarily
- modified to allow saving plots with sizes independent of the current widget size.
- */
-
- /*! \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, 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.
-
- \see plottableDoubleClick
- */
-
- /*! \fn void QCustomPlot::plottableDoubleClick(QCPAbstractPlottable *plottable, 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.
-
- \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:: titleClick(QMouseEvent *event, QCPPlotTitle *title)
-
- This signal is emitted when a plot title is clicked.
-
- \a event is the mouse event that caused the click and \a title is the plot title that received
- the click.
-
- \see titleDoubleClick
- */
-
- /*! \fn void QCustomPlot::titleDoubleClick(QMouseEvent *event, QCPPlotTitle *title)
-
- This signal is emitted when a plot title is double clicked.
-
- \a event is the mouse event that caused the click and \a title is the plot title that received
- the click.
-
- \see titleClick
- */
-
- /*! \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 setSelected() on an object or by calling \ref deselectAll.
-
- In addition to this signal, selectable objects also provide individual signals, for example
- QCPAxis::selectionChanged or 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.
- */
-
- /*! \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.
- */
-
- /*! \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.
- */
-
- /*! \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.
- */
-
- /*! \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.
- */
-
- /* 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)
- , 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::phForceRepaint)
- , mMultiSelectModifier(Qt::ControlModifier)
- , mPaintBuffer(size())
- , mMouseEventElement(0)
- , mReplotting(false)
- // 增加游标
- //, m_isShowTracer(false)
- //, m_xTracer(Q_NULLPTR)
- //, m_yTracer(Q_NULLPTR)
- //, m_dataTracers(QList<XxwTracer*>())
- //, m_lineTracer(Q_NULLPTR)
- {
- setAttribute(Qt::WA_NoMousePropagation);
- setAttribute(Qt::WA_OpaquePaintEvent);
- setMouseTracking(true);
- QLocale currentLocale = locale();
- currentLocale.setNumberOptions(QLocale::OmitGroupSeparator);
- setLocale(currentLocale);
-
- // 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")));
- updateLayerIndices();
- setCurrentLayer(QLatin1String("main"));
-
- // 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"));
-
- setViewport(rect()); // needs to be called after mPlotLayout has been created
-
- replot();
- }
-
- 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& notAntialiasedElements)
- {
- 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 addPlottable, 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 selection</b> is controlled by \ref QCP::iSelectPlottables. If \ref
- QCP::iSelectPlottables is set, the user may select plottables (graphs, curves, bars,...) by
- clicking on them or in their vicinity (\ref setSelectionTolerance). Whether the user can actually
- select a plottable can further be restricted with the \ref QCPAbstractPlottable::setSelectable
- function on the specific plottable. To find out whether a specific plottable is selected, call
- QCPAbstractPlottable::selected(). 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.
- QCPPlotTitle, 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 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 the viewport of this QCustomPlot. The Viewport is the area that the top level layout
- (QCustomPlot::plotLayout()) uses as its rect. Normally, the viewport is the entire widget rect.
-
- 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 \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, addPlottable
- */
- 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 with \ref addPlottable. If there are no plottables in
- the plot, returns 0.
-
- \see plottableCount, addPlottable
- */
- QCPAbstractPlottable* QCustomPlot::plottable()
- {
- if (!mPlottables.isEmpty()) {
- return mPlottables.last();
- } else
- return 0;
- }
-
- /*!
- Adds the specified plottable to the plot and, if \ref setAutoAddPlottableToLegend is enabled, to
- the legend (QCustomPlot::legend). QCustomPlot takes ownership of the plottable.
-
- Returns true on success, i.e. when \a plottable isn't already in the plot and the parent plot of
- \a plottable is this QCustomPlot (the latter is controlled by what axes were passed in the
- plottable's constructor).
-
- \see plottable, plottableCount, removePlottable, clearPlottables
- */
- bool QCustomPlot::addPlottable(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();
- // special handling for QCPGraphs to maintain the simple graph interface:
- if (QCPGraph* graph = qobject_cast<QCPGraph*>(plottable))
- mGraphs.append(graph);
- if (!plottable->layer()) // usually the layer is already set in the constructor of the plottable
- // (via QCPLayerable constructor)
- plottable->setLayer(currentLayer());
- return true;
- }
-
- /*!
- Removes the specified plottable from the plot and, if necessary, from the legend
- (QCustomPlot::legend).
-
- Returns true on success.
-
- \see addPlottable, 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 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 the QCustomPlot::legend, if necessary).
-
- 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, addPlottable
- */
- 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::setSelected
- */
- 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.
-
- \see addPlottable
- */
- 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);
- if (addPlottable(newGraph)) {
- newGraph->setName(QLatin1String("Graph ") + QString::number(mGraphs.size()));
- return newGraph;
- } else {
- delete newGraph;
- return 0;
- }
- }
-
- /*!
- Removes the specified \a graph from the plot and, if necessary, from the 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 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 the QCustomPlot::legend, if necessary).
-
- 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::setSelected
- */
- 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, addItem
- */
- 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 with \ref addItem. If there are no items in the plot,
- returns 0.
-
- \see itemCount, addItem
- */
- QCPAbstractItem* QCustomPlot::item() const
- {
- if (!mItems.isEmpty()) {
- return mItems.last();
- } else
- return 0;
- }
-
- /*!
- Adds the specified item to the plot. 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.
-
- \see item, itemCount, removeItem, clearItems
- */
- bool QCustomPlot::addItem(QCPAbstractItem* item)
- {
- if (!mItems.contains(item) && item->parentPlot() == this) {
- mItems.append(item);
- return true;
- } else {
- qDebug() << Q_FUNC_INFO
- << "item either already in list or not created with this QCustomPlot as parent:"
- << reinterpret_cast<quintptr>(item);
- return false;
- }
- }
-
- /*!
- Removes the specified item from the plot.
-
- Returns true on success.
-
- \see addItem, 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 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.
-
- 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, addItem
- */
- 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 addItem
- */
- 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();
- 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);
- // 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));
-
- 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 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 buffer. Finally, update() is called, to redraw the
- buffer on the QCustomPlot widget surface. This is the method that must be called to make changes,
- for example on the axis ranges or data points of graphs, visible.
-
- 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.
- */
- void QCustomPlot::replot(QCustomPlot::RefreshPriority refreshPriority)
- {
- if (mReplotting) // incase signals loop back to replot slot
- return;
- mReplotting = true;
- emit beforeReplot();
-
- mPaintBuffer.fill(mBackgroundBrush.style() == Qt::SolidPattern ? mBackgroundBrush.color()
- : Qt::transparent);
- QCPPainter painter;
- painter.begin(&mPaintBuffer);
- if (painter.isActive()) {
- painter.setRenderHint(
- QPainter::HighQualityAntialiasing); // to make Antialiasing look good if using the
- // OpenGL graphicssystem
- if (mBackgroundBrush.style() != Qt::SolidPattern && mBackgroundBrush.style() != Qt::NoBrush)
- painter.fillRect(mViewport, mBackgroundBrush);
- draw(&painter);
- painter.end();
- if ((refreshPriority == rpHint && mPlottingHints.testFlag(QCP::phForceRepaint))
- || refreshPriority == rpImmediate)
- repaint();
- else
- update();
- } else // might happen if QCustomPlot has width or height zero
- qDebug() << Q_FUNC_INFO
- << "Couldn't activate painter on buffer. This usually happens because QCustomPlot "
- "has width or height zero.";
-
- 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.
-
- \a noCosmeticPen disables 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 noCosmeticPen to true 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 QT_NO_PRINTER is set.
-
- \see savePng, saveBmp, saveJpg, saveRastered
- */
- bool QCustomPlot::savePdf(const QString& fileName, bool noCosmeticPen, int width, int height,
- const QString& pdfCreator, const QString& pdfTitle)
- {
- bool success = false;
- #ifdef QT_NO_PRINTER
- Q_UNUSED(fileName)
- Q_UNUSED(noCosmeticPen)
- 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, noCosmeticPen);
- 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. If either \a width or \a height is zero, the exported image will have
- the same dimensions as the QCustomPlot widget currently has. 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 via
- temporarily setting \ref QCustomPlot::setAntialiasedElements to \ref QCP::aeAll as this allows
- QCustomPlot to place objects with sub-pixel accuracy.
-
- \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.
-
- 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.
-
- PNG compression can be controlled with the \a quality parameter which must be between 0 and 100 or
- -1 to use the default setting.
-
- 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().
-
- \see savePdf, saveBmp, saveJpg, saveRastered
- */
- bool QCustomPlot::savePng(const QString& fileName, int width, int height, double scale, int quality)
- {
- return saveRastered(fileName, width, height, scale, "PNG", quality);
- }
-
- /*!
- Saves a JPG image file to \a fileName on disc. The output plot will have the dimensions \a width
- and \a height in pixels. If either \a width or \a height is zero, the exported image will have
- the same dimensions as the QCustomPlot widget currently has. 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 via
- temporarily setting \ref QCustomPlot::setAntialiasedElements to \ref QCP::aeAll as this allows
- QCustomPlot to place objects with sub-pixel accuracy.
-
- \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.
-
- 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.
-
- JPG compression can be controlled with the \a quality parameter which must be between 0 and 100 or
- -1 to use the default setting.
-
- Returns true on success. If this function fails, most likely the JPG format isn't supported by
- the system, see Qt docs about QImageWriter::supportedImageFormats().
-
- \see savePdf, savePng, saveBmp, saveRastered
- */
- bool QCustomPlot::saveJpg(const QString& fileName, int width, int height, double scale, int quality)
- {
- return saveRastered(fileName, width, height, scale, "JPG", quality);
- }
-
- /*!
- Saves a BMP image file to \a fileName on disc. The output plot will have the dimensions \a width
- and \a height in pixels. If either \a width or \a height is zero, the exported image will have
- the same dimensions as the QCustomPlot widget currently has. 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 via
- temporarily setting \ref QCustomPlot::setAntialiasedElements to \ref QCP::aeAll as this allows
- QCustomPlot to place objects with sub-pixel accuracy.
-
- \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.
-
- 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. If this function fails, most likely the BMP format isn't supported by
- the system, see Qt docs about QImageWriter::supportedImageFormats().
-
- \see savePdf, savePng, saveJpg, saveRastered
- */
- bool QCustomPlot::saveBmp(const QString& fileName, int width, int height, double scale)
- {
- return saveRastered(fileName, width, height, scale, "BMP");
- }
-
- /*! \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);
- QPainter painter(this);
- painter.drawPixmap(0, 0, mPaintBuffer);
- }
-
- /*! \internal
-
- Event handler for a resize of the QCustomPlot widget. Causes the internal buffer to be resized to
- the new size. The viewport (which becomes the outer rect of mPlotLayout) is resized
- appropriately. Finally a \ref replot is performed.
- */
- void QCustomPlot::resizeEvent(QResizeEvent* event)
- {
- // resize and repaint the buffer:
- mPaintBuffer = QPixmap(event->size());
- setViewport(rect());
- replot(
- rpQueued); // queued update is important here, to prevent painting issues in some contexts
- }
-
- /*! \internal
-
- Event handler for when a double click occurs. Emits the \ref mouseDoubleClick signal, then emits
- the specialized signals when certain objecs are clicked (e.g. \ref plottableDoubleClick, \ref
- axisDoubleClick, etc.). Finally determines the affected layout element and forwards the event to
- it.
-
- \see mousePressEvent, mouseReleaseEvent
- */
- void QCustomPlot::mouseDoubleClickEvent(QMouseEvent* event)
- {
- emit mouseDoubleClick(event);
-
- QVariant details;
- QCPLayerable* clickedLayerable = layerableAt(event->pos(), false, &details);
-
- // emit specialized object double click signals:
- if (QCPAbstractPlottable* ap = qobject_cast<QCPAbstractPlottable*>(clickedLayerable))
- emit plottableDoubleClick(ap, event);
- else if (QCPAxis* ax = qobject_cast<QCPAxis*>(clickedLayerable))
- emit axisDoubleClick(ax, details.value<QCPAxis::SelectablePart>(), event);
- else if (QCPAbstractItem* ai = qobject_cast<QCPAbstractItem*>(clickedLayerable))
- emit itemDoubleClick(ai, event);
- else if (QCPLegend* lg = qobject_cast<QCPLegend*>(clickedLayerable))
- emit legendDoubleClick(lg, 0, event);
- else if (QCPAbstractLegendItem* li = qobject_cast<QCPAbstractLegendItem*>(clickedLayerable))
- emit legendDoubleClick(li->parentLegend(), li, event);
- else if (QCPPlotTitle* pt = qobject_cast<QCPPlotTitle*>(clickedLayerable))
- emit titleDoubleClick(event, pt);
-
- // call double click event of affected layout element:
- if (QCPLayoutElement* el = layoutElementAt(event->pos()))
- el->mouseDoubleClickEvent(event);
-
- // call release event of affected layout element (as in mouseReleaseEvent, since the
- // mouseDoubleClick replaces the second release event in double click case):
- if (mMouseEventElement) {
- mMouseEventElement->mouseReleaseEvent(event);
- mMouseEventElement = 0;
- }
-
- // QWidget::mouseDoubleClickEvent(event); don't call base class implementation because it would
- // just cause a mousePress/ReleaseEvent, which we don't want.
- }
-
- /*! \internal
-
- Event handler for when a mouse button is pressed. Emits the mousePress signal. Then determines
- the affected layout element and forwards the event to it.
-
- \see mouseMoveEvent, mouseReleaseEvent
- */
- void QCustomPlot::mousePressEvent(QMouseEvent* event)
- {
- emit mousePress(event);
- mMousePressPos = event->pos(); // need this to determine in releaseEvent whether it was a click
- // (no position change between press and release)
-
- // call event of affected layout element:
- mMouseEventElement = layoutElementAt(event->pos());
- if (mMouseEventElement)
- mMouseEventElement->mousePressEvent(event);
-
- QWidget::mousePressEvent(event);
- }
-
- /*! \internal
-
- Event handler for when the cursor is moved. Emits the \ref mouseMove signal.
-
- 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);
-
- // call event of affected layout element:
- if (mMouseEventElement)
- mMouseEventElement->mouseMoveEvent(event);
-
- QWidget::mouseMoveEvent(event);
- /*
- //增加游标
- if (m_isShowTracer)
- {
- //当前鼠标位置(像素坐标)
- int x_pos = event->pos().x();
- int y_pos = event->pos().y();
-
- //像素坐标转成实际的x,y轴的坐标
- float x_val = this->xAxis->pixelToCoord(x_pos);
- float y_val = this->yAxis->pixelToCoord(y_pos);
-
-
-
- if (Q_NULLPTR == m_xTracer)
- m_xTracer = new XxwTracer(this, XxwTracer::DataTracer);//x轴
- m_xTracer->updatePosition(x_val, y_val);
-
- if (Q_NULLPTR == m_lineTracer)
- m_lineTracer = new XxwTraceLine(this, XxwTraceLine::Both);//直线
- m_lineTracer->updatePosition(x_val, y_val);
-
- this->replot();//曲线重绘
- }
- */
- }
-
- /*! \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 layout element has mouse capture focus (a \ref mousePressEvent happened on top of the layout
- element before), the \ref mouseReleaseEvent is forwarded to that element.
-
- \see mousePressEvent, mouseMoveEvent
- */
- void QCustomPlot::mouseReleaseEvent(QMouseEvent* event)
- {
- emit mouseRelease(event);
- bool doReplot = false;
-
- if ((mMousePressPos - event->pos()).manhattanLength()
- < 5) // determine whether it was a click operation
- {
- if (event->button() == Qt::LeftButton) {
- // handle selection mechanism:
- 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) {
- doReplot = true;
- emit selectionChangedByUser();
- }
- }
-
- // emit specialized object click signals:
- QVariant details;
- QCPLayerable* clickedLayerable =
- layerableAt(event->pos(), false,
- &details); // for these signals, selectability is ignored, that's why we
- // call this again with onlySelectable set to false
- if (QCPAbstractPlottable* ap = qobject_cast<QCPAbstractPlottable*>(clickedLayerable))
- emit plottableClick(ap, event);
- else if (QCPAxis* ax = qobject_cast<QCPAxis*>(clickedLayerable))
- emit axisClick(ax, details.value<QCPAxis::SelectablePart>(), event);
- else if (QCPAbstractItem* ai = qobject_cast<QCPAbstractItem*>(clickedLayerable))
- emit itemClick(ai, event);
- else if (QCPLegend* lg = qobject_cast<QCPLegend*>(clickedLayerable))
- emit legendClick(lg, 0, event);
- else if (QCPAbstractLegendItem* li = qobject_cast<QCPAbstractLegendItem*>(clickedLayerable))
- emit legendClick(li->parentLegend(), li, event);
- else if (QCPPlotTitle* pt = qobject_cast<QCPPlotTitle*>(clickedLayerable))
- emit titleClick(event, pt);
- }
-
- // call event of affected layout element:
- if (mMouseEventElement) {
- mMouseEventElement->mouseReleaseEvent(event);
- mMouseEventElement = 0;
- }
-
- if (doReplot || noAntialiasingOnDrag())
- replot();
-
- QWidget::mouseReleaseEvent(event);
- }
-
- /*! \internal
-
- Event handler for mouse wheel events. First, the \ref mouseWheel signal is emitted. Then
- determines the affected layout element and forwards the event to it.
-
- */
- void QCustomPlot::wheelEvent(QWheelEvent* event)
- {
- emit mouseWheel(event);
-
- // call event of affected layout element:
- if (QCPLayoutElement* el = layoutElementAt(event->pos()))
- el->wheelEvent(event);
-
- QWidget::wheelEvent(event);
- }
-
- /*! \internal
-
- This is the main draw function. It draws the entire plot, including background pixmap, with the
- specified \a painter. 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 (e.g. \ref replot, \ref toPixmap and \ref toPainter).
- */
- void QCustomPlot::draw(QCPPainter* painter)
- {
- // run through layout phases:
- mPlotLayout->update(QCPLayoutElement::upPreparation);
- mPlotLayout->update(QCPLayoutElement::upMargins);
- mPlotLayout->update(QCPLayoutElement::upLayout);
-
- // draw viewport background pixmap:
- drawBackground(painter);
-
- // draw all layered objects (grid, axes, plottables, items, legend,...):
- foreach (QCPLayer* layer, mLayers) {
- foreach (QCPLayerable* child, layer->children()) {
- if (child->realVisibility()) {
- painter->save();
- painter->setClipRect(child->clipRect().translated(0, -1));
- child->applyDefaultAntialiasingHint(painter);
- child->draw(painter);
- painter->restore();
- }
- }
- }
-
- /* 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
-
- 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
-
- 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
-
- 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 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).
- */
- QCPLayerable* QCustomPlot::layerableAt(const QPointF& pos, bool onlySelectable,
- QVariant* selectionDetails) const
- {
- for (int layerIndex = mLayers.size() - 1; layerIndex >= 0; --layerIndex) {
- const QList<QCPLayerable*> layerables = mLayers.at(layerIndex)->children();
- double minimumDistance = selectionTolerance() * 1.1;
- QCPLayerable* minimumDistanceLayerable = 0;
- 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, &details);
- if (dist >= 0 && dist < minimumDistance) {
- minimumDistance = dist;
- minimumDistanceLayerable = layerables.at(i);
- if (selectionDetails)
- *selectionDetails = details;
- }
- }
- if (minimumDistance < selectionTolerance())
- return minimumDistanceLayerable;
- }
- return 0;
- }
-
- /*!
- 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().
-
- \see saveBmp, saveJpg, savePng, savePdf
- */
- bool QCustomPlot::saveRastered(const QString& fileName, int width, int height, double scale,
- const char* format, int quality)
- {
- QPixmap buffer = toPixmap(width, height, scale);
- 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";
- }
-
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// 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.
-
- \image html QCPColorGradient.png
-
- The fact that the \ref QCPColorGradient(GradientPreset preset) constructor allows directly
- converting a \ref GradientPreset to a QCPColorGradient, you can also 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 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.
-
- \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 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;
- }
-
- /*!
- 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>.
- */
- 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()
- 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);
- }
- }
- }
- }
-
- /*! \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.
- */
- 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
-
- 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);
- 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().rgb();
- } else if (it == mColorStops.constBegin()) // position is on or before first stop, use
- // color of first stop
- {
- mColorBuffer[i] = it.value().rgb();
- } 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: {
- 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;
- 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) {
- mColorBuffer.fill(mColorStops.constBegin().value().rgb());
- } else // mColorStops is empty, fill color buffer with black
- {
- mColorBuffer.fill(qRgb(0, 0, 0));
- }
- mColorBufferInvalidated = false;
- }
-
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// 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.
-
- 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);
- 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;
- }
-
- /*!
- 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 scale log base (\ref QCPAxis::setScaleLogBase)
- \li ticks (\ref QCPAxis::setTicks)
- \li auto (major) tick count (\ref QCPAxis::setAutoTickCount)
- \li sub tick count (\ref QCPAxis::setSubTickCount)
- \li auto sub ticks (\ref QCPAxis::setAutoSubTicks)
- \li tick step (\ref QCPAxis::setTickStep)
- \li auto tick step (\ref QCPAxis::setAutoTickStep)
- \li number format (\ref QCPAxis::setNumberFormat)
- \li number precision (\ref QCPAxis::setNumberPrecision)
- \li tick label type (\ref QCPAxis::setTickLabelType)
- \li date time format (\ref QCPAxis::setDateTimeFormat)
- \li date time spec (\ref QCPAxis::setDateTimeSpec)
-
- Tick labels (\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->setScaleLogBase(xAxis->scaleLogBase());
- xAxis2->setTicks(xAxis->ticks());
- xAxis2->setAutoTickCount(xAxis->autoTickCount());
- xAxis2->setSubTickCount(xAxis->subTickCount());
- xAxis2->setAutoSubTicks(xAxis->autoSubTicks());
- xAxis2->setTickStep(xAxis->tickStep());
- xAxis2->setAutoTickStep(xAxis->autoTickStep());
- xAxis2->setNumberFormat(xAxis->numberFormat());
- xAxis2->setNumberPrecision(xAxis->numberPrecision());
- xAxis2->setTickLabelType(xAxis->tickLabelType());
- xAxis2->setDateTimeFormat(xAxis->dateTimeFormat());
- xAxis2->setDateTimeSpec(xAxis->dateTimeSpec());
-
- yAxis2->setRange(yAxis->range());
- yAxis2->setRangeReversed(yAxis->rangeReversed());
- yAxis2->setScaleType(yAxis->scaleType());
- yAxis2->setScaleLogBase(yAxis->scaleLogBase());
- yAxis2->setTicks(yAxis->ticks());
- yAxis2->setAutoTickCount(yAxis->autoTickCount());
- yAxis2->setSubTickCount(yAxis->subTickCount());
- yAxis2->setAutoSubTicks(yAxis->autoSubTicks());
- yAxis2->setTickStep(yAxis->tickStep());
- yAxis2->setAutoTickStep(yAxis->autoTickStep());
- yAxis2->setNumberFormat(yAxis->numberFormat());
- yAxis2->setNumberPrecision(yAxis->numberPrecision());
- yAxis2->setTickLabelType(yAxis->tickLabelType());
- yAxis2->setDateTimeFormat(yAxis->dateTimeFormat());
- yAxis2->setDateTimeSpec(yAxis->dateTimeSpec());
-
- 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.
- */
- 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.
-
- \see setRangeDragAxes
- */
- QCPAxis* QCPAxisRect::rangeDragAxis(Qt::Orientation orientation)
- {
- return (orientation == Qt::Horizontal ? mRangeDragHorzAxis.data() : mRangeDragVertAxis.data());
- }
-
- /*!
- Returns the range zoom axis of the \a orientation provided.
-
- \see setRangeZoomAxes
- */
- QCPAxis* QCPAxisRect::rangeZoomAxis(Qt::Orientation orientation)
- {
- return (orientation == Qt::Horizontal ? mRangeZoomHorzAxis.data() : mRangeZoomVertAxis.data());
- }
-
- /*!
- 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;
- }
-
- /*!
- Sets the axes whose range will be dragged when \ref setRangeDrag enables mouse range dragging
- on the QCustomPlot widget.
-
- \see setRangeZoomAxes
- */
- void QCPAxisRect::setRangeDragAxes(QCPAxis* horizontal, QCPAxis* vertical)
- {
- mRangeDragHorzAxis = horizontal;
- mRangeDragVertAxis = vertical;
- }
-
- /*!
- Sets the axes whose range will be zoomed when \ref setRangeZoom enables mouse wheel zooming on the
- QCustomPlot widget. The two axes can be zoomed with different strengths, when different factors
- are passed to \ref setRangeZoomFactor(double horizontalFactor, double verticalFactor).
-
- \see setRangeDragAxes
- */
- void QCPAxisRect::setRangeZoomAxes(QCPAxis* horizontal, QCPAxis* vertical)
- {
- mRangeZoomHorzAxis = horizontal;
- mRangeZoomVertAxis = vertical;
- }
-
- /*!
- 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
- dependant on the \ref setBackgroundScaledMode), or when a differend axis backgroud 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
-
- 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)
- {
- 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)) {
- if (mRangeDragHorzAxis)
- mDragStartHorzRange = mRangeDragHorzAxis.data()->range();
- if (mRangeDragVertAxis)
- mDragStartVertRange = mRangeDragVertAxis.data()->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)
- {
- // Mouse range dragging interaction:
- if (mDragging && mParentPlot->interactions().testFlag(QCP::iRangeDrag)) {
- if (mRangeDrag.testFlag(Qt::Horizontal)) {
- if (QCPAxis* rangeDragHorzAxis = mRangeDragHorzAxis.data()) {
- if (rangeDragHorzAxis->mScaleType == QCPAxis::stLinear) {
- double diff = rangeDragHorzAxis->pixelToCoord(mDragStart.x())
- - rangeDragHorzAxis->pixelToCoord(event->pos().x());
- rangeDragHorzAxis->setRange(mDragStartHorzRange.lower + diff,
- mDragStartHorzRange.upper + diff);
- } else if (rangeDragHorzAxis->mScaleType == QCPAxis::stLogarithmic) {
- double diff = rangeDragHorzAxis->pixelToCoord(mDragStart.x())
- / rangeDragHorzAxis->pixelToCoord(event->pos().x());
- rangeDragHorzAxis->setRange(mDragStartHorzRange.lower * diff,
- mDragStartHorzRange.upper * diff);
- }
- }
- }
- if (mRangeDrag.testFlag(Qt::Vertical)) {
- if (QCPAxis* rangeDragVertAxis = mRangeDragVertAxis.data()) {
- if (rangeDragVertAxis->mScaleType == QCPAxis::stLinear) {
- double diff = rangeDragVertAxis->pixelToCoord(mDragStart.y())
- - rangeDragVertAxis->pixelToCoord(event->pos().y());
- rangeDragVertAxis->setRange(mDragStartVertRange.lower + diff,
- mDragStartVertRange.upper + diff);
- } else if (rangeDragVertAxis->mScaleType == QCPAxis::stLogarithmic) {
- double diff = rangeDragVertAxis->pixelToCoord(mDragStart.y())
- / rangeDragVertAxis->pixelToCoord(event->pos().y());
- rangeDragVertAxis->setRange(mDragStartVertRange.lower * diff,
- mDragStartVertRange.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)
- {
- Q_UNUSED(event)
- 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
- dependant 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);
- if (mRangeZoomHorzAxis.data())
- mRangeZoomHorzAxis.data()->scaleRange(
- factor, mRangeZoomHorzAxis.data()->pixelToCoord(event->pos().x()));
- }
- if (mRangeZoom.testFlag(Qt::Vertical)) {
- factor = qPow(mRangeZoomFactorVert, wheelSteps);
- if (mRangeZoomVertAxis.data())
- mRangeZoomVertAxis.data()->scaleRange(
- factor, mRangeZoomVertAxis.data()->pixelToCoord(event->pos().y()));
- }
- mParentPlot->replot();
- }
- }
- }
-
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// 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(8, 2, 8, 2));
- }
-
- /*!
- 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)
- {}
-
- /*! \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);
- painter->drawRect(iconRect);
- }
- }
-
- /*! \internal
-
- Calculates and returns the size of this item. This includes the icon, the text and the padding in
- between.
- */
- 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.
-
- The QCPLegend derives from QCPLayoutGrid and as such can be placed in any position a
- QCPLayoutElement may be positioned. The legend items are themselves QCPLayoutElements which are
- placed in the grid layout of the legend. QCPLegend only adds an interface specialized for
- handling child elements of type QCPAbstractLegendItem, as mentioned above. In principle, any
- other layout elements may also be added to a legend via the normal \ref QCPLayoutGrid interface.
- However, the QCPAbstractLegendItem-Interface will ignore those elements (e.g. \ref itemCount will
- only return the number of items with QCPAbstractLegendItems type).
-
- By default, every QCustomPlot has one legend (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 QCPLayout/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 \a parentPlot as the containing plot and default values.
-
- Note that by default, QCustomPlot already contains a legend ready to be used as
- QCustomPlot::legend
- */
- QCPLegend::QCPLegend()
- {
- setRowSpacing(0);
- setColumnSpacing(10);
- setMargins(QMargins(2, 3, 2, 2));
- setAntialiased(false);
- setIconSize(32, 18);
-
- setIconTextPadding(7);
-
- setSelectableParts(spLegendBox | spItems);
- setSelectedParts(spNone);
-
- setBorderPen(QPen(Qt::black));
- 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.
-
- \see itemCount
- */
- 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.
- \see item
- */
- int QCPLegend::itemCount() const
- {
- return elementCount();
- }
-
- /*!
- Returns whether the legend contains \a itm.
- */
- 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.
-
- 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.
- */
- bool QCPLegend::addItem(QCPAbstractLegendItem* item)
- {
- if (!hasItem(item)) {
- return addElement(rowCount(), 0, item);
- } else
- return false;
- }
-
- /*!
- Removes the item with index \a index from the legend.
-
- Returns true, if successful.
-
- \see itemCount, clearItems
- */
- bool QCPLegend::removeItem(int index)
- {
- if (QCPAbstractLegendItem* ali = item(index)) {
- bool success = remove(ali);
- simplify();
- return success;
- } else
- return false;
- }
-
- /*! \overload
-
- Removes \a item from the legend.
-
- Returns true, if successful.
-
- \see clearItems
- */
- bool QCPLegend::removeItem(QCPAbstractLegendItem* item)
- {
- bool success = remove(item);
- simplify();
- 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.
-
- \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)
- {
- Q_UNUSED(parentPlot)
- }
-
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPPlotTitle
- ////////////////////////////////////////////////////////////////////////////////////////////////////
-
- /*! \class QCPPlotTitle
- \brief A layout element displaying a plot title text
-
- The text may be specified with \ref setText, theformatting can be controlled with \ref setFont
- and \ref setTextColor.
-
- A plot title can be added as follows:
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcpplottitle-creation
-
- Since a plot title is a common requirement, QCustomPlot offers specialized selection signals for
- easy interaction with QCPPlotTitle. If a layout element of type QCPPlotTitle is clicked, the
- signal \ref QCustomPlot::titleClick is emitted. A double click emits the \ref
- QCustomPlot::titleDoubleClick signal.
- */
-
- /* start documentation of signals */
-
- /*! \fn void QCPPlotTitle::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
- */
-
- /* end documentation of signals */
-
- /*!
- Creates a new QCPPlotTitle instance and sets default values. The initial text is empty (\ref
- setText).
-
- To set the title text in the constructor, rather use \ref QCPPlotTitle(QCustomPlot *parentPlot,
- const QString &text).
- */
- QCPPlotTitle::QCPPlotTitle(QCustomPlot* parentPlot)
- : QCPLayoutElement(parentPlot)
- , mFont(QFont(QLatin1String("sans serif"), 13 * 1.5, QFont::Bold))
- , mTextColor(Qt::black)
- , mSelectedFont(QFont(QLatin1String("sans serif"), 13 * 1.6, QFont::Bold))
- , mSelectedTextColor(Qt::blue)
- , mSelectable(false)
- , mSelected(false)
- {
- if (parentPlot) {
- setLayer(parentPlot->currentLayer());
- mFont =
- QFont(parentPlot->font().family(), parentPlot->font().pointSize() * 1.5, QFont::Bold);
- mSelectedFont =
- QFont(parentPlot->font().family(), parentPlot->font().pointSize() * 1.6, QFont::Bold);
- }
- setMargins(QMargins(5, 5, 5, 0));
- }
-
- /*! \overload
-
- Creates a new QCPPlotTitle instance and sets default values. The initial text is set to \a text.
- */
- QCPPlotTitle::QCPPlotTitle(QCustomPlot* parentPlot, const QString& text)
- : QCPLayoutElement(parentPlot)
- , mText(text)
- , mFont(QFont(parentPlot->font().family(), parentPlot->font().pointSize() * 1.5, QFont::Bold))
- , mTextColor(Qt::black)
- , mSelectedFont(
- QFont(parentPlot->font().family(), parentPlot->font().pointSize() * 1.6, QFont::Bold))
- , mSelectedTextColor(Qt::blue)
- , mSelectable(false)
- , mSelected(false)
- {
- setLayer(QLatin1String("axes"));
- setMargins(QMargins(5, 5, 5, 0));
- }
-
- /*!
- Sets the text that will be displayed to \a text. Multiple lines can be created by insertion of
- "\n".
-
- \see setFont, setTextColor
- */
- void QCPPlotTitle::setText(const QString& text)
- {
- mText = text;
- }
-
- /*!
- Sets the \a font of the title text.
-
- \see setTextColor, setSelectedFont
- */
- void QCPPlotTitle::setFont(const QFont& font)
- {
- mFont = font;
- }
-
- /*!
- Sets the \a color of the title text.
-
- \see setFont, setSelectedTextColor
- */
- void QCPPlotTitle::setTextColor(const QColor& color)
- {
- mTextColor = color;
- }
-
- /*!
- Sets the \a font of the title text that will be used if the plot title is selected (\ref
- setSelected).
-
- \see setFont
- */
- void QCPPlotTitle::setSelectedFont(const QFont& font)
- {
- mSelectedFont = font;
- }
-
- /*!
- Sets the \a color of the title text that will be used if the plot title is selected (\ref
- setSelected).
-
- \see setTextColor
- */
- void QCPPlotTitle::setSelectedTextColor(const QColor& color)
- {
- mSelectedTextColor = color;
- }
-
- /*!
- Sets whether the user may select this plot title to \a selectable.
-
- Note that even when \a selectable is set to <tt>false</tt>, the selection state may be changed
- programmatically via \ref setSelected.
- */
- void QCPPlotTitle::setSelectable(bool selectable)
- {
- if (mSelectable != selectable) {
- mSelectable = selectable;
- emit selectableChanged(mSelectable);
- }
- }
-
- /*!
- Sets the selection state of this plot title 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 QCPPlotTitle::setSelected(bool selected)
- {
- if (mSelected != selected) {
- mSelected = selected;
- emit selectionChanged(mSelected);
- }
- }
-
- /* inherits documentation from base class */
- void QCPPlotTitle::applyDefaultAntialiasingHint(QCPPainter* painter) const
- {
- applyAntialiasingHint(painter, mAntialiased, QCP::aeNone);
- }
-
- /* inherits documentation from base class */
- void QCPPlotTitle::draw(QCPPainter* painter)
- {
- painter->setFont(mainFont());
- painter->setPen(QPen(mainTextColor()));
- painter->drawText(mRect, Qt::AlignCenter, mText, &mTextBoundingRect);
- }
-
- /* inherits documentation from base class */
- QSize QCPPlotTitle::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 QCPPlotTitle::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 QCPPlotTitle::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 QCPPlotTitle::deselectEvent(bool* selectionStateChanged)
- {
- if (mSelectable) {
- bool selBefore = mSelected;
- setSelected(false);
- if (selectionStateChanged)
- *selectionStateChanged = mSelected != selBefore;
- }
- }
-
- /* inherits documentation from base class */
- double QCPPlotTitle::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;
- }
-
- /*! \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 QCPPlotTitle::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 QCPPlotTitle::mainTextColor() const
- {
- return mSelected ? mSelectedTextColor : mTextColor;
- }
-
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// 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-autotickcount
-
- 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(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(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, log base and label.
- */
- 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);
- double logBaseTransfer = 10;
- QString labelTransfer;
- // revert some settings on old axis:
- if (mColorAxis) {
- rangeTransfer = mColorAxis.data()->range();
- labelTransfer = mColorAxis.data()->label();
- logBaseTransfer = mColorAxis.data()->scaleLogBase();
- 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:
- mColorAxis.data()->setRange(
- rangeTransfer); // transfer range of old axis to new one (necessary if axis changes from
- // vertical to horizontal or vice versa)
- mColorAxis.data()->setLabel(labelTransfer);
- mColorAxis.data()->setScaleLogBase(
- logBaseTransfer); // scaleType is synchronized among axes in realtime via signals
- // (connected in QCPColorScale ctor), so we only need to take care of
- // log base here
- 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(
- QCPAxis::orientation(mType) == Qt::Horizontal ? mColorAxis.data() : 0,
- QCPAxis::orientation(mType) == Qt::Vertical ? mColorAxis.data() : 0);
- }
- }
-
- /*!
- 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;
- int sign = 0; // TODO: should change this to QCPAbstractPlottable::SignDomain later (currently
- // is protected, maybe move to QCP namespace)
- if (mDataScaleType == QCPAxis::stLogarithmic)
- sign = (mDataRange.upper < 0 ? -1 : 1);
- 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 == 1) {
- 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 == -1) {
- 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)
- {
- if (!mAxisRect) {
- qDebug() << Q_FUNC_INFO << "internal axis rect was deleted";
- return;
- }
- mAxisRect.data()->mousePressEvent(event);
- }
-
- /* inherits documentation from base class */
- void QCPColorScale::mouseMoveEvent(QMouseEvent* event)
- {
- if (!mAxisRect) {
- qDebug() << Q_FUNC_INFO << "internal axis rect was deleted";
- return;
- }
- mAxisRect.data()->mouseMoveEvent(event);
- }
-
- /* inherits documentation from base class */
- void QCPColorScale::mouseReleaseEvent(QMouseEvent* event)
- {
- if (!mAxisRect) {
- qDebug() << Q_FUNC_INFO << "internal axis rect was deleted";
- return;
- }
- mAxisRect.data()->mouseReleaseEvent(event);
- }
-
- /* 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.
- */
- 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;
-
- 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, QImage::Format_RGB32);
- 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, QImage::Format_RGB32);
- 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);
- }
- }
- }
-
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPData
- ////////////////////////////////////////////////////////////////////////////////////////////////////
-
- /*! \class QCPData
- \brief Holds the data of one single data point for QCPGraph.
-
- The container for storing multiple data points is \ref QCPDataMap.
-
- The stored data is:
- \li \a key: coordinate on the key axis of this data point
- \li \a value: coordinate on the value axis of this data point
- \li \a keyErrorMinus: negative error in the key dimension (for error bars)
- \li \a keyErrorPlus: positive error in the key dimension (for error bars)
- \li \a valueErrorMinus: negative error in the value dimension (for error bars)
- \li \a valueErrorPlus: positive error in the value dimension (for error bars)
-
- \see QCPDataMap
- */
-
- /*!
- Constructs a data point with key, value and all errors set to zero.
- */
- QCPData::QCPData()
- : key(0), value(0), keyErrorPlus(0), keyErrorMinus(0), valueErrorPlus(0), valueErrorMinus(0)
- {}
-
- /*!
- Constructs a data point with the specified \a key and \a value. All errors are set to zero.
- */
- QCPData::QCPData(double key, double value)
- : key(key)
- , value(value)
- , keyErrorPlus(0)
- , keyErrorMinus(0)
- , valueErrorPlus(0)
- , valueErrorMinus(0)
- {}
-
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// 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 graph's data via the \ref data method, which returns a pointer to the
- internal \ref QCPDataMap.
-
- 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 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 QCPDataMap *QCPGraph::data() const
-
- Returns a pointer to the internal data storage of type \ref QCPDataMap. 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 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 constructed QCPGraph can be added to the plot with QCustomPlot::addPlottable, QCustomPlot
- then takes ownership of the graph.
-
- To directly create a graph inside a plot, you can also use the simpler QCustomPlot::addGraph
- function.
- */
- QCPGraph::QCPGraph(QCPAxis* keyAxis, QCPAxis* valueAxis) : QCPAbstractPlottable(keyAxis, valueAxis)
- {
- mData = new QCPDataMap;
-
- setPen(QPen(Qt::blue, 0));
- setErrorPen(QPen(Qt::black));
- setBrush(Qt::NoBrush);
- setSelectedPen(QPen(QColor(80, 80, 255), 2.5));
- setSelectedBrush(Qt::NoBrush);
-
- setLineStyle(lsLine);
- setErrorType(etNone);
- setErrorBarSize(6);
- setErrorBarSkipSymbol(true);
- setChannelFillGraph(0);
- setAdaptiveSampling(true);
- }
-
- QCPGraph::~QCPGraph()
- {
- delete mData;
- }
-
- /*!
- Replaces the current data with the provided \a data.
-
- If \a copy is set to true, data points in \a data will only be copied. if false, the graph
- takes ownership of the passed data and replaces the internal data pointer with it. This is
- significantly faster than copying for large datasets.
-
- Alternatively, you can also access and modify the graph's data via the \ref data method, which
- returns a pointer to the internal \ref QCPDataMap.
- */
- void QCPGraph::setData(QCPDataMap* data, bool copy)
- {
- if (mData == data) {
- qDebug() << Q_FUNC_INFO << "The data pointer is already in (and owned by) this plottable"
- << reinterpret_cast<quintptr>(data);
- return;
- }
- if (copy) {
- *mData = *data;
- } else {
- delete mData;
- mData = data;
- }
- }
-
- /*! \overload
-
- Replaces the current data with the provided points in \a key and \a value pairs. The provided
- vectors should have equal length. Else, the number of added points will be the size of the
- smallest vector.
- */
- void QCPGraph::setData(const QVector<double>& key, const QVector<double>& value)
- {
- mData->clear();
- int n = key.size();
- n = qMin(n, value.size());
- QCPData newData;
- for (int i = 0; i < n; ++i) {
- newData.key = key[i];
- newData.value = value[i];
- mData->insertMulti(newData.key, newData);
- }
- }
-
- /*!
- Replaces the current data with the provided points in \a key and \a value pairs. Additionally the
- symmetrical value error of the data points are set to the values in \a valueError.
- For error bars to show appropriately, see \ref setErrorType.
- The provided vectors should have equal length. Else, the number of added points will be the size
- of the smallest vector.
-
- For asymmetrical errors (plus different from minus), see the overloaded version of this function.
- */
- void QCPGraph::setDataValueError(const QVector<double>& key, const QVector<double>& value,
- const QVector<double>& valueError)
- {
- mData->clear();
- int n = key.size();
- n = qMin(n, value.size());
- n = qMin(n, valueError.size());
- QCPData newData;
- for (int i = 0; i < n; ++i) {
- newData.key = key[i];
- newData.value = value[i];
- newData.valueErrorMinus = valueError[i];
- newData.valueErrorPlus = valueError[i];
- mData->insertMulti(key[i], newData);
- }
- }
-
- /*!
- \overload
- Replaces the current data with the provided points in \a key and \a value pairs. Additionally the
- negative value error of the data points are set to the values in \a valueErrorMinus, the positive
- value error to \a valueErrorPlus.
- For error bars to show appropriately, see \ref setErrorType.
- The provided vectors should have equal length. Else, the number of added points will be the size
- of the smallest vector.
- */
- void QCPGraph::setDataValueError(const QVector<double>& key, const QVector<double>& value,
- const QVector<double>& valueErrorMinus,
- const QVector<double>& valueErrorPlus)
- {
- mData->clear();
- int n = key.size();
- n = qMin(n, value.size());
- n = qMin(n, valueErrorMinus.size());
- n = qMin(n, valueErrorPlus.size());
- QCPData newData;
- for (int i = 0; i < n; ++i) {
- newData.key = key[i];
- newData.value = value[i];
- newData.valueErrorMinus = valueErrorMinus[i];
- newData.valueErrorPlus = valueErrorPlus[i];
- mData->insertMulti(key[i], newData);
- }
- }
-
- /*!
- Replaces the current data with the provided points in \a key and \a value pairs. Additionally the
- symmetrical key error of the data points are set to the values in \a keyError.
- For error bars to show appropriately, see \ref setErrorType.
- The provided vectors should have equal length. Else, the number of added points will be the size
- of the smallest vector.
-
- For asymmetrical errors (plus different from minus), see the overloaded version of this function.
- */
- void QCPGraph::setDataKeyError(const QVector<double>& key, const QVector<double>& value,
- const QVector<double>& keyError)
- {
- mData->clear();
- int n = key.size();
- n = qMin(n, value.size());
- n = qMin(n, keyError.size());
- QCPData newData;
- for (int i = 0; i < n; ++i) {
- newData.key = key[i];
- newData.value = value[i];
- newData.keyErrorMinus = keyError[i];
- newData.keyErrorPlus = keyError[i];
- mData->insertMulti(key[i], newData);
- }
- }
-
- /*!
- \overload
- Replaces the current data with the provided points in \a key and \a value pairs. Additionally the
- negative key error of the data points are set to the values in \a keyErrorMinus, the positive
- key error to \a keyErrorPlus.
- For error bars to show appropriately, see \ref setErrorType.
- The provided vectors should have equal length. Else, the number of added points will be the size
- of the smallest vector.
- */
- void QCPGraph::setDataKeyError(const QVector<double>& key, const QVector<double>& value,
- const QVector<double>& keyErrorMinus,
- const QVector<double>& keyErrorPlus)
- {
- mData->clear();
- int n = key.size();
- n = qMin(n, value.size());
- n = qMin(n, keyErrorMinus.size());
- n = qMin(n, keyErrorPlus.size());
- QCPData newData;
- for (int i = 0; i < n; ++i) {
- newData.key = key[i];
- newData.value = value[i];
- newData.keyErrorMinus = keyErrorMinus[i];
- newData.keyErrorPlus = keyErrorPlus[i];
- mData->insertMulti(key[i], newData);
- }
- }
-
- /*!
- Replaces the current data with the provided points in \a key and \a value pairs. Additionally the
- symmetrical key and value errors of the data points are set to the values in \a keyError and \a
- valueError. For error bars to show appropriately, see \ref setErrorType. The provided vectors
- should have equal length. Else, the number of added points will be the size of the smallest
- vector.
-
- For asymmetrical errors (plus different from minus), see the overloaded version of this function.
- */
- void QCPGraph::setDataBothError(const QVector<double>& key, const QVector<double>& value,
- const QVector<double>& keyError, const QVector<double>& valueError)
- {
- mData->clear();
- int n = key.size();
- n = qMin(n, value.size());
- n = qMin(n, valueError.size());
- n = qMin(n, keyError.size());
- QCPData newData;
- for (int i = 0; i < n; ++i) {
- newData.key = key[i];
- newData.value = value[i];
- newData.keyErrorMinus = keyError[i];
- newData.keyErrorPlus = keyError[i];
- newData.valueErrorMinus = valueError[i];
- newData.valueErrorPlus = valueError[i];
- mData->insertMulti(key[i], newData);
- }
- }
-
- /*!
- \overload
- Replaces the current data with the provided points in \a key and \a value pairs. Additionally the
- negative key and value errors of the data points are set to the values in \a keyErrorMinus and \a
- valueErrorMinus. The positive key and value errors are set to the values in \a keyErrorPlus \a
- valueErrorPlus. For error bars to show appropriately, see \ref setErrorType. The provided vectors
- should have equal length. Else, the number of added points will be the size of the smallest
- vector.
- */
- void QCPGraph::setDataBothError(const QVector<double>& key, const QVector<double>& value,
- const QVector<double>& keyErrorMinus,
- const QVector<double>& keyErrorPlus,
- const QVector<double>& valueErrorMinus,
- const QVector<double>& valueErrorPlus)
- {
- mData->clear();
- int n = key.size();
- n = qMin(n, value.size());
- n = qMin(n, valueErrorMinus.size());
- n = qMin(n, valueErrorPlus.size());
- n = qMin(n, keyErrorMinus.size());
- n = qMin(n, keyErrorPlus.size());
- QCPData newData;
- for (int i = 0; i < n; ++i) {
- newData.key = key[i];
- newData.value = value[i];
- newData.keyErrorMinus = keyErrorMinus[i];
- newData.keyErrorPlus = keyErrorPlus[i];
- newData.valueErrorMinus = valueErrorMinus[i];
- newData.valueErrorPlus = valueErrorPlus[i];
- mData->insertMulti(key[i], newData);
- }
- }
-
- /*!
- 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;
- }
-
- /*!
- Sets which kind of error bars (Key Error, Value Error or both) should be drawn on each data
- point. If you set \a errorType to something other than \ref etNone, make sure to actually pass
- error data via the specific setData functions along with the data points (e.g. \ref
- setDataValueError, \ref setDataKeyError, \ref setDataBothError).
-
- \see ErrorType
- */
- void QCPGraph::setErrorType(ErrorType errorType)
- {
- mErrorType = errorType;
- }
-
- /*!
- Sets the pen with which the error bars will be drawn.
- \see setErrorBarSize, setErrorType
- */
- void QCPGraph::setErrorPen(const QPen& pen)
- {
- mErrorPen = pen;
- }
-
- /*!
- Sets the width of the handles at both ends of an error bar in pixels.
- */
- void QCPGraph::setErrorBarSize(double size)
- {
- mErrorBarSize = size;
- }
-
- /*!
- If \a enabled is set to true, the error bar will not be drawn as a solid line under the scatter
- symbol but leave some free space around the symbol.
-
- This feature uses the current scatter size (\ref QCPScatterStyle::setSize) to determine the size
- of the area to leave blank. So when drawing Pixmaps as scatter points (\ref
- QCPScatterStyle::ssPixmap), the scatter size must be set manually to a value corresponding to the
- size of the Pixmap, if the error bars should leave gaps to its boundaries.
-
- \ref setErrorType, setErrorBarSize, setScatterStyle
- */
- void QCPGraph::setErrorBarSkipSymbol(bool enabled)
- {
- mErrorBarSkipSymbol = enabled;
- }
-
- /*!
- 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;
- }
-
- /*!
- Adds the provided data points in \a dataMap to the current data.
-
- Alternatively, you can also access and modify the graph's data via the \ref data method, which
- returns a pointer to the internal \ref QCPDataMap.
-
- \see removeData
- */
- void QCPGraph::addData(const QCPDataMap& dataMap)
- {
- mData->unite(dataMap);
- }
-
- /*! \overload
- Adds the provided single data point in \a data to the current data.
-
- Alternatively, you can also access and modify the graph's data via the \ref data method, which
- returns a pointer to the internal \ref QCPDataMap.
-
- \see removeData
- */
- void QCPGraph::addData(const QCPData& data)
- {
- mData->insertMulti(data.key, data);
- }
-
- /*! \overload
- Adds the provided single data point as \a key and \a value pair to the current data.
-
- Alternatively, you can also access and modify the graph's data via the \ref data method, which
- returns a pointer to the internal \ref QCPDataMap.
-
- \see removeData
- */
- void QCPGraph::addData(double key, double value)
- {
- QCPData newData;
- newData.key = key;
- newData.value = value;
- mData->insertMulti(newData.key, newData);
- }
-
- /*! \overload
- Adds the provided data points as \a key and \a value pairs to the current data.
-
- Alternatively, you can also access and modify the graph's data via the \ref data method, which
- returns a pointer to the internal \ref QCPDataMap.
-
- \see removeData
- */
- void QCPGraph::addData(const QVector<double>& keys, const QVector<double>& values)
- {
- int n = qMin(keys.size(), values.size());
- QCPData newData;
- for (int i = 0; i < n; ++i) {
- newData.key = keys[i];
- newData.value = values[i];
- mData->insertMulti(newData.key, newData);
- }
- }
-
- /*!
- Removes all data points with keys smaller than \a key.
- \see addData, clearData
- */
- void QCPGraph::removeDataBefore(double key)
- {
- QCPDataMap::iterator it = mData->begin();
- while (it != mData->end() && it.key() < key)
- it = mData->erase(it);
- }
-
- /*!
- Removes all data points with keys greater than \a key.
- \see addData, clearData
- */
- void QCPGraph::removeDataAfter(double key)
- {
- if (mData->isEmpty())
- return;
- QCPDataMap::iterator it = mData->upperBound(key);
- while (it != mData->end())
- it = mData->erase(it);
- }
-
- /*!
- Removes all data points with keys between \a fromKey and \a toKey.
- if \a fromKey is greater or equal to \a toKey, the function does nothing. To remove
- a single data point with known key, use \ref removeData(double key).
-
- \see addData, clearData
- */
- void QCPGraph::removeData(double fromKey, double toKey)
- {
- if (fromKey >= toKey || mData->isEmpty())
- return;
- QCPDataMap::iterator it = mData->upperBound(fromKey);
- QCPDataMap::iterator itEnd = mData->upperBound(toKey);
- while (it != itEnd)
- it = mData->erase(it);
- }
-
- /*! \overload
-
- Removes a single data point at \a key. If the position is not known with absolute precision,
- consider using \ref removeData(double fromKey, double toKey) with a small fuzziness interval
- around the suspected position, depeding on the precision with which the key is known.
-
- \see addData, clearData
- */
- void QCPGraph::removeData(double key)
- {
- mData->remove(key);
- }
-
- /*!
- Removes all data points.
- \see removeData, removeDataAfter, removeDataBefore
- */
- void QCPGraph::clearData()
- {
- mData->clear();
- }
-
- /* inherits documentation from base class */
- double QCPGraph::selectTest(const QPointF& pos, bool onlySelectable, QVariant* details) const
- {
- Q_UNUSED(details)
- if ((onlySelectable && !mSelectable) || mData->isEmpty())
- return -1;
- if (!mKeyAxis || !mValueAxis) {
- qDebug() << Q_FUNC_INFO << "invalid key or value axis";
- return -1;
- }
-
- if (mKeyAxis.data()->axisRect()->rect().contains(pos.toPoint()))
- return pointDistance(pos);
- else
- return -1;
- }
-
- /*! \overload
-
- Allows to define whether error bars are taken into consideration when determining the new axis
- range.
-
- \see rescaleKeyAxis, rescaleValueAxis, QCPAbstractPlottable::rescaleAxes, QCustomPlot::rescaleAxes
- */
- void QCPGraph::rescaleAxes(bool onlyEnlarge, bool includeErrorBars) const
- {
- rescaleKeyAxis(onlyEnlarge, includeErrorBars);
- rescaleValueAxis(onlyEnlarge, includeErrorBars);
- }
-
- /*! \overload
-
- Allows to define whether error bars (of kind \ref QCPGraph::etKey) are taken into consideration
- when determining the new axis range.
-
- \see rescaleAxes, QCPAbstractPlottable::rescaleKeyAxis
- */
- void QCPGraph::rescaleKeyAxis(bool onlyEnlarge, bool includeErrorBars) const
- {
- // this code is a copy of QCPAbstractPlottable::rescaleKeyAxis with the only change
- // that getKeyRange is passed the includeErrorBars value.
- if (mData->isEmpty())
- return;
-
- QCPAxis* keyAxis = mKeyAxis.data();
- if (!keyAxis) {
- qDebug() << Q_FUNC_INFO << "invalid key axis";
- return;
- }
-
- SignDomain signDomain = sdBoth;
- if (keyAxis->scaleType() == QCPAxis::stLogarithmic)
- signDomain = (keyAxis->range().upper < 0 ? sdNegative : sdPositive);
-
- bool foundRange;
- QCPRange newRange = getKeyRange(foundRange, signDomain, includeErrorBars);
-
- if (foundRange) {
- if (onlyEnlarge) {
- if (keyAxis->range().lower < newRange.lower)
- newRange.lower = keyAxis->range().lower;
- if (keyAxis->range().upper > newRange.upper)
- newRange.upper = keyAxis->range().upper;
- }
- keyAxis->setRange(newRange);
- }
- }
-
- /*! \overload
-
- Allows to define whether error bars (of kind \ref QCPGraph::etValue) are taken into consideration
- when determining the new axis range.
-
- \see rescaleAxes, QCPAbstractPlottable::rescaleValueAxis
- */
- void QCPGraph::rescaleValueAxis(bool onlyEnlarge, bool includeErrorBars) const
- {
- // this code is a copy of QCPAbstractPlottable::rescaleValueAxis with the only change
- // is that getValueRange is passed the includeErrorBars value.
- if (mData->isEmpty())
- return;
-
- QCPAxis* valueAxis = mValueAxis.data();
- if (!valueAxis) {
- qDebug() << Q_FUNC_INFO << "invalid value axis";
- return;
- }
-
- SignDomain signDomain = sdBoth;
- if (valueAxis->scaleType() == QCPAxis::stLogarithmic)
- signDomain = (valueAxis->range().upper < 0 ? sdNegative : sdPositive);
-
- bool foundRange;
- QCPRange newRange = getValueRange(foundRange, signDomain, includeErrorBars);
-
- if (foundRange) {
- if (onlyEnlarge) {
- if (valueAxis->range().lower < newRange.lower)
- newRange.lower = valueAxis->range().lower;
- if (valueAxis->range().upper > newRange.upper)
- newRange.upper = valueAxis->range().upper;
- }
- valueAxis->setRange(newRange);
- }
- }
-
- /* 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 || mData->isEmpty())
- return;
- if (mLineStyle == lsNone && mScatterStyle.isNone())
- return;
-
- // allocate line and (if necessary) point vectors:
- QVector<QPointF>* lineData = new QVector<QPointF>;
- QVector<QCPData>* scatterData = 0;
- if (!mScatterStyle.isNone())
- scatterData = new QVector<QCPData>;
-
- // fill vectors with data appropriate to plot style:
- getPlotData(lineData, scatterData);
-
- // check data validity if flag set:
- #ifdef QCUSTOMPLOT_CHECK_DATA
- QCPDataMap::const_iterator it;
- for (it = mData->constBegin(); it != mData->constEnd(); ++it) {
- if (QCP::isInvalidData(it.value().key, it.value().value)
- || QCP::isInvalidData(it.value().keyErrorPlus, it.value().keyErrorMinus)
- || QCP::isInvalidData(it.value().valueErrorPlus, it.value().valueErrorPlus))
- qDebug() << Q_FUNC_INFO << "Data point at" << it.key() << "invalid."
- << "Plottable name:" << name();
- }
- #endif
-
- // draw fill of graph:
- if (mLineStyle != lsNone)
- drawFill(painter, lineData);
-
- // draw line:
- if (mLineStyle == lsImpulse)
- drawImpulsePlot(painter, lineData);
- else if (mLineStyle != lsNone)
- drawLinePlot(painter, lineData); // also step plots can be drawn as a line plot
-
- // draw scatters:
- if (scatterData)
- drawScatterPlot(painter, scatterData);
-
- // free allocated line and point vectors:
- delete lineData;
- if (scatterData)
- delete scatterData;
- }
-
- /* 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 function branches out to the line style specific "get(...)PlotData" functions, according to
- the line style of the graph.
-
- \a lineData will be filled with raw points that will be drawn with the according draw functions,
- e.g. \ref drawLinePlot and \ref drawImpulsePlot. These aren't necessarily the original data
- points, since for step plots for example, additional points are needed for drawing lines that
- make up steps. If the line style of the graph is \ref lsNone, the \a lineData vector will be left
- untouched.
-
- \a scatterData will be filled with the original data points so \ref drawScatterPlot can draw the
- scatter symbols accordingly. If no scatters need to be drawn, i.e. the scatter style's shape is
- \ref QCPScatterStyle::ssNone, pass 0 as \a scatterData, and this step will be skipped.
-
- \see getScatterPlotData, getLinePlotData, getStepLeftPlotData, getStepRightPlotData,
- getStepCenterPlotData, getImpulsePlotData
- */
- void QCPGraph::getPlotData(QVector<QPointF>* lineData, QVector<QCPData>* scatterData) const
- {
- switch (mLineStyle) {
- case lsNone:
- getScatterPlotData(scatterData);
- break;
- case lsLine:
- getLinePlotData(lineData, scatterData);
- break;
- case lsStepLeft:
- getStepLeftPlotData(lineData, scatterData);
- break;
- case lsStepRight:
- getStepRightPlotData(lineData, scatterData);
- break;
- case lsStepCenter:
- getStepCenterPlotData(lineData, scatterData);
- break;
- case lsImpulse:
- getImpulsePlotData(lineData, scatterData);
- break;
- }
- }
-
- /*! \internal
-
- If line style is \ref lsNone and the scatter style's shape is not \ref QCPScatterStyle::ssNone,
- this function serves at providing the visible data points in \a scatterData, so the \ref
- drawScatterPlot function can draw the scatter points accordingly.
-
- If line style is not \ref lsNone, this function is not called and the data for the scatter points
- are (if needed) calculated inside the corresponding other "get(...)PlotData" functions.
-
- \see drawScatterPlot
- */
- void QCPGraph::getScatterPlotData(QVector<QCPData>* scatterData) const
- {
- getPreparedData(0, scatterData);
- }
-
- /*! \internal
-
- Places the raw data points needed for a normal linearly connected graph in \a linePixelData.
-
- As for all plot data retrieval functions, \a scatterData just contains all unaltered data
- (scatter) points that are visible for drawing scatter points, if necessary. If drawing scatter
- points is disabled (i.e. the scatter style's shape is \ref QCPScatterStyle::ssNone), pass 0 as \a
- scatterData, and the function will skip filling the vector.
-
- \see drawLinePlot
- */
- void QCPGraph::getLinePlotData(QVector<QPointF>* linePixelData, QVector<QCPData>* scatterData) const
- {
- QCPAxis* keyAxis = mKeyAxis.data();
- QCPAxis* valueAxis = mValueAxis.data();
- if (!keyAxis || !valueAxis) {
- qDebug() << Q_FUNC_INFO << "invalid key or value axis";
- return;
- }
- if (!linePixelData) {
- qDebug() << Q_FUNC_INFO << "null pointer passed as linePixelData";
- return;
- }
-
- QVector<QCPData> lineData;
- getPreparedData(&lineData, scatterData);
- linePixelData->reserve(lineData.size() + 2); // added 2 to reserve memory for lower/upper fill
- // base points that might be needed for fill
- linePixelData->resize(lineData.size());
-
- // transform lineData points to pixels:
- if (keyAxis->orientation() == Qt::Vertical) {
- for (int i = 0; i < lineData.size(); ++i) {
- (*linePixelData)[i].setX(valueAxis->coordToPixel(lineData.at(i).value));
- (*linePixelData)[i].setY(keyAxis->coordToPixel(lineData.at(i).key));
- }
- } else // key axis is horizontal
- {
- for (int i = 0; i < lineData.size(); ++i) {
- (*linePixelData)[i].setX(keyAxis->coordToPixel(lineData.at(i).key));
- (*linePixelData)[i].setY(valueAxis->coordToPixel(lineData.at(i).value));
- }
- }
- }
-
- /*!
- \internal
- Places the raw data points needed for a step plot with left oriented steps in \a lineData.
-
- As for all plot data retrieval functions, \a scatterData just contains all unaltered data
- (scatter) points that are visible for drawing scatter points, if necessary. If drawing scatter
- points is disabled (i.e. the scatter style's shape is \ref QCPScatterStyle::ssNone), pass 0 as \a
- scatterData, and the function will skip filling the vector.
-
- \see drawLinePlot
- */
- void QCPGraph::getStepLeftPlotData(QVector<QPointF>* linePixelData,
- QVector<QCPData>* scatterData) const
- {
- QCPAxis* keyAxis = mKeyAxis.data();
- QCPAxis* valueAxis = mValueAxis.data();
- if (!keyAxis || !valueAxis) {
- qDebug() << Q_FUNC_INFO << "invalid key or value axis";
- return;
- }
- if (!linePixelData) {
- qDebug() << Q_FUNC_INFO << "null pointer passed as lineData";
- return;
- }
-
- QVector<QCPData> lineData;
- getPreparedData(&lineData, scatterData);
- linePixelData->reserve(lineData.size() * 2
- + 2); // added 2 to reserve memory for lower/upper fill base points that
- // might be needed for fill
- linePixelData->resize(lineData.size() * 2);
-
- // calculate steps from lineData and transform to pixel coordinates:
- if (keyAxis->orientation() == Qt::Vertical) {
- double lastValue = valueAxis->coordToPixel(lineData.first().value);
- double key;
- for (int i = 0; i < lineData.size(); ++i) {
- key = keyAxis->coordToPixel(lineData.at(i).key);
- (*linePixelData)[i * 2 + 0].setX(lastValue);
- (*linePixelData)[i * 2 + 0].setY(key);
- lastValue = valueAxis->coordToPixel(lineData.at(i).value);
- (*linePixelData)[i * 2 + 1].setX(lastValue);
- (*linePixelData)[i * 2 + 1].setY(key);
- }
- } else // key axis is horizontal
- {
- double lastValue = valueAxis->coordToPixel(lineData.first().value);
- double key;
- for (int i = 0; i < lineData.size(); ++i) {
- key = keyAxis->coordToPixel(lineData.at(i).key);
- (*linePixelData)[i * 2 + 0].setX(key);
- (*linePixelData)[i * 2 + 0].setY(lastValue);
- lastValue = valueAxis->coordToPixel(lineData.at(i).value);
- (*linePixelData)[i * 2 + 1].setX(key);
- (*linePixelData)[i * 2 + 1].setY(lastValue);
- }
- }
- }
-
- /*!
- \internal
- Places the raw data points needed for a step plot with right oriented steps in \a lineData.
-
- As for all plot data retrieval functions, \a scatterData just contains all unaltered data
- (scatter) points that are visible for drawing scatter points, if necessary. If drawing scatter
- points is disabled (i.e. the scatter style's shape is \ref QCPScatterStyle::ssNone), pass 0 as \a
- scatterData, and the function will skip filling the vector.
-
- \see drawLinePlot
- */
- void QCPGraph::getStepRightPlotData(QVector<QPointF>* linePixelData,
- QVector<QCPData>* scatterData) const
- {
- QCPAxis* keyAxis = mKeyAxis.data();
- QCPAxis* valueAxis = mValueAxis.data();
- if (!keyAxis || !valueAxis) {
- qDebug() << Q_FUNC_INFO << "invalid key or value axis";
- return;
- }
- if (!linePixelData) {
- qDebug() << Q_FUNC_INFO << "null pointer passed as lineData";
- return;
- }
-
- QVector<QCPData> lineData;
- getPreparedData(&lineData, scatterData);
- linePixelData->reserve(lineData.size() * 2
- + 2); // added 2 to reserve memory for lower/upper fill base points that
- // might be needed for fill
- linePixelData->resize(lineData.size() * 2);
-
- // calculate steps from lineData and transform to pixel coordinates:
- if (keyAxis->orientation() == Qt::Vertical) {
- double lastKey = keyAxis->coordToPixel(lineData.first().key);
- double value;
- for (int i = 0; i < lineData.size(); ++i) {
- value = valueAxis->coordToPixel(lineData.at(i).value);
- (*linePixelData)[i * 2 + 0].setX(value);
- (*linePixelData)[i * 2 + 0].setY(lastKey);
- lastKey = keyAxis->coordToPixel(lineData.at(i).key);
- (*linePixelData)[i * 2 + 1].setX(value);
- (*linePixelData)[i * 2 + 1].setY(lastKey);
- }
- } else // key axis is horizontal
- {
- double lastKey = keyAxis->coordToPixel(lineData.first().key);
- double value;
- for (int i = 0; i < lineData.size(); ++i) {
- value = valueAxis->coordToPixel(lineData.at(i).value);
- (*linePixelData)[i * 2 + 0].setX(lastKey);
- (*linePixelData)[i * 2 + 0].setY(value);
- lastKey = keyAxis->coordToPixel(lineData.at(i).key);
- (*linePixelData)[i * 2 + 1].setX(lastKey);
- (*linePixelData)[i * 2 + 1].setY(value);
- }
- }
- }
-
- /*!
- \internal
- Places the raw data points needed for a step plot with centered steps in \a lineData.
-
- As for all plot data retrieval functions, \a scatterData just contains all unaltered data
- (scatter) points that are visible for drawing scatter points, if necessary. If drawing scatter
- points is disabled (i.e. the scatter style's shape is \ref QCPScatterStyle::ssNone), pass 0 as \a
- scatterData, and the function will skip filling the vector.
-
- \see drawLinePlot
- */
- void QCPGraph::getStepCenterPlotData(QVector<QPointF>* linePixelData,
- QVector<QCPData>* scatterData) const
- {
- QCPAxis* keyAxis = mKeyAxis.data();
- QCPAxis* valueAxis = mValueAxis.data();
- if (!keyAxis || !valueAxis) {
- qDebug() << Q_FUNC_INFO << "invalid key or value axis";
- return;
- }
- if (!linePixelData) {
- qDebug() << Q_FUNC_INFO << "null pointer passed as lineData";
- return;
- }
-
- QVector<QCPData> lineData;
- getPreparedData(&lineData, scatterData);
- linePixelData->reserve(lineData.size() * 2
- + 2); // added 2 to reserve memory for lower/upper fill base points that
- // might be needed for fill
- linePixelData->resize(lineData.size() * 2);
- // calculate steps from lineData and transform to pixel coordinates:
- if (keyAxis->orientation() == Qt::Vertical) {
- double lastKey = keyAxis->coordToPixel(lineData.first().key);
- double lastValue = valueAxis->coordToPixel(lineData.first().value);
- double key;
- (*linePixelData)[0].setX(lastValue);
- (*linePixelData)[0].setY(lastKey);
- for (int i = 1; i < lineData.size(); ++i) {
- key = (keyAxis->coordToPixel(lineData.at(i).key) + lastKey) * 0.5;
- (*linePixelData)[i * 2 - 1].setX(lastValue);
- (*linePixelData)[i * 2 - 1].setY(key);
- lastValue = valueAxis->coordToPixel(lineData.at(i).value);
- lastKey = keyAxis->coordToPixel(lineData.at(i).key);
- (*linePixelData)[i * 2 + 0].setX(lastValue);
- (*linePixelData)[i * 2 + 0].setY(key);
- }
- (*linePixelData)[lineData.size() * 2 - 1].setX(lastValue);
- (*linePixelData)[lineData.size() * 2 - 1].setY(lastKey);
- } else // key axis is horizontal
- {
- double lastKey = keyAxis->coordToPixel(lineData.first().key);
- double lastValue = valueAxis->coordToPixel(lineData.first().value);
- double key;
- (*linePixelData)[0].setX(lastKey);
- (*linePixelData)[0].setY(lastValue);
- for (int i = 1; i < lineData.size(); ++i) {
- key = (keyAxis->coordToPixel(lineData.at(i).key) + lastKey) * 0.5;
- (*linePixelData)[i * 2 - 1].setX(key);
- (*linePixelData)[i * 2 - 1].setY(lastValue);
- lastValue = valueAxis->coordToPixel(lineData.at(i).value);
- lastKey = keyAxis->coordToPixel(lineData.at(i).key);
- (*linePixelData)[i * 2 + 0].setX(key);
- (*linePixelData)[i * 2 + 0].setY(lastValue);
- }
- (*linePixelData)[lineData.size() * 2 - 1].setX(lastKey);
- (*linePixelData)[lineData.size() * 2 - 1].setY(lastValue);
- }
- }
-
- /*!
- \internal
- Places the raw data points needed for an impulse plot in \a lineData.
-
- As for all plot data retrieval functions, \a scatterData just contains all unaltered data
- (scatter) points that are visible for drawing scatter points, if necessary. If drawing scatter
- points is disabled (i.e. the scatter style's shape is \ref QCPScatterStyle::ssNone), pass 0 as \a
- scatterData, and the function will skip filling the vector.
-
- \see drawImpulsePlot
- */
- void QCPGraph::getImpulsePlotData(QVector<QPointF>* linePixelData,
- QVector<QCPData>* scatterData) const
- {
- QCPAxis* keyAxis = mKeyAxis.data();
- QCPAxis* valueAxis = mValueAxis.data();
- if (!keyAxis || !valueAxis) {
- qDebug() << Q_FUNC_INFO << "invalid key or value axis";
- return;
- }
- if (!linePixelData) {
- qDebug() << Q_FUNC_INFO << "null pointer passed as linePixelData";
- return;
- }
-
- QVector<QCPData> lineData;
- getPreparedData(&lineData, scatterData);
- linePixelData->resize(
- lineData.size() * 2); // no need to reserve 2 extra points because impulse plot has no fill
-
- // transform lineData points to pixels:
- if (keyAxis->orientation() == Qt::Vertical) {
- double zeroPointX = valueAxis->coordToPixel(0);
- double key;
- for (int i = 0; i < lineData.size(); ++i) {
- key = keyAxis->coordToPixel(lineData.at(i).key);
- (*linePixelData)[i * 2 + 0].setX(zeroPointX);
- (*linePixelData)[i * 2 + 0].setY(key);
- (*linePixelData)[i * 2 + 1].setX(valueAxis->coordToPixel(lineData.at(i).value));
- (*linePixelData)[i * 2 + 1].setY(key);
- }
- } else // key axis is horizontal
- {
- double zeroPointY = valueAxis->coordToPixel(0);
- double key;
- for (int i = 0; i < lineData.size(); ++i) {
- key = keyAxis->coordToPixel(lineData.at(i).key);
- (*linePixelData)[i * 2 + 0].setX(key);
- (*linePixelData)[i * 2 + 0].setY(zeroPointY);
- (*linePixelData)[i * 2 + 1].setX(key);
- (*linePixelData)[i * 2 + 1].setY(valueAxis->coordToPixel(lineData.at(i).value));
- }
- }
- }
-
- /*! \internal
-
- Draws the fill of the graph with the specified brush.
-
- If the fill is a normal fill towards the zero-value-line, only the \a lineData is 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).
-
- If the fill is a channel fill between this QCPGraph and another QCPGraph (mChannelFillGraph), the
- more complex polygon is calculated with the \ref getChannelFillPolygon function.
-
- \see drawLinePlot
- */
- void QCPGraph::drawFill(QCPPainter* painter, QVector<QPointF>* lineData) const
- {
- if (mLineStyle == lsImpulse)
- return; // fill doesn't make sense for impulse plot
- if (mainBrush().style() == Qt::NoBrush || mainBrush().color().alpha() == 0)
- return;
-
- applyFillAntialiasingHint(painter);
- if (!mChannelFillGraph) {
- // draw base fill under graph, fill goes all the way to the zero-value-line:
- addFillBasePoints(lineData);
- painter->setPen(Qt::NoPen);
- painter->setBrush(mainBrush());
- painter->drawPolygon(QPolygonF(*lineData));
- removeFillBasePoints(lineData);
- } else {
- // draw channel fill between this graph and mChannelFillGraph:
- painter->setPen(Qt::NoPen);
- painter->setBrush(mainBrush());
- painter->drawPolygon(getChannelFillPolygon(lineData));
- }
- }
-
- /*! \internal
-
- Draws scatter symbols at every data point passed in \a scatterData. scatter symbols are
- independent of the line style and are always drawn if the scatter style's shape is not \ref
- QCPScatterStyle::ssNone. Hence, the \a scatterData vector is outputted by all "get(...)PlotData"
- functions, together with the (line style dependent) line data.
-
- \see drawLinePlot, drawImpulsePlot
- */
- void QCPGraph::drawScatterPlot(QCPPainter* painter, QVector<QCPData>* scatterData) const
- {
- QCPAxis* keyAxis = mKeyAxis.data();
- QCPAxis* valueAxis = mValueAxis.data();
- if (!keyAxis || !valueAxis) {
- qDebug() << Q_FUNC_INFO << "invalid key or value axis";
- return;
- }
-
- // draw error bars:
- if (mErrorType != etNone) {
- applyErrorBarsAntialiasingHint(painter);
- painter->setPen(mErrorPen);
- if (keyAxis->orientation() == Qt::Vertical) {
- for (int i = 0; i < scatterData->size(); ++i)
- drawError(painter, valueAxis->coordToPixel(scatterData->at(i).value),
- keyAxis->coordToPixel(scatterData->at(i).key), scatterData->at(i));
- } else {
- for (int i = 0; i < scatterData->size(); ++i)
- drawError(painter, keyAxis->coordToPixel(scatterData->at(i).key),
- valueAxis->coordToPixel(scatterData->at(i).value), scatterData->at(i));
- }
- }
-
- // draw scatter point symbols:
- applyScattersAntialiasingHint(painter);
- mScatterStyle.applyTo(painter, mPen);
- if (keyAxis->orientation() == Qt::Vertical) {
- for (int i = 0; i < scatterData->size(); ++i)
- if (!qIsNaN(scatterData->at(i).value))
- mScatterStyle.drawShape(painter, valueAxis->coordToPixel(scatterData->at(i).value),
- keyAxis->coordToPixel(scatterData->at(i).key));
- } else {
- for (int i = 0; i < scatterData->size(); ++i)
- if (!qIsNaN(scatterData->at(i).value))
- mScatterStyle.drawShape(painter, keyAxis->coordToPixel(scatterData->at(i).key),
- valueAxis->coordToPixel(scatterData->at(i).value));
- }
- }
-
- /*! \internal
-
- Draws line graphs from the provided data. It connects all points in \a lineData, which was
- created by one of the "get(...)PlotData" functions for line styles that require simple line
- connections between the point vector they create. These are for example \ref getLinePlotData,
- \ref getStepLeftPlotData, \ref getStepRightPlotData and \ref getStepCenterPlotData.
-
- \see drawScatterPlot, drawImpulsePlot
- */
- void QCPGraph::drawLinePlot(QCPPainter* painter, QVector<QPointF>* lineData) const
- {
- // draw line of graph:
- if (mainPen().style() != Qt::NoPen && mainPen().color().alpha() != 0) {
- applyDefaultAntialiasingHint(painter);
- painter->setPen(mainPen());
- painter->setBrush(Qt::NoBrush);
-
- /* Draws polyline in batches, currently not used:
- int p = 0;
- while (p < lineData->size())
- {
- int batch = qMin(25, lineData->size()-p);
- if (p != 0)
- {
- ++batch;
- --p; // to draw the connection lines between two batches
- }
- painter->drawPolyline(lineData->constData()+p, batch);
- p += batch;
- }
- */
-
- // if drawing solid line and not in PDF, use much faster line drawing instead of polyline:
- if (mParentPlot->plottingHints().testFlag(QCP::phFastPolylines)
- && painter->pen().style() == Qt::SolidLine
- && !painter->modes().testFlag(QCPPainter::pmVectorized)
- && !painter->modes().testFlag(QCPPainter::pmNoCaching)) {
- int i = 0;
- bool lastIsNan = false;
- const int lineDataSize = lineData->size();
- while (i < lineDataSize
- && (qIsNaN(lineData->at(i).y())
- || qIsNaN(lineData->at(i).x()))) // make sure first point is not NaN
- ++i;
- ++i; // because drawing works in 1 point retrospect
- while (i < lineDataSize) {
- if (!qIsNaN(lineData->at(i).y())
- && !qIsNaN(lineData->at(i).x())) // NaNs create a gap in the line
- {
- if (!lastIsNan)
- painter->drawLine(lineData->at(i - 1), lineData->at(i));
- else
- lastIsNan = false;
- } else
- lastIsNan = true;
- ++i;
- }
- } else {
- int segmentStart = 0;
- int i = 0;
- const int lineDataSize = lineData->size();
- while (i < lineDataSize) {
- if (qIsNaN(lineData->at(i).y()) || qIsNaN(lineData->at(i).x())
- || qIsInf(lineData->at(i).y())) // NaNs create a gap in the line. Also filter
- // Infs which make drawPolyline block
- {
- painter->drawPolyline(lineData->constData() + segmentStart,
- i - segmentStart); // i, because we don't want to include
- // the current NaN point
- segmentStart = i + 1;
- }
- ++i;
- }
- // draw last segment:
- painter->drawPolyline(lineData->constData() + segmentStart,
- lineDataSize - segmentStart);
- }
- }
- }
-
- /*! \internal
-
- Draws impulses from the provided data, i.e. it connects all line pairs in \a lineData, which was
- created by \ref getImpulsePlotData.
-
- \see drawScatterPlot, drawLinePlot
- */
- void QCPGraph::drawImpulsePlot(QCPPainter* painter, QVector<QPointF>* lineData) const
- {
- // draw impulses:
- if (mainPen().style() != Qt::NoPen && mainPen().color().alpha() != 0) {
- applyDefaultAntialiasingHint(painter);
- QPen pen = mainPen();
- pen.setCapStyle(Qt::FlatCap); // so impulse line doesn't reach beyond zero-line
- painter->setPen(pen);
- painter->setBrush(Qt::NoBrush);
- painter->drawLines(*lineData);
- }
- }
-
- /*! \internal
-
- Returns the \a lineData and \a scatterData that need to be plotted for this graph taking into
- consideration the current axis ranges and, if \ref setAdaptiveSampling is enabled, local point
- densities.
-
- 0 may be passed as \a lineData or \a scatterData to indicate that the respective dataset isn't
- needed. For example, if the scatter style (\ref setScatterStyle) is \ref QCPScatterStyle::ssNone,
- \a scatterData should be 0 to prevent unnecessary calculations.
-
- This method is used by the various "get(...)PlotData" methods to get the basic working set of
- data.
- */
- void QCPGraph::getPreparedData(QVector<QCPData>* lineData, QVector<QCPData>* scatterData) const
- {
- 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:
- QCPDataMap::const_iterator lower,
- upper; // note that upper is the actual upper point, and not 1 step after the upper point
- getVisibleDataBounds(lower, upper);
- if (lower == mData->constEnd() || upper == mData->constEnd())
- return;
-
- // count points in visible range, taking into account that we only need to count to the limit
- // maxCount if using adaptive sampling:
- int maxCount = std::numeric_limits<int>::max();
- if (mAdaptiveSampling) {
- int keyPixelSpan =
- qAbs(keyAxis->coordToPixel(lower.key()) - keyAxis->coordToPixel(upper.key()));
- maxCount = 2 * keyPixelSpan + 2;
- }
- int dataCount = countDataInBounds(lower, upper, maxCount);
-
- if (mAdaptiveSampling && dataCount >= maxCount) // use adaptive sampling only if there are at
- // least two points per pixel on average
- {
- if (lineData) {
- QCPDataMap::const_iterator it = lower;
- QCPDataMap::const_iterator upperEnd = upper + 1;
- double minValue = it.value().value;
- double maxValue = it.value().value;
- QCPDataMap::const_iterator currentIntervalFirstPoint = it;
- int reversedFactor =
- keyAxis->rangeReversed() != (keyAxis->orientation() == Qt::Vertical)
- ? -1
- : 1; // is used to calculate keyEpsilon pixel into the correct direction
- int reversedRound = keyAxis->rangeReversed() != (keyAxis->orientation() == Qt::Vertical)
- ? 1
- : 0; // is used to switch between floor (normal) and ceil
- // (reversed) rounding of currentIntervalStartKey
- double currentIntervalStartKey =
- keyAxis->pixelToCoord((int)(keyAxis->coordToPixel(lower.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 != upperEnd) {
- 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().value < minValue)
- minValue = it.value().value;
- else if (it.value().value > maxValue)
- maxValue = it.value().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(QCPData(currentIntervalStartKey + keyEpsilon * 0.2,
- currentIntervalFirstPoint.value().value));
- lineData->append(
- QCPData(currentIntervalStartKey + keyEpsilon * 0.25, minValue));
- lineData->append(
- QCPData(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(QCPData(currentIntervalStartKey + keyEpsilon * 0.8,
- (it - 1).value().value));
- } else
- lineData->append(QCPData(currentIntervalFirstPoint.key(),
- currentIntervalFirstPoint.value().value));
- lastIntervalEndKey = (it - 1).value().key;
- minValue = it.value().value;
- maxValue = it.value().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(QCPData(currentIntervalStartKey + keyEpsilon * 0.2,
- currentIntervalFirstPoint.value().value));
- lineData->append(QCPData(currentIntervalStartKey + keyEpsilon * 0.25, minValue));
- lineData->append(QCPData(currentIntervalStartKey + keyEpsilon * 0.75, maxValue));
- } else
- lineData->append(QCPData(currentIntervalFirstPoint.key(),
- currentIntervalFirstPoint.value().value));
- }
-
- if (scatterData) {
- double valueMaxRange = valueAxis->range().upper;
- double valueMinRange = valueAxis->range().lower;
- QCPDataMap::const_iterator it = lower;
- QCPDataMap::const_iterator upperEnd = upper + 1;
- double minValue = it.value().value;
- double maxValue = it.value().value;
- QCPDataMap::const_iterator minValueIt = it;
- QCPDataMap::const_iterator maxValueIt = it;
- QCPDataMap::const_iterator currentIntervalStart = it;
- int reversedFactor =
- keyAxis->rangeReversed()
- ? -1
- : 1; // is used to calculate keyEpsilon pixel into the correct direction
- int reversedRound = keyAxis->rangeReversed()
- ? 1
- : 0; // is used to switch between floor (normal) and ceil
- // (reversed) rounding of currentIntervalStartKey
- double currentIntervalStartKey =
- keyAxis->pixelToCoord((int)(keyAxis->coordToPixel(lower.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;
- ++it; // advance iterator to second data point because adaptive sampling works in 1
- // point retrospect
- while (it != upperEnd) {
- 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().value < minValue && it.value().value > valueMinRange
- && it.value().value < valueMaxRange) {
- minValue = it.value().value;
- minValueIt = it;
- } else if (it.value().value > maxValue && it.value().value > valueMinRange
- && it.value().value < valueMaxRange) {
- maxValue = it.value().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
- QCPDataMap::const_iterator intervalIt = currentIntervalStart;
- int c = 0;
- while (intervalIt != it) {
- if ((c % dataModulo == 0 || intervalIt == minValueIt
- || intervalIt == maxValueIt)
- && intervalIt.value().value > valueMinRange
- && intervalIt.value().value < valueMaxRange)
- scatterData->append(intervalIt.value());
- ++c;
- ++intervalIt;
- }
- } else if (currentIntervalStart.value().value > valueMinRange
- && currentIntervalStart.value().value < valueMaxRange)
- scatterData->append(currentIntervalStart.value());
- minValue = it.value().value;
- maxValue = it.value().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;
- }
- ++it;
- }
- // 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
- QCPDataMap::const_iterator intervalIt = currentIntervalStart;
- int c = 0;
- while (intervalIt != it) {
- if ((c % dataModulo == 0 || intervalIt == minValueIt
- || intervalIt == maxValueIt)
- && intervalIt.value().value > valueMinRange
- && intervalIt.value().value < valueMaxRange)
- scatterData->append(intervalIt.value());
- ++c;
- ++intervalIt;
- }
- } else if (currentIntervalStart.value().value > valueMinRange
- && currentIntervalStart.value().value < valueMaxRange)
- scatterData->append(currentIntervalStart.value());
- }
- } else // don't use adaptive sampling algorithm, transfer points one-to-one from the map into
- // the output parameters
- {
- QVector<QCPData>* dataVector = 0;
- if (lineData)
- dataVector = lineData;
- else if (scatterData)
- dataVector = scatterData;
- if (dataVector) {
- QCPDataMap::const_iterator it = lower;
- QCPDataMap::const_iterator upperEnd = upper + 1;
- dataVector->reserve(dataCount + 2); // +2 for possible fill end points
- while (it != upperEnd) {
- dataVector->append(it.value());
- ++it;
- }
- }
- if (lineData && scatterData)
- *scatterData = *dataVector;
- }
- }
-
- /*! \internal
-
- called by the scatter drawing function (\ref drawScatterPlot) to draw the error bars on one data
- point. \a x and \a y pixel positions of the data point are passed since they are already known in
- pixel coordinates in the drawing function, so we save some extra coordToPixel transforms here. \a
- data is therefore only used for the errors, not key and value.
- */
- void QCPGraph::drawError(QCPPainter* painter, double x, double y, const QCPData& data) const
- {
- if (qIsNaN(data.value))
- return;
- QCPAxis* keyAxis = mKeyAxis.data();
- QCPAxis* valueAxis = mValueAxis.data();
- if (!keyAxis || !valueAxis) {
- qDebug() << Q_FUNC_INFO << "invalid key or value axis";
- return;
- }
-
- double a, b; // positions of error bar bounds in pixels
- double barWidthHalf = mErrorBarSize * 0.5;
- double skipSymbolMargin =
- mScatterStyle.size(); // pixels left blank per side, when mErrorBarSkipSymbol is true
-
- if (keyAxis->orientation() == Qt::Vertical) {
- // draw key error vertically and value error horizontally
- if (mErrorType == etKey || mErrorType == etBoth) {
- a = keyAxis->coordToPixel(data.key - data.keyErrorMinus);
- b = keyAxis->coordToPixel(data.key + data.keyErrorPlus);
- if (keyAxis->rangeReversed())
- qSwap(a, b);
- // draw spine:
- if (mErrorBarSkipSymbol) {
- if (a - y > skipSymbolMargin) // don't draw spine if error is so small it's within
- // skipSymbolmargin
- painter->drawLine(QLineF(x, a, x, y + skipSymbolMargin));
- if (y - b > skipSymbolMargin)
- painter->drawLine(QLineF(x, y - skipSymbolMargin, x, b));
- } else
- painter->drawLine(QLineF(x, a, x, b));
- // draw handles:
- painter->drawLine(QLineF(x - barWidthHalf, a, x + barWidthHalf, a));
- painter->drawLine(QLineF(x - barWidthHalf, b, x + barWidthHalf, b));
- }
- if (mErrorType == etValue || mErrorType == etBoth) {
- a = valueAxis->coordToPixel(data.value - data.valueErrorMinus);
- b = valueAxis->coordToPixel(data.value + data.valueErrorPlus);
- if (valueAxis->rangeReversed())
- qSwap(a, b);
- // draw spine:
- if (mErrorBarSkipSymbol) {
- if (x - a > skipSymbolMargin) // don't draw spine if error is so small it's within
- // skipSymbolmargin
- painter->drawLine(QLineF(a, y, x - skipSymbolMargin, y));
- if (b - x > skipSymbolMargin)
- painter->drawLine(QLineF(x + skipSymbolMargin, y, b, y));
- } else
- painter->drawLine(QLineF(a, y, b, y));
- // draw handles:
- painter->drawLine(QLineF(a, y - barWidthHalf, a, y + barWidthHalf));
- painter->drawLine(QLineF(b, y - barWidthHalf, b, y + barWidthHalf));
- }
- } else // mKeyAxis->orientation() is Qt::Horizontal
- {
- // draw value error vertically and key error horizontally
- if (mErrorType == etKey || mErrorType == etBoth) {
- a = keyAxis->coordToPixel(data.key - data.keyErrorMinus);
- b = keyAxis->coordToPixel(data.key + data.keyErrorPlus);
- if (keyAxis->rangeReversed())
- qSwap(a, b);
- // draw spine:
- if (mErrorBarSkipSymbol) {
- if (x - a > skipSymbolMargin) // don't draw spine if error is so small it's within
- // skipSymbolmargin
- painter->drawLine(QLineF(a, y, x - skipSymbolMargin, y));
- if (b - x > skipSymbolMargin)
- painter->drawLine(QLineF(x + skipSymbolMargin, y, b, y));
- } else
- painter->drawLine(QLineF(a, y, b, y));
- // draw handles:
- painter->drawLine(QLineF(a, y - barWidthHalf, a, y + barWidthHalf));
- painter->drawLine(QLineF(b, y - barWidthHalf, b, y + barWidthHalf));
- }
- if (mErrorType == etValue || mErrorType == etBoth) {
- a = valueAxis->coordToPixel(data.value - data.valueErrorMinus);
- b = valueAxis->coordToPixel(data.value + data.valueErrorPlus);
- if (valueAxis->rangeReversed())
- qSwap(a, b);
- // draw spine:
- if (mErrorBarSkipSymbol) {
- if (a - y > skipSymbolMargin) // don't draw spine if error is so small it's within
- // skipSymbolmargin
- painter->drawLine(QLineF(x, a, x, y + skipSymbolMargin));
- if (y - b > skipSymbolMargin)
- painter->drawLine(QLineF(x, y - skipSymbolMargin, x, b));
- } else
- painter->drawLine(QLineF(x, a, x, b));
- // draw handles:
- painter->drawLine(QLineF(x - barWidthHalf, a, x + barWidthHalf, a));
- painter->drawLine(QLineF(x - barWidthHalf, b, x + barWidthHalf, b));
- }
- }
- }
-
- /*! \internal
-
- called by \ref getPreparedData to determine which data (key) range is visible at the current key
- axis range setting, so only that needs to be processed.
-
- \a lower 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 upper returns an iterator to the highest data point. Same as before, \a upper may also lie
- just outside of the visible range.
-
- if the graph contains no data, both \a lower and \a upper point to constEnd.
- */
- void QCPGraph::getVisibleDataBounds(QCPDataMap::const_iterator& lower,
- QCPDataMap::const_iterator& upper) const
- {
- if (!mKeyAxis) {
- qDebug() << Q_FUNC_INFO << "invalid key axis";
- return;
- }
- if (mData->isEmpty()) {
- lower = mData->constEnd();
- upper = mData->constEnd();
- return;
- }
-
- // get visible data range as QMap iterators
- QCPDataMap::const_iterator lbound = mData->lowerBound(mKeyAxis.data()->range().lower);
- QCPDataMap::const_iterator ubound = mData->upperBound(mKeyAxis.data()->range().upper);
- bool lowoutlier =
- lbound != mData->constBegin(); // indicates whether there exist points below axis range
- bool highoutlier =
- ubound != mData->constEnd(); // indicates whether there exist points above axis range
-
- lower = (lowoutlier ? lbound - 1 : lbound); // data point range that will be actually drawn
- upper = (highoutlier ? ubound : ubound - 1); // data point range that will be actually drawn
- }
-
- /*! \internal
-
- Counts the number of data points between \a lower and \a upper (including them), up to a maximum
- of \a maxCount.
-
- This function is used by \ref getPreparedData to determine whether adaptive sampling shall be
- used (if enabled via \ref setAdaptiveSampling) or not. This is also why counting of data points
- only needs to be done until \a maxCount is reached, which should be set to the number of data
- points at which adaptive sampling sets in.
- */
- int QCPGraph::countDataInBounds(const QCPDataMap::const_iterator& lower,
- const QCPDataMap::const_iterator& upper, int maxCount) const
- {
- if (upper == mData->constEnd() && lower == mData->constEnd())
- return 0;
- QCPDataMap::const_iterator it = lower;
- int count = 1;
- while (it != upper && count < maxCount) {
- ++it;
- ++count;
- }
- return count;
- }
-
- /*! \internal
-
- The line data vector generated by e.g. getLinePlotData contains 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 lineData by two points will not cause unnecessary memory reallocations,
- because the data vector generation functions (getLinePlotData etc.) reserve two extra points when
- they allocate memory for \a lineData.
-
- \see removeFillBasePoints, lowerFillBasePoint, upperFillBasePoint
- */
- void QCPGraph::addFillBasePoints(QVector<QPointF>* lineData) const
- {
- if (!mKeyAxis) {
- qDebug() << Q_FUNC_INFO << "invalid key axis";
- return;
- }
- if (!lineData) {
- qDebug() << Q_FUNC_INFO << "passed null as lineData";
- return;
- }
- if (lineData->isEmpty())
- return;
-
- // append points that close the polygon fill at the key axis:
- if (mKeyAxis.data()->orientation() == Qt::Vertical) {
- *lineData << upperFillBasePoint(lineData->last().y());
- *lineData << lowerFillBasePoint(lineData->first().y());
- } else {
- *lineData << upperFillBasePoint(lineData->last().x());
- *lineData << lowerFillBasePoint(lineData->first().x());
- }
- }
-
- /*! \internal
-
- removes the two points from \a lineData that were added by \ref addFillBasePoints.
-
- \see addFillBasePoints, lowerFillBasePoint, upperFillBasePoint
- */
- void QCPGraph::removeFillBasePoints(QVector<QPointF>* lineData) const
- {
- if (!lineData) {
- qDebug() << Q_FUNC_INFO << "passed null as lineData";
- return;
- }
- if (lineData->isEmpty())
- return;
-
- lineData->remove(lineData->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 (data passed via \a
- lineData) and the graph specified by mChannelFillGraph (data generated by calling its \ref
- getPlotData function). May return an empty polygon if the key ranges have no overlap or fill
- target graph and this graph don't have same orientation (i.e. both key axes horizontal or both
- key axes vertical). For increased performance (due to implicit sharing), keep the returned
- QPolygonF const.
- */
- const QPolygonF QCPGraph::getChannelFillPolygon(const QVector<QPointF>* lineData) 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 (lineData->isEmpty())
- return QPolygonF();
- QVector<QPointF> otherData;
- mChannelFillGraph.data()->getPlotData(&otherData, 0);
- if (otherData.isEmpty())
- return QPolygonF();
- QVector<QPointF> thisData;
- thisData.reserve(
- lineData->size()
- + otherData.size()); // because we will join both vectors at end of this function
- for (int i = 0; i < lineData->size();
- ++i) // don't use the vector<<(vector), it squeezes internally, which ruins the
- // performance tuning with reserve()
- thisData << lineData->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 in pixels. This is used to determine whether the graph was clicked or not, e.g. in
- \ref selectTest.
-
- 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) const
- {
- if (mData->isEmpty())
- return -1.0;
- if (mLineStyle == lsNone && mScatterStyle.isNone())
- return -1.0;
-
- // calculate minimum distances to graph representation:
- if (mLineStyle == lsNone) {
- // no line displayed, only calculate distance to scatter points:
- QVector<QCPData> scatterData;
- getScatterPlotData(&scatterData);
- if (scatterData.size() > 0) {
- double minDistSqr = std::numeric_limits<double>::max();
- for (int i = 0; i < scatterData.size(); ++i) {
- double currentDistSqr =
- QVector2D(coordsToPixels(scatterData.at(i).key, scatterData.at(i).value)
- - pixelPoint)
- .lengthSquared();
- if (currentDistSqr < minDistSqr)
- minDistSqr = currentDistSqr;
- }
- return qSqrt(minDistSqr);
- } else // no data available in view to calculate distance to
- return -1.0;
- } else {
- // line displayed, calculate distance to line segments:
- QVector<QPointF> lineData;
- getPlotData(&lineData, 0); // unlike with getScatterPlotData we get pixel coordinates here
- if (lineData.size() > 1) // at least one line segment, compare distance to line segments
- {
- double minDistSqr = std::numeric_limits<double>::max();
- if (mLineStyle == lsImpulse) {
- // 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 += 2) // iterate pairs
- {
- double currentDistSqr =
- distSqrToLine(lineData.at(i), lineData.at(i + 1), pixelPoint);
- if (currentDistSqr < minDistSqr)
- minDistSqr = currentDistSqr;
- }
- } else {
- // all other line plots (line and step) connect points directly:
- for (int i = 0; i < lineData.size() - 1; ++i) {
- double currentDistSqr =
- distSqrToLine(lineData.at(i), lineData.at(i + 1), pixelPoint);
- if (currentDistSqr < minDistSqr)
- minDistSqr = currentDistSqr;
- }
- }
- return qSqrt(minDistSqr);
- } else if (lineData.size() > 0) // only single data point, calculate distance to that point
- {
- return QVector2D(lineData.at(0) - pixelPoint).length();
- } else // no data available in view to calculate distance to
- return -1.0;
- }
- }
-
- /*! \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;
- }
-
- /* inherits documentation from base class */
- QCPRange QCPGraph::getKeyRange(bool& foundRange, SignDomain inSignDomain) const
- {
- // just call the specialized version which takes an additional argument whether error bars
- // should also be taken into consideration for range calculation. We set this to true here.
- return getKeyRange(foundRange, inSignDomain, true);
- }
-
- /* inherits documentation from base class */
- QCPRange QCPGraph::getValueRange(bool& foundRange, SignDomain inSignDomain) const
- {
- // just call the specialized version which takes an additional argument whether error bars
- // should also be taken into consideration for range calculation. We set this to true here.
- return getValueRange(foundRange, inSignDomain, true);
- }
-
- /*! \overload
-
- Allows to specify whether the error bars should be included in the range calculation.
-
- \see getKeyRange(bool &foundRange, SignDomain inSignDomain)
- */
- QCPRange QCPGraph::getKeyRange(bool& foundRange, SignDomain inSignDomain, bool includeErrors) const
- {
- QCPRange range;
- bool haveLower = false;
- bool haveUpper = false;
-
- double current, currentErrorMinus, currentErrorPlus;
-
- if (inSignDomain == sdBoth) // range may be anywhere
- {
- QCPDataMap::const_iterator it = mData->constBegin();
- while (it != mData->constEnd()) {
- if (!qIsNaN(it.value().value)) {
- current = it.value().key;
- currentErrorMinus = (includeErrors ? it.value().keyErrorMinus : 0);
- currentErrorPlus = (includeErrors ? it.value().keyErrorPlus : 0);
- if (current - currentErrorMinus < range.lower || !haveLower) {
- range.lower = current - currentErrorMinus;
- haveLower = true;
- }
- if (current + currentErrorPlus > range.upper || !haveUpper) {
- range.upper = current + currentErrorPlus;
- haveUpper = true;
- }
- }
- ++it;
- }
- } else if (inSignDomain == sdNegative) // range may only be in the negative sign domain
- {
- QCPDataMap::const_iterator it = mData->constBegin();
- while (it != mData->constEnd()) {
- if (!qIsNaN(it.value().value)) {
- current = it.value().key;
- currentErrorMinus = (includeErrors ? it.value().keyErrorMinus : 0);
- currentErrorPlus = (includeErrors ? it.value().keyErrorPlus : 0);
- if ((current - currentErrorMinus < range.lower || !haveLower)
- && current - currentErrorMinus < 0) {
- range.lower = current - currentErrorMinus;
- haveLower = true;
- }
- if ((current + currentErrorPlus > range.upper || !haveUpper)
- && current + currentErrorPlus < 0) {
- range.upper = current + currentErrorPlus;
- haveUpper = true;
- }
- if (includeErrors) // in case point is in valid sign domain but errobars stretch
- // beyond it, we still want to geht that point.
- {
- if ((current < range.lower || !haveLower) && current < 0) {
- range.lower = current;
- haveLower = true;
- }
- if ((current > range.upper || !haveUpper) && current < 0) {
- range.upper = current;
- haveUpper = true;
- }
- }
- }
- ++it;
- }
- } else if (inSignDomain == sdPositive) // range may only be in the positive sign domain
- {
- QCPDataMap::const_iterator it = mData->constBegin();
- while (it != mData->constEnd()) {
- if (!qIsNaN(it.value().value)) {
- current = it.value().key;
- currentErrorMinus = (includeErrors ? it.value().keyErrorMinus : 0);
- currentErrorPlus = (includeErrors ? it.value().keyErrorPlus : 0);
- if ((current - currentErrorMinus < range.lower || !haveLower)
- && current - currentErrorMinus > 0) {
- range.lower = current - currentErrorMinus;
- haveLower = true;
- }
- if ((current + currentErrorPlus > range.upper || !haveUpper)
- && current + currentErrorPlus > 0) {
- range.upper = current + currentErrorPlus;
- haveUpper = true;
- }
- if (includeErrors) // in case point is in valid sign domain but errobars stretch
- // beyond it, we still want to get that point.
- {
- if ((current < range.lower || !haveLower) && current > 0) {
- range.lower = current;
- haveLower = true;
- }
- if ((current > range.upper || !haveUpper) && current > 0) {
- range.upper = current;
- haveUpper = true;
- }
- }
- }
- ++it;
- }
- }
-
- foundRange = haveLower && haveUpper;
- return range;
- }
-
- /*! \overload
-
- Allows to specify whether the error bars should be included in the range calculation.
-
- \see getValueRange(bool &foundRange, SignDomain inSignDomain)
- */
- QCPRange QCPGraph::getValueRange(bool& foundRange, SignDomain inSignDomain,
- bool includeErrors) const
- {
- QCPRange range;
- bool haveLower = false;
- bool haveUpper = false;
-
- double current, currentErrorMinus, currentErrorPlus;
-
- if (inSignDomain == sdBoth) // range may be anywhere
- {
- QCPDataMap::const_iterator it = mData->constBegin();
- while (it != mData->constEnd()) {
- current = it.value().value;
- if (!qIsNaN(current)) {
- currentErrorMinus = (includeErrors ? it.value().valueErrorMinus : 0);
- currentErrorPlus = (includeErrors ? it.value().valueErrorPlus : 0);
- if (current - currentErrorMinus < range.lower || !haveLower) {
- range.lower = current - currentErrorMinus;
- haveLower = true;
- }
- if (current + currentErrorPlus > range.upper || !haveUpper) {
- range.upper = current + currentErrorPlus;
- haveUpper = true;
- }
- }
- ++it;
- }
- } else if (inSignDomain == sdNegative) // range may only be in the negative sign domain
- {
- QCPDataMap::const_iterator it = mData->constBegin();
- while (it != mData->constEnd()) {
- current = it.value().value;
- if (!qIsNaN(current)) {
- currentErrorMinus = (includeErrors ? it.value().valueErrorMinus : 0);
- currentErrorPlus = (includeErrors ? it.value().valueErrorPlus : 0);
- if ((current - currentErrorMinus < range.lower || !haveLower)
- && current - currentErrorMinus < 0) {
- range.lower = current - currentErrorMinus;
- haveLower = true;
- }
- if ((current + currentErrorPlus > range.upper || !haveUpper)
- && current + currentErrorPlus < 0) {
- range.upper = current + currentErrorPlus;
- haveUpper = true;
- }
- if (includeErrors) // in case point is in valid sign domain but errobars stretch
- // beyond it, we still want to get that point.
- {
- if ((current < range.lower || !haveLower) && current < 0) {
- range.lower = current;
- haveLower = true;
- }
- if ((current > range.upper || !haveUpper) && current < 0) {
- range.upper = current;
- haveUpper = true;
- }
- }
- }
- ++it;
- }
- } else if (inSignDomain == sdPositive) // range may only be in the positive sign domain
- {
- QCPDataMap::const_iterator it = mData->constBegin();
- while (it != mData->constEnd()) {
- current = it.value().value;
- if (!qIsNaN(current)) {
- currentErrorMinus = (includeErrors ? it.value().valueErrorMinus : 0);
- currentErrorPlus = (includeErrors ? it.value().valueErrorPlus : 0);
- if ((current - currentErrorMinus < range.lower || !haveLower)
- && current - currentErrorMinus > 0) {
- range.lower = current - currentErrorMinus;
- haveLower = true;
- }
- if ((current + currentErrorPlus > range.upper || !haveUpper)
- && current + currentErrorPlus > 0) {
- range.upper = current + currentErrorPlus;
- haveUpper = true;
- }
- if (includeErrors) // in case point is in valid sign domain but errobars stretch
- // beyond it, we still want to geht that point.
- {
- if ((current < range.lower || !haveLower) && current > 0) {
- range.lower = current;
- haveLower = true;
- }
- if ((current > range.upper || !haveUpper) && current > 0) {
- range.upper = current;
- haveUpper = true;
- }
- }
- }
- ++it;
- }
- }
-
- foundRange = haveLower && haveUpper;
- return range;
- }
-
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPCurveData
- ////////////////////////////////////////////////////////////////////////////////////////////////////
-
- /*! \class QCPCurveData
- \brief Holds the data of one single data point for QCPCurve.
-
- The container for storing multiple data points is \ref QCPCurveDataMap.
-
- The stored data is:
- \li \a t: the free parameter of the curve at this curve point (cp. the mathematical vector
- <em>(x(t), y(t))</em>) \li \a key: coordinate on the key axis of this curve point \li \a value:
- coordinate on the value axis of this curve point
-
- \see QCPCurveDataMap
- */
-
- /*!
- 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.
-
- 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 appearance Changing the appearance
-
- The appearance of the curve is determined by the pen and the brush (\ref setPen, \ref setBrush).
- \section 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::addPlottable, QCustomPlot::removePlottable, etc.)
-
- Usually, you first create an instance and add it to the customPlot:
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcurve-creation-1
- and then modify the properties of the newly created plottable, e.g.:
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcurve-creation-2
- */
-
- /*!
- 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 constructed QCPCurve can be added to the plot with QCustomPlot::addPlottable, QCustomPlot
- then takes ownership of the graph.
- */
- QCPCurve::QCPCurve(QCPAxis* keyAxis, QCPAxis* valueAxis) : QCPAbstractPlottable(keyAxis, valueAxis)
- {
- mData = new QCPCurveDataMap;
- mPen.setColor(Qt::blue);
- mPen.setStyle(Qt::SolidLine);
- mBrush.setColor(Qt::blue);
- mBrush.setStyle(Qt::NoBrush);
- mSelectedPen = mPen;
- mSelectedPen.setWidthF(2.5);
- mSelectedPen.setColor(QColor(80, 80, 255)); // lighter than Qt::blue of mPen
- mSelectedBrush = mBrush;
-
- setScatterStyle(QCPScatterStyle());
- setLineStyle(lsLine);
- }
-
- QCPCurve::~QCPCurve()
- {
- delete mData;
- }
-
- /*!
- Replaces the current data with the provided \a data.
-
- If \a copy is set to true, data points in \a data will only be copied. if false, the plottable
- takes ownership of the passed data and replaces the internal data pointer with it. This is
- significantly faster than copying for large datasets.
- */
- void QCPCurve::setData(QCPCurveDataMap* data, bool copy)
- {
- if (mData == data) {
- qDebug() << Q_FUNC_INFO << "The data pointer is already in (and owned by) this plottable"
- << reinterpret_cast<quintptr>(data);
- return;
- }
- if (copy) {
- *mData = *data;
- } else {
- delete mData;
- mData = data;
- }
- }
-
- /*! \overload
-
- Replaces the current data with the provided points in \a t, \a key and \a value tuples. The
- provided vectors should have equal length. Else, the number of added points will be the size of
- the smallest vector.
- */
- void QCPCurve::setData(const QVector<double>& t, const QVector<double>& key,
- const QVector<double>& value)
- {
- mData->clear();
- int n = t.size();
- n = qMin(n, key.size());
- n = qMin(n, value.size());
- QCPCurveData newData;
- for (int i = 0; i < n; ++i) {
- newData.t = t[i];
- newData.key = key[i];
- newData.value = value[i];
- mData->insertMulti(newData.t, newData);
- }
- }
-
- /*! \overload
-
- Replaces the current data with the provided \a key and \a value pairs. The t parameter
- of each data point will be set to the integer index of the respective key/value pair.
- */
- void QCPCurve::setData(const QVector<double>& key, const QVector<double>& value)
- {
- mData->clear();
- int n = key.size();
- n = qMin(n, value.size());
- QCPCurveData newData;
- for (int i = 0; i < n; ++i) {
- newData.t = i; // no t vector given, so we assign t the index of the key/value pair
- newData.key = key[i];
- newData.value = value[i];
- mData->insertMulti(newData.t, newData);
- }
- }
-
- /*!
- 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;
- }
-
- /*!
- 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;
- }
-
- /*!
- Adds the provided data points in \a dataMap to the current data.
- \see removeData
- */
- void QCPCurve::addData(const QCPCurveDataMap& dataMap)
- {
- mData->unite(dataMap);
- }
-
- /*! \overload
- Adds the provided single data point in \a data to the current data.
- \see removeData
- */
- void QCPCurve::addData(const QCPCurveData& data)
- {
- mData->insertMulti(data.t, data);
- }
-
- /*! \overload
- Adds the provided single data point as \a t, \a key and \a value tuple to the current data
- \see removeData
- */
- void QCPCurve::addData(double t, double key, double value)
- {
- QCPCurveData newData;
- newData.t = t;
- newData.key = key;
- newData.value = value;
- mData->insertMulti(newData.t, newData);
- }
-
- /*! \overload
-
- Adds the provided single data point as \a key and \a value pair to the current data The t
- parameter of the data point is set to the t of the last data point plus 1. If there is no last
- data point, t will be set to 0.
-
- \see removeData
- */
- void QCPCurve::addData(double key, double value)
- {
- QCPCurveData newData;
- if (!mData->isEmpty())
- newData.t = (mData->constEnd() - 1).key() + 1;
- else
- newData.t = 0;
- newData.key = key;
- newData.value = value;
- mData->insertMulti(newData.t, newData);
- }
-
- /*! \overload
- Adds the provided data points as \a t, \a key and \a value tuples to the current data.
- \see removeData
- */
- void QCPCurve::addData(const QVector<double>& ts, const QVector<double>& keys,
- const QVector<double>& values)
- {
- int n = ts.size();
- n = qMin(n, keys.size());
- n = qMin(n, values.size());
- QCPCurveData newData;
- for (int i = 0; i < n; ++i) {
- newData.t = ts[i];
- newData.key = keys[i];
- newData.value = values[i];
- mData->insertMulti(newData.t, newData);
- }
- }
-
- /*!
- Removes all data points with curve parameter t smaller than \a t.
- \see addData, clearData
- */
- void QCPCurve::removeDataBefore(double t)
- {
- QCPCurveDataMap::iterator it = mData->begin();
- while (it != mData->end() && it.key() < t)
- it = mData->erase(it);
- }
-
- /*!
- Removes all data points with curve parameter t greater than \a t.
- \see addData, clearData
- */
- void QCPCurve::removeDataAfter(double t)
- {
- if (mData->isEmpty())
- return;
- QCPCurveDataMap::iterator it = mData->upperBound(t);
- while (it != mData->end())
- it = mData->erase(it);
- }
-
- /*!
- Removes all data points with curve parameter t between \a fromt and \a tot. if \a fromt is
- greater or equal to \a tot, the function does nothing. To remove a single data point with known
- t, use \ref removeData(double t).
-
- \see addData, clearData
- */
- void QCPCurve::removeData(double fromt, double tot)
- {
- if (fromt >= tot || mData->isEmpty())
- return;
- QCPCurveDataMap::iterator it = mData->upperBound(fromt);
- QCPCurveDataMap::iterator itEnd = mData->upperBound(tot);
- while (it != itEnd)
- it = mData->erase(it);
- }
-
- /*! \overload
-
- Removes a single data point at curve parameter \a t. If the position is not known with absolute
- precision, consider using \ref removeData(double fromt, double tot) with a small fuzziness
- interval around the suspected position, depeding on the precision with which the curve parameter
- is known.
-
- \see addData, clearData
- */
- void QCPCurve::removeData(double t)
- {
- mData->remove(t);
- }
-
- /*!
- Removes all data points.
- \see removeData, removeDataAfter, removeDataBefore
- */
- void QCPCurve::clearData()
- {
- mData->clear();
- }
-
- /* inherits documentation from base class */
- double QCPCurve::selectTest(const QPointF& pos, bool onlySelectable, QVariant* details) const
- {
- Q_UNUSED(details)
- if ((onlySelectable && !mSelectable) || mData->isEmpty())
- return -1;
- if (!mKeyAxis || !mValueAxis) {
- qDebug() << Q_FUNC_INFO << "invalid key or value axis";
- return -1;
- }
-
- if (mKeyAxis.data()->axisRect()->rect().contains(pos.toPoint()))
- return pointDistance(pos);
- else
- return -1;
- }
-
- /* inherits documentation from base class */
- void QCPCurve::draw(QCPPainter* painter)
- {
- if (mData->isEmpty())
- return;
-
- // allocate line vector:
- QVector<QPointF>* lineData = new QVector<QPointF>;
-
- // fill with curve data:
- getCurveData(lineData);
-
- // check data validity if flag set:
- #ifdef QCUSTOMPLOT_CHECK_DATA
- QCPCurveDataMap::const_iterator it;
- for (it = mData->constBegin(); it != mData->constEnd(); ++it) {
- if (QCP::isInvalidData(it.value().t)
- || QCP::isInvalidData(it.value().key, it.value().value))
- qDebug() << Q_FUNC_INFO << "Data point at" << it.key() << "invalid."
- << "Plottable name:" << name();
- }
- #endif
-
- // draw curve fill:
- if (mainBrush().style() != Qt::NoBrush && mainBrush().color().alpha() != 0) {
- applyFillAntialiasingHint(painter);
- painter->setPen(Qt::NoPen);
- painter->setBrush(mainBrush());
- painter->drawPolygon(QPolygonF(*lineData));
- }
-
- // draw curve line:
- if (mLineStyle != lsNone && mainPen().style() != Qt::NoPen && mainPen().color().alpha() != 0) {
- applyDefaultAntialiasingHint(painter);
- painter->setPen(mainPen());
- painter->setBrush(Qt::NoBrush);
- // if drawing solid line and not in PDF, use much faster line drawing instead of polyline:
- if (mParentPlot->plottingHints().testFlag(QCP::phFastPolylines)
- && painter->pen().style() == Qt::SolidLine
- && !painter->modes().testFlag(QCPPainter::pmVectorized)
- && !painter->modes().testFlag(QCPPainter::pmNoCaching)) {
- int i = 0;
- bool lastIsNan = false;
- const int lineDataSize = lineData->size();
- while (i < lineDataSize
- && (qIsNaN(lineData->at(i).y())
- || qIsNaN(lineData->at(i).x()))) // make sure first point is not NaN
- ++i;
- ++i; // because drawing works in 1 point retrospect
- while (i < lineDataSize) {
- if (!qIsNaN(lineData->at(i).y())
- && !qIsNaN(lineData->at(i).x())) // NaNs create a gap in the line
- {
- if (!lastIsNan)
- painter->drawLine(lineData->at(i - 1), lineData->at(i));
- else
- lastIsNan = false;
- } else
- lastIsNan = true;
- ++i;
- }
- } else {
- int segmentStart = 0;
- int i = 0;
- const int lineDataSize = lineData->size();
- while (i < lineDataSize) {
- if (qIsNaN(lineData->at(i).y())
- || qIsNaN(lineData->at(i).x())) // NaNs create a gap in the line
- {
- painter->drawPolyline(lineData->constData() + segmentStart,
- i - segmentStart); // i, because we don't want to include
- // the current NaN point
- segmentStart = i + 1;
- }
- ++i;
- }
- // draw last segment:
- painter->drawPolyline(lineData->constData() + segmentStart,
- lineDataSize - segmentStart);
- }
- }
-
- // draw scatters:
- if (!mScatterStyle.isNone())
- drawScatterPlot(painter, lineData);
-
- // free allocated line data:
- delete lineData;
- }
-
- /* 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 scatter symbols at every data point passed in \a pointData. scatter symbols are independent
- of the line style and are always drawn if scatter shape is not \ref QCPScatterStyle::ssNone.
- */
- void QCPCurve::drawScatterPlot(QCPPainter* painter, const QVector<QPointF>* pointData) const
- {
- // draw scatter point symbols:
- applyScattersAntialiasingHint(painter);
- mScatterStyle.applyTo(painter, mPen);
- for (int i = 0; i < pointData->size(); ++i)
- if (!qIsNaN(pointData->at(i).x()) && !qIsNaN(pointData->at(i).y()))
- mScatterStyle.drawShape(painter, pointData->at(i));
- }
-
- /*! \internal
-
- called by QCPCurve::draw to generate a point vector (in pixel coordinates) which represents 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.
-
- Methods that are also involved in the algorithm are: \ref getRegion, \ref getOptimizedPoint, \ref
- getOptimizedCornerPoints \ref mayTraverse, \ref getTraverse, \ref getTraverseCornerPoints.
- */
- void QCPCurve::getCurveData(QVector<QPointF>* lineData) const
- {
- 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
- double strokeMargin =
- qMax(qreal(1.0), qreal(mainPen().widthF() * 0.75)); // stroke radius + 50% safety
- if (!mScatterStyle.isNone())
- strokeMargin = qMax(strokeMargin, mScatterStyle.size());
- double rectLeft = keyAxis->pixelToCoord(
- keyAxis->coordToPixel(keyAxis->range().lower)
- - strokeMargin
- * ((keyAxis->orientation() == Qt::Vertical) != keyAxis->rangeReversed() ? -1 : 1));
- double rectRight = keyAxis->pixelToCoord(
- keyAxis->coordToPixel(keyAxis->range().upper)
- + strokeMargin
- * ((keyAxis->orientation() == Qt::Vertical) != keyAxis->rangeReversed() ? -1 : 1));
- double rectBottom = valueAxis->pixelToCoord(
- valueAxis->coordToPixel(valueAxis->range().lower)
- + strokeMargin
- * ((valueAxis->orientation() == Qt::Horizontal) != valueAxis->rangeReversed() ? -1
- : 1));
- double rectTop = valueAxis->pixelToCoord(
- valueAxis->coordToPixel(valueAxis->range().upper)
- - strokeMargin
- * ((valueAxis->orientation() == Qt::Horizontal) != valueAxis->rangeReversed() ? -1
- : 1));
- int currentRegion;
- QCPCurveDataMap::const_iterator it = mData->constBegin();
- QCPCurveDataMap::const_iterator prevIt = mData->constEnd() - 1;
- int prevRegion = getRegion(prevIt.value().key, prevIt.value().value, rectLeft, rectTop,
- rectRight, rectBottom);
- 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 != mData->constEnd()) {
- currentRegion =
- getRegion(it.value().key, it.value().value, rectLeft, rectTop, rectRight, rectBottom);
- 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
- {
- lineData->append(getOptimizedPoint(
- currentRegion, it.value().key, it.value().value, prevIt.value().key,
- prevIt.value().value, rectLeft, rectTop, rectRight, rectBottom));
- // 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
- *lineData << getOptimizedCornerPoints(
- prevRegion, currentRegion, prevIt.value().key, prevIt.value().value,
- it.value().key, it.value().value, rectLeft, rectTop, rectRight, rectBottom);
- } else if (mayTraverse(prevRegion, currentRegion)
- && getTraverse(prevIt.value().key, prevIt.value().value, it.value().key,
- it.value().value, rectLeft, rectTop, rectRight,
- rectBottom, 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, rectLeft, rectTop, rectRight,
- rectBottom, beforeTraverseCornerPoints,
- afterTraverseCornerPoints);
- if (it != mData->constBegin()) {
- *lineData << beforeTraverseCornerPoints;
- lineData->append(crossA);
- lineData->append(crossB);
- *lineData << afterTraverseCornerPoints;
- } else {
- lineData->append(crossB);
- *lineData << 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)
- {
- *lineData << getOptimizedCornerPoints(
- prevRegion, currentRegion, prevIt.value().key, prevIt.value().value,
- it.value().key, it.value().value, rectLeft, rectTop, rectRight, rectBottom);
- }
- } else // segment does end in R, so we add previous point optimized and this point at
- // original position
- {
- if (it == mData->constBegin()) // 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.value().key, prevIt.value().value, it.value().key,
- it.value().value, rectLeft, rectTop, rectRight, rectBottom);
- else
- lineData->append(getOptimizedPoint(
- prevRegion, prevIt.value().key, prevIt.value().value, it.value().key,
- it.value().value, rectLeft, rectTop, rectRight, rectBottom));
- lineData->append(coordsToPixels(it.value().key, it.value().value));
- }
- } else // region didn't change
- {
- if (currentRegion == 5) // still in R, keep adding original points
- {
- lineData->append(coordsToPixels(it.value().key, it.value().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;
- }
- *lineData << trailingPoints;
- }
-
- /*! \internal
-
- This function is part of the curve optimization algorithm of \ref getCurveData.
-
- It returns the region of the given point (\a x, \a y) with respect to a rectangle defined by \a
- rectLeft, \a rectTop, \a rectRight, and \a rectBottom.
-
- The regions are enumerated from top to bottom and left to right:
-
- <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 x, double y, double rectLeft, double rectTop, double rectRight,
- double rectBottom) const
- {
- if (x < rectLeft) // region 123
- {
- if (y > rectTop)
- return 1;
- else if (y < rectBottom)
- return 3;
- else
- return 2;
- } else if (x > rectRight) // region 789
- {
- if (y > rectTop)
- return 7;
- else if (y < rectBottom)
- return 9;
- else
- return 8;
- } else // region 456
- {
- if (y > rectTop)
- return 4;
- else if (y < rectBottom)
- return 6;
- else
- return 5;
- }
- }
-
- /*! \internal
-
- This function is part of the curve optimization algorithm of \ref getCurveData.
-
- 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 rectLeft, double rectTop, double rectRight,
- double rectBottom) const
- {
- double intersectKey = rectLeft; // initial value is just fail-safe
- double intersectValue = rectTop; // initial value is just fail-safe
- switch (otherRegion) {
- case 1: // top and left edge
- {
- intersectValue = rectTop;
- intersectKey =
- otherKey + (key - otherKey) / (value - otherValue) * (intersectValue - otherValue);
- if (intersectKey < rectLeft
- || intersectKey > rectRight) // doesn't intersect, so must intersect other:
- {
- intersectKey = rectLeft;
- intersectValue =
- otherValue + (value - otherValue) / (key - otherKey) * (intersectKey - otherKey);
- }
- break;
- }
- case 2: // left edge
- {
- intersectKey = rectLeft;
- intersectValue =
- otherValue + (value - otherValue) / (key - otherKey) * (intersectKey - otherKey);
- break;
- }
- case 3: // bottom and left edge
- {
- intersectValue = rectBottom;
- intersectKey =
- otherKey + (key - otherKey) / (value - otherValue) * (intersectValue - otherValue);
- if (intersectKey < rectLeft
- || intersectKey > rectRight) // doesn't intersect, so must intersect other:
- {
- intersectKey = rectLeft;
- intersectValue =
- otherValue + (value - otherValue) / (key - otherKey) * (intersectKey - otherKey);
- }
- break;
- }
- case 4: // top edge
- {
- intersectValue = rectTop;
- 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 = rectBottom;
- intersectKey =
- otherKey + (key - otherKey) / (value - otherValue) * (intersectValue - otherValue);
- break;
- }
- case 7: // top and right edge
- {
- intersectValue = rectTop;
- intersectKey =
- otherKey + (key - otherKey) / (value - otherValue) * (intersectValue - otherValue);
- if (intersectKey < rectLeft
- || intersectKey > rectRight) // doesn't intersect, so must intersect other:
- {
- intersectKey = rectRight;
- intersectValue =
- otherValue + (value - otherValue) / (key - otherKey) * (intersectKey - otherKey);
- }
- break;
- }
- case 8: // right edge
- {
- intersectKey = rectRight;
- intersectValue =
- otherValue + (value - otherValue) / (key - otherKey) * (intersectKey - otherKey);
- break;
- }
- case 9: // bottom and right edge
- {
- intersectValue = rectBottom;
- intersectKey =
- otherKey + (key - otherKey) / (value - otherValue) * (intersectValue - otherValue);
- if (intersectKey < rectLeft
- || intersectKey > rectRight) // doesn't intersect, so must intersect other:
- {
- intersectKey = rectRight;
- 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 getCurveData.
-
- 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 rectLeft, double rectTop,
- double rectRight, double rectBottom) const
- {
- QVector<QPointF> result;
- switch (prevRegion) {
- case 1: {
- switch (currentRegion) {
- case 2: {
- result << coordsToPixels(rectLeft, rectTop);
- break;
- }
- case 4: {
- result << coordsToPixels(rectLeft, rectTop);
- break;
- }
- case 3: {
- result << coordsToPixels(rectLeft, rectTop) << coordsToPixels(rectLeft, rectBottom);
- break;
- }
- case 7: {
- result << coordsToPixels(rectLeft, rectTop) << coordsToPixels(rectRight, rectTop);
- break;
- }
- case 6: {
- result << coordsToPixels(rectLeft, rectTop) << coordsToPixels(rectLeft, rectBottom);
- result.append(result.last());
- break;
- }
- case 8: {
- result << coordsToPixels(rectLeft, rectTop) << coordsToPixels(rectRight, rectTop);
- 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) * (rectLeft - key) + value
- < rectBottom) // segment passes below R
- {
- result << coordsToPixels(rectLeft, rectTop) << coordsToPixels(rectLeft, rectBottom);
- result.append(result.last());
- result << coordsToPixels(rectRight, rectBottom);
- } else {
- result << coordsToPixels(rectLeft, rectTop) << coordsToPixels(rectRight, rectTop);
- result.append(result.last());
- result << coordsToPixels(rectRight, rectBottom);
- }
- break;
- }
- }
- break;
- }
- case 2: {
- switch (currentRegion) {
- case 1: {
- result << coordsToPixels(rectLeft, rectTop);
- break;
- }
- case 3: {
- result << coordsToPixels(rectLeft, rectBottom);
- break;
- }
- case 4: {
- result << coordsToPixels(rectLeft, rectTop);
- result.append(result.last());
- break;
- }
- case 6: {
- result << coordsToPixels(rectLeft, rectBottom);
- result.append(result.last());
- break;
- }
- case 7: {
- result << coordsToPixels(rectLeft, rectTop);
- result.append(result.last());
- result << coordsToPixels(rectRight, rectTop);
- break;
- }
- case 9: {
- result << coordsToPixels(rectLeft, rectBottom);
- result.append(result.last());
- result << coordsToPixels(rectRight, rectBottom);
- break;
- }
- }
- break;
- }
- case 3: {
- switch (currentRegion) {
- case 2: {
- result << coordsToPixels(rectLeft, rectBottom);
- break;
- }
- case 6: {
- result << coordsToPixels(rectLeft, rectBottom);
- break;
- }
- case 1: {
- result << coordsToPixels(rectLeft, rectBottom) << coordsToPixels(rectLeft, rectTop);
- break;
- }
- case 9: {
- result << coordsToPixels(rectLeft, rectBottom) << coordsToPixels(rectRight, rectBottom);
- break;
- }
- case 4: {
- result << coordsToPixels(rectLeft, rectBottom) << coordsToPixels(rectLeft, rectTop);
- result.append(result.last());
- break;
- }
- case 8: {
- result << coordsToPixels(rectLeft, rectBottom) << coordsToPixels(rectRight, rectBottom);
- 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) * (rectRight - key) + value
- < rectBottom) // segment passes below R
- {
- result << coordsToPixels(rectLeft, rectBottom)
- << coordsToPixels(rectRight, rectBottom);
- result.append(result.last());
- result << coordsToPixels(rectRight, rectTop);
- } else {
- result << coordsToPixels(rectLeft, rectBottom) << coordsToPixels(rectLeft, rectTop);
- result.append(result.last());
- result << coordsToPixels(rectRight, rectTop);
- }
- break;
- }
- }
- break;
- }
- case 4: {
- switch (currentRegion) {
- case 1: {
- result << coordsToPixels(rectLeft, rectTop);
- break;
- }
- case 7: {
- result << coordsToPixels(rectRight, rectTop);
- break;
- }
- case 2: {
- result << coordsToPixels(rectLeft, rectTop);
- result.append(result.last());
- break;
- }
- case 8: {
- result << coordsToPixels(rectRight, rectTop);
- result.append(result.last());
- break;
- }
- case 3: {
- result << coordsToPixels(rectLeft, rectTop);
- result.append(result.last());
- result << coordsToPixels(rectLeft, rectBottom);
- break;
- }
- case 9: {
- result << coordsToPixels(rectRight, rectTop);
- result.append(result.last());
- result << coordsToPixels(rectRight, rectBottom);
- break;
- }
- }
- break;
- }
- case 5: {
- switch (currentRegion) {
- case 1: {
- result << coordsToPixels(rectLeft, rectTop);
- break;
- }
- case 7: {
- result << coordsToPixels(rectRight, rectTop);
- break;
- }
- case 9: {
- result << coordsToPixels(rectRight, rectBottom);
- break;
- }
- case 3: {
- result << coordsToPixels(rectLeft, rectBottom);
- break;
- }
- }
- break;
- }
- case 6: {
- switch (currentRegion) {
- case 3: {
- result << coordsToPixels(rectLeft, rectBottom);
- break;
- }
- case 9: {
- result << coordsToPixels(rectRight, rectBottom);
- break;
- }
- case 2: {
- result << coordsToPixels(rectLeft, rectBottom);
- result.append(result.last());
- break;
- }
- case 8: {
- result << coordsToPixels(rectRight, rectBottom);
- result.append(result.last());
- break;
- }
- case 1: {
- result << coordsToPixels(rectLeft, rectBottom);
- result.append(result.last());
- result << coordsToPixels(rectLeft, rectTop);
- break;
- }
- case 7: {
- result << coordsToPixels(rectRight, rectBottom);
- result.append(result.last());
- result << coordsToPixels(rectRight, rectTop);
- break;
- }
- }
- break;
- }
- case 7: {
- switch (currentRegion) {
- case 4: {
- result << coordsToPixels(rectRight, rectTop);
- break;
- }
- case 8: {
- result << coordsToPixels(rectRight, rectTop);
- break;
- }
- case 1: {
- result << coordsToPixels(rectRight, rectTop) << coordsToPixels(rectLeft, rectTop);
- break;
- }
- case 9: {
- result << coordsToPixels(rectRight, rectTop) << coordsToPixels(rectRight, rectBottom);
- break;
- }
- case 2: {
- result << coordsToPixels(rectRight, rectTop) << coordsToPixels(rectLeft, rectTop);
- result.append(result.last());
- break;
- }
- case 6: {
- result << coordsToPixels(rectRight, rectTop) << coordsToPixels(rectRight, rectBottom);
- 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) * (rectRight - key) + value
- < rectBottom) // segment passes below R
- {
- result << coordsToPixels(rectRight, rectTop)
- << coordsToPixels(rectRight, rectBottom);
- result.append(result.last());
- result << coordsToPixels(rectLeft, rectBottom);
- } else {
- result << coordsToPixels(rectRight, rectTop) << coordsToPixels(rectLeft, rectTop);
- result.append(result.last());
- result << coordsToPixels(rectLeft, rectBottom);
- }
- break;
- }
- }
- break;
- }
- case 8: {
- switch (currentRegion) {
- case 7: {
- result << coordsToPixels(rectRight, rectTop);
- break;
- }
- case 9: {
- result << coordsToPixels(rectRight, rectBottom);
- break;
- }
- case 4: {
- result << coordsToPixels(rectRight, rectTop);
- result.append(result.last());
- break;
- }
- case 6: {
- result << coordsToPixels(rectRight, rectBottom);
- result.append(result.last());
- break;
- }
- case 1: {
- result << coordsToPixels(rectRight, rectTop);
- result.append(result.last());
- result << coordsToPixels(rectLeft, rectTop);
- break;
- }
- case 3: {
- result << coordsToPixels(rectRight, rectBottom);
- result.append(result.last());
- result << coordsToPixels(rectLeft, rectBottom);
- break;
- }
- }
- break;
- }
- case 9: {
- switch (currentRegion) {
- case 6: {
- result << coordsToPixels(rectRight, rectBottom);
- break;
- }
- case 8: {
- result << coordsToPixels(rectRight, rectBottom);
- break;
- }
- case 3: {
- result << coordsToPixels(rectRight, rectBottom) << coordsToPixels(rectLeft, rectBottom);
- break;
- }
- case 7: {
- result << coordsToPixels(rectRight, rectBottom) << coordsToPixels(rectRight, rectTop);
- break;
- }
- case 2: {
- result << coordsToPixels(rectRight, rectBottom) << coordsToPixels(rectLeft, rectBottom);
- result.append(result.last());
- break;
- }
- case 4: {
- result << coordsToPixels(rectRight, rectBottom) << coordsToPixels(rectRight, rectTop);
- 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) * (rectLeft - key) + value
- < rectBottom) // segment passes below R
- {
- result << coordsToPixels(rectRight, rectBottom)
- << coordsToPixels(rectLeft, rectBottom);
- result.append(result.last());
- result << coordsToPixels(rectLeft, rectTop);
- } else {
- result << coordsToPixels(rectRight, rectBottom)
- << coordsToPixels(rectRight, rectTop);
- result.append(result.last());
- result << coordsToPixels(rectLeft, rectTop);
- }
- break;
- }
- }
- break;
- }
- }
- return result;
- }
-
- /*! \internal
-
- This function is part of the curve optimization algorithm of \ref getCurveData.
-
- 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 getCurveData.
-
- 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 rectLeft, double rectTop, double rectRight, double rectBottom,
- 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, rectBottom)); // direction will be taken care of at end of method
- intersections.append(QPointF(key, rectTop));
- } 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(rectLeft, value)); // direction will be taken care of at end of method
- intersections.append(QPointF(rectRight, value));
- } else // line is skewed
- {
- double gamma;
- double keyPerValue = (key - prevKey) / (value - prevValue);
- // check top of rect:
- gamma = prevKey + (rectTop - prevValue) * keyPerValue;
- if (gamma >= rectLeft && gamma <= rectRight)
- intersections.append(QPointF(gamma, rectTop));
- // check bottom of rect:
- gamma = prevKey + (rectBottom - prevValue) * keyPerValue;
- if (gamma >= rectLeft && gamma <= rectRight)
- intersections.append(QPointF(gamma, rectBottom));
- double valuePerKey = 1.0 / keyPerValue;
- // check left of rect:
- gamma = prevValue + (rectLeft - prevKey) * valuePerKey;
- if (gamma >= rectBottom && gamma <= rectTop)
- intersections.append(QPointF(rectLeft, gamma));
- // check right of rect:
- gamma = prevValue + (rectRight - prevKey) * valuePerKey;
- if (gamma >= rectBottom && gamma <= rectTop)
- intersections.append(QPointF(rectRight, 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 getCurveData.
-
- 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 rectLeft,
- double rectTop, double rectRight, double rectBottom,
- QVector<QPointF>& beforeTraverse,
- QVector<QPointF>& afterTraverse) const
- {
- switch (prevRegion) {
- case 1: {
- switch (currentRegion) {
- case 6: {
- beforeTraverse << coordsToPixels(rectLeft, rectTop);
- break;
- }
- case 9: {
- beforeTraverse << coordsToPixels(rectLeft, rectTop);
- afterTraverse << coordsToPixels(rectRight, rectBottom);
- break;
- }
- case 8: {
- beforeTraverse << coordsToPixels(rectLeft, rectTop);
- break;
- }
- }
- break;
- }
- case 2: {
- switch (currentRegion) {
- case 7: {
- afterTraverse << coordsToPixels(rectRight, rectTop);
- break;
- }
- case 9: {
- afterTraverse << coordsToPixels(rectRight, rectBottom);
- break;
- }
- }
- break;
- }
- case 3: {
- switch (currentRegion) {
- case 4: {
- beforeTraverse << coordsToPixels(rectLeft, rectBottom);
- break;
- }
- case 7: {
- beforeTraverse << coordsToPixels(rectLeft, rectBottom);
- afterTraverse << coordsToPixels(rectRight, rectTop);
- break;
- }
- case 8: {
- beforeTraverse << coordsToPixels(rectLeft, rectBottom);
- break;
- }
- }
- break;
- }
- case 4: {
- switch (currentRegion) {
- case 3: {
- afterTraverse << coordsToPixels(rectLeft, rectBottom);
- break;
- }
- case 9: {
- afterTraverse << coordsToPixels(rectRight, rectBottom);
- break;
- }
- }
- break;
- }
- case 5: {
- break;
- } // shouldn't happen because this method only handles full traverses
- case 6: {
- switch (currentRegion) {
- case 1: {
- afterTraverse << coordsToPixels(rectLeft, rectTop);
- break;
- }
- case 7: {
- afterTraverse << coordsToPixels(rectRight, rectTop);
- break;
- }
- }
- break;
- }
- case 7: {
- switch (currentRegion) {
- case 2: {
- beforeTraverse << coordsToPixels(rectRight, rectTop);
- break;
- }
- case 3: {
- beforeTraverse << coordsToPixels(rectRight, rectTop);
- afterTraverse << coordsToPixels(rectLeft, rectBottom);
- break;
- }
- case 6: {
- beforeTraverse << coordsToPixels(rectRight, rectTop);
- break;
- }
- }
- break;
- }
- case 8: {
- switch (currentRegion) {
- case 1: {
- afterTraverse << coordsToPixels(rectLeft, rectTop);
- break;
- }
- case 3: {
- afterTraverse << coordsToPixels(rectLeft, rectBottom);
- break;
- }
- }
- break;
- }
- case 9: {
- switch (currentRegion) {
- case 2: {
- beforeTraverse << coordsToPixels(rectRight, rectBottom);
- break;
- }
- case 1: {
- beforeTraverse << coordsToPixels(rectRight, rectBottom);
- afterTraverse << coordsToPixels(rectLeft, rectTop);
- break;
- }
- case 4: {
- beforeTraverse << coordsToPixels(rectRight, rectBottom);
- 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.
- */
- double QCPCurve::pointDistance(const QPointF& pixelPoint) const
- {
- if (mData->isEmpty()) {
- qDebug() << Q_FUNC_INFO << "requested point distance on curve" << mName << "without data";
- return 500;
- }
- if (mData->size() == 1) {
- QPointF dataPoint =
- coordsToPixels(mData->constBegin().key(), mData->constBegin().value().value);
- return QVector2D(dataPoint - pixelPoint).length();
- }
-
- // calculate minimum distance to line segments:
- QVector<QPointF>* lineData = new QVector<QPointF>;
- getCurveData(lineData);
- double minDistSqr = std::numeric_limits<double>::max();
- for (int i = 0; i < lineData->size() - 1; ++i) {
- double currentDistSqr = distSqrToLine(lineData->at(i), lineData->at(i + 1), pixelPoint);
- if (currentDistSqr < minDistSqr)
- minDistSqr = currentDistSqr;
- }
- delete lineData;
- return qSqrt(minDistSqr);
- }
-
- /* inherits documentation from base class */
- QCPRange QCPCurve::getKeyRange(bool& foundRange, SignDomain inSignDomain) const
- {
- QCPRange range;
- bool haveLower = false;
- bool haveUpper = false;
-
- double current;
-
- QCPCurveDataMap::const_iterator it = mData->constBegin();
- while (it != mData->constEnd()) {
- current = it.value().key;
- if (!qIsNaN(current) && !qIsNaN(it.value().value)) {
- if (inSignDomain == sdBoth || (inSignDomain == sdNegative && current < 0)
- || (inSignDomain == sdPositive && current > 0)) {
- if (current < range.lower || !haveLower) {
- range.lower = current;
- haveLower = true;
- }
- if (current > range.upper || !haveUpper) {
- range.upper = current;
- haveUpper = true;
- }
- }
- }
- ++it;
- }
-
- foundRange = haveLower && haveUpper;
- return range;
- }
-
- /* inherits documentation from base class */
- QCPRange QCPCurve::getValueRange(bool& foundRange, SignDomain inSignDomain) const
- {
- QCPRange range;
- bool haveLower = false;
- bool haveUpper = false;
-
- double current;
-
- QCPCurveDataMap::const_iterator it = mData->constBegin();
- while (it != mData->constEnd()) {
- current = it.value().value;
- if (!qIsNaN(current) && !qIsNaN(it.value().key)) {
- if (inSignDomain == sdBoth || (inSignDomain == sdNegative && current < 0)
- || (inSignDomain == sdPositive && current > 0)) {
- if (current < range.lower || !haveLower) {
- range.lower = current;
- haveLower = true;
- }
- if (current > range.upper || !haveUpper) {
- range.upper = current;
- haveUpper = true;
- }
- }
- }
- ++it;
- }
-
- foundRange = haveLower && haveUpper;
- return range;
- }
-
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// 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) {
- int startIndex;
- double lowerPixelWidth, upperPixelWidth;
- if (baseBars.size() % 2 == 1
- && index == (baseBars.size() - 1) / 2) // is center bar (int division on purpose)
- {
- return result;
- } else if (index < (baseBars.size() - 1) / 2.0) // bar is to the left of center
- {
- if (baseBars.size() % 2 == 0) // even number of bars
- {
- startIndex = baseBars.size() / 2 - 1;
- result -= getPixelSpacing(baseBars.at(startIndex), keyCoord)
- * 0.5; // half of middle spacing
- } else // uneven number of bars
- {
- startIndex = (baseBars.size() - 1) / 2 - 1;
- 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) // 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;
- } else // bar is to the right of center
- {
- if (baseBars.size() % 2 == 0) // even number of bars
- {
- startIndex = baseBars.size() / 2;
- result += getPixelSpacing(baseBars.at(startIndex), keyCoord)
- * 0.5; // half of middle spacing
- } else // uneven number of bars
- {
- startIndex = (baseBars.size() - 1) / 2 + 1;
- 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) // 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;
- }
- }
- 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 acces 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 bars->keyAxis()->coordToPixel(keyCoord + mSpacing) - keyPixel;
- }
- }
- return 0;
- }
-
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPBarData
- ////////////////////////////////////////////////////////////////////////////////////////////////////
-
- /*! \class QCPBarData
- \brief Holds the data of one single data point (one bar) for QCPBars.
-
- The container for storing multiple data points is \ref QCPBarDataMap.
-
- The stored data is:
- \li \a key: coordinate on the key axis of this bar
- \li \a value: height coordinate on the value axis of this bar
-
- \see QCPBarDataaMap
- */
-
- /*!
- Constructs a bar data point with key and value set to zero.
- */
- QCPBarData::QCPBarData() : key(0), value(0)
- {}
-
- /*!
- Constructs a bar data point with the specified \a key and \a value.
- */
- QCPBarData::QCPBarData(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 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 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::addPlottable, QCustomPlot::removePlottable, etc.)
-
- Usually, you first create an instance:
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcpbars-creation-1
- add it to the customPlot with QCustomPlot::addPlottable:
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcpbars-creation-2
- and then modify the properties of the newly created plottable, e.g.:
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcpbars-creation-3
- */
-
- /* start of documentation of inline functions */
-
- /*! \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 constructed QCPBars can be added to the plot with QCustomPlot::addPlottable, QCustomPlot
- then takes ownership of the bar chart.
- */
- QCPBars::QCPBars(QCPAxis* keyAxis, QCPAxis* valueAxis)
- : QCPAbstractPlottable(keyAxis, valueAxis)
- , mData(new QCPBarDataMap)
- , mWidth(0.75)
- , mWidthType(wtPlotCoords)
- , mBarsGroup(0)
- , mBaseValue(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);
- mSelectedPen = mPen;
- mSelectedPen.setWidthF(2.5);
- mSelectedPen.setColor(QColor(80, 80, 255)); // lighter than Qt::blue of mPen
- mSelectedBrush = mBrush;
- }
-
- QCPBars::~QCPBars()
- {
- setBarsGroup(0);
- if (mBarBelow || mBarAbove)
- connectBars(mBarBelow.data(), mBarAbove.data()); // take this bar out of any stacking
- delete mData;
- }
-
- /*!
- 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;
- }
-
- /*!
- Replaces the current data with the provided \a data.
-
- If \a copy is set to true, data points in \a data will only be copied. if false, the plottable
- takes ownership of the passed data and replaces the internal data pointer with it. This is
- significantly faster than copying for large datasets.
- */
- void QCPBars::setData(QCPBarDataMap* data, bool copy)
- {
- if (mData == data) {
- qDebug() << Q_FUNC_INFO << "The data pointer is already in (and owned by) this plottable"
- << reinterpret_cast<quintptr>(data);
- return;
- }
- if (copy) {
- *mData = *data;
- } else {
- delete mData;
- mData = data;
- }
- }
-
- /*! \overload
-
- Replaces the current data with the provided points in \a key and \a value tuples. The
- provided vectors should have equal length. Else, the number of added points will be the size of
- the smallest vector.
- */
- void QCPBars::setData(const QVector<double>& key, const QVector<double>& value)
- {
- mData->clear();
- int n = key.size();
- n = qMin(n, value.size());
- QCPBarData newData;
- for (int i = 0; i < n; ++i) {
- newData.key = key[i];
- newData.value = value[i];
- mData->insertMulti(newData.key, newData);
- }
- }
-
- /*!
- 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);
- }
- }
-
- /*!
- Adds the provided data points in \a dataMap to the current data.
- \see removeData
- */
- void QCPBars::addData(const QCPBarDataMap& dataMap)
- {
- mData->unite(dataMap);
- }
-
- /*! \overload
- Adds the provided single data point in \a data to the current data.
- \see removeData
- */
- void QCPBars::addData(const QCPBarData& data)
- {
- mData->insertMulti(data.key, data);
- }
-
- /*! \overload
- Adds the provided single data point as \a key and \a value tuple to the current data
- \see removeData
- */
- void QCPBars::addData(double key, double value)
- {
- QCPBarData newData;
- newData.key = key;
- newData.value = value;
- mData->insertMulti(newData.key, newData);
- }
-
- /*! \overload
- Adds the provided data points as \a key and \a value tuples to the current data.
- \see removeData
- */
- void QCPBars::addData(const QVector<double>& keys, const QVector<double>& values)
- {
- int n = keys.size();
- n = qMin(n, values.size());
- QCPBarData newData;
- for (int i = 0; i < n; ++i) {
- newData.key = keys[i];
- newData.value = values[i];
- mData->insertMulti(newData.key, newData);
- }
- }
-
- /*!
- Removes all data points with key smaller than \a key.
- \see addData, clearData
- */
- void QCPBars::removeDataBefore(double key)
- {
- QCPBarDataMap::iterator it = mData->begin();
- while (it != mData->end() && it.key() < key)
- it = mData->erase(it);
- }
-
- /*!
- Removes all data points with key greater than \a key.
- \see addData, clearData
- */
- void QCPBars::removeDataAfter(double key)
- {
- if (mData->isEmpty())
- return;
- QCPBarDataMap::iterator it = mData->upperBound(key);
- while (it != mData->end())
- it = mData->erase(it);
- }
-
- /*!
- Removes all data points with key between \a fromKey and \a toKey. if \a fromKey is
- greater or equal to \a toKey, the function does nothing. To remove a single data point with known
- key, use \ref removeData(double key).
-
- \see addData, clearData
- */
- void QCPBars::removeData(double fromKey, double toKey)
- {
- if (fromKey >= toKey || mData->isEmpty())
- return;
- QCPBarDataMap::iterator it = mData->upperBound(fromKey);
- QCPBarDataMap::iterator itEnd = mData->upperBound(toKey);
- while (it != itEnd)
- it = mData->erase(it);
- }
-
- /*! \overload
-
- Removes a single data point at \a key. If the position is not known with absolute precision,
- consider using \ref removeData(double fromKey, double toKey) with a small fuzziness interval
- around the suspected position, depeding on the precision with which the key is known.
-
- \see addData, clearData
- */
- void QCPBars::removeData(double key)
- {
- mData->remove(key);
- }
-
- /*!
- Removes all data points.
- \see removeData, removeDataAfter, removeDataBefore
- */
- void QCPBars::clearData()
- {
- mData->clear();
- }
-
- /* inherits documentation from base class */
- double QCPBars::selectTest(const QPointF& pos, bool onlySelectable, QVariant* details) const
- {
- Q_UNUSED(details)
- if (onlySelectable && !mSelectable)
- return -1;
- if (!mKeyAxis || !mValueAxis) {
- qDebug() << Q_FUNC_INFO << "invalid key or value axis";
- return -1;
- }
-
- if (mKeyAxis.data()->axisRect()->rect().contains(pos.toPoint())) {
- QCPBarDataMap::ConstIterator it;
- for (it = mData->constBegin(); it != mData->constEnd(); ++it) {
- if (getBarPolygon(it.value().key, it.value().value).boundingRect().contains(pos))
- return mParentPlot->selectionTolerance() * 0.99;
- }
- }
- return -1;
- }
-
- /* inherits documentation from base class */
- void QCPBars::draw(QCPPainter* painter)
- {
- if (!mKeyAxis || !mValueAxis) {
- qDebug() << Q_FUNC_INFO << "invalid key or value axis";
- return;
- }
- if (mData->isEmpty())
- return;
-
- QCPBarDataMap::const_iterator it, lower, upperEnd;
- getVisibleDataBounds(lower, upperEnd);
- for (it = lower; it != upperEnd; ++it) {
- // check data validity if flag set:
- #ifdef QCUSTOMPLOT_CHECK_DATA
- if (QCP::isInvalidData(it.value().key, it.value().value))
- qDebug() << Q_FUNC_INFO << "Data point at" << it.key() << "of drawn range invalid."
- << "Plottable name:" << name();
- #endif
- QPolygonF barPolygon = getBarPolygon(it.key(), it.value().value);
- // draw bar fill:
- if (mainBrush().style() != Qt::NoBrush && mainBrush().color().alpha() != 0) {
- applyFillAntialiasingHint(painter);
- painter->setPen(Qt::NoPen);
- painter->setBrush(mainBrush());
- painter->drawPolygon(barPolygon);
- }
- // draw bar line:
- if (mainPen().style() != Qt::NoPen && mainPen().color().alpha() != 0) {
- applyDefaultAntialiasingHint(painter);
- painter->setPen(mainPen());
- painter->setBrush(Qt::NoBrush);
- painter->drawPolyline(barPolygon);
- }
- }
- }
-
- /* 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 lower 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 upperEnd returns an iterator one higher than the highest visible data point. Same as before, \a
- upperEnd may also lie just outside of the visible range.
-
- if the bars plottable contains no data, both \a lower and \a upperEnd point to constEnd.
- */
- void QCPBars::getVisibleDataBounds(QCPBarDataMap::const_iterator& lower,
- QCPBarDataMap::const_iterator& upperEnd) const
- {
- if (!mKeyAxis) {
- qDebug() << Q_FUNC_INFO << "invalid key axis";
- return;
- }
- if (mData->isEmpty()) {
- lower = mData->constEnd();
- upperEnd = mData->constEnd();
- return;
- }
-
- // get visible data range as QMap iterators
- lower = mData->lowerBound(mKeyAxis.data()->range().lower);
- upperEnd = mData->upperBound(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 lbound to find lower bar that actually is completely outside visible pixel
- // range:
- QCPBarDataMap::const_iterator it = lower;
- while (it != mData->constBegin()) {
- --it;
- QRectF barBounds = getBarPolygon(it.value().key, it.value().value).boundingRect();
- if (mKeyAxis.data()->orientation() == Qt::Horizontal)
- isVisible =
- ((!mKeyAxis.data()->rangeReversed() && barBounds.right() >= lowerPixelBound)
- || (mKeyAxis.data()->rangeReversed() && barBounds.left() <= lowerPixelBound));
- else // keyaxis is vertical
- isVisible =
- ((!mKeyAxis.data()->rangeReversed() && barBounds.top() <= lowerPixelBound)
- || (mKeyAxis.data()->rangeReversed() && barBounds.bottom() >= lowerPixelBound));
- if (isVisible)
- lower = it;
- else
- break;
- }
- // walk right from ubound to find upper bar that actually is completely outside visible pixel
- // range:
- it = upperEnd;
- while (it != mData->constEnd()) {
- QRectF barBounds =
- getBarPolygon(upperEnd.value().key, upperEnd.value().value).boundingRect();
- if (mKeyAxis.data()->orientation() == Qt::Horizontal)
- isVisible =
- ((!mKeyAxis.data()->rangeReversed() && barBounds.left() <= upperPixelBound)
- || (mKeyAxis.data()->rangeReversed() && barBounds.right() >= upperPixelBound));
- else // keyaxis is vertical
- isVisible =
- ((!mKeyAxis.data()->rangeReversed() && barBounds.bottom() >= upperPixelBound)
- || (mKeyAxis.data()->rangeReversed() && barBounds.top() <= upperPixelBound));
- if (isVisible)
- upperEnd = it + 1;
- else
- break;
- ++it;
- }
- }
-
- /*! \internal
-
- Returns the polygon of a single bar with \a key and \a value. The Polygon is open at the bottom
- and shifted according to the bar stacking (see \ref moveAbove) and base value (see \ref
- setBaseValue).
- */
- QPolygonF QCPBars::getBarPolygon(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 QPolygonF();
- }
-
- QPolygonF result;
- 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);
- if (keyAxis->orientation() == Qt::Horizontal) {
- result << QPointF(keyPixel + lowerPixelWidth, basePixel);
- result << QPointF(keyPixel + lowerPixelWidth, valuePixel);
- result << QPointF(keyPixel + upperPixelWidth, valuePixel);
- result << QPointF(keyPixel + upperPixelWidth, basePixel);
- } else {
- result << QPointF(basePixel, keyPixel + lowerPixelWidth);
- result << QPointF(valuePixel, keyPixel + lowerPixelWidth);
- result << QPointF(valuePixel, keyPixel + upperPixelWidth);
- result << QPointF(basePixel, keyPixel + upperPixelWidth);
- }
- return result;
- }
-
- /*! \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
- {
- switch (mWidthType) {
- case wtAbsolute: {
- upper = mWidth * 0.5;
- lower = -upper;
- if (mKeyAxis
- && (mKeyAxis.data()->rangeReversed()
- ^ (mKeyAxis.data()->orientation() == Qt::Vertical)))
- qSwap(lower, upper);
- break;
- }
- case wtAxisRectRatio: {
- if (mKeyAxis && mKeyAxis.data()->axisRect()) {
- if (mKeyAxis.data()->orientation() == Qt::Horizontal)
- upper = mKeyAxis.data()->axisRect()->width() * mWidth * 0.5;
- else
- upper = mKeyAxis.data()->axisRect()->height() * mWidth * 0.5;
- lower = -upper;
- if (mKeyAxis
- && (mKeyAxis.data()->rangeReversed()
- ^ (mKeyAxis.data()->orientation() == Qt::Vertical)))
- qSwap(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 use 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) * 1e-6; // should be safe even when changed to use float at some point
- if (key == 0)
- epsilon = 1e-6;
- QCPBarDataMap::const_iterator it = mBarBelow.data()->mData->lowerBound(key - epsilon);
- QCPBarDataMap::const_iterator itEnd = mBarBelow.data()->mData->upperBound(key + epsilon);
- while (it != itEnd) {
- if ((positive && it.value().value > max) || (!positive && it.value().value < max))
- max = it.value().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;
- }
- }
-
- /* inherits documentation from base class */
- QCPRange QCPBars::getKeyRange(bool& foundRange, SignDomain inSignDomain) const
- {
- QCPRange range;
- bool haveLower = false;
- bool haveUpper = false;
-
- double current;
- QCPBarDataMap::const_iterator it = mData->constBegin();
- while (it != mData->constEnd()) {
- current = it.value().key;
- if (inSignDomain == sdBoth || (inSignDomain == sdNegative && current < 0)
- || (inSignDomain == sdPositive && current > 0)) {
- if (current < range.lower || !haveLower) {
- range.lower = current;
- haveLower = true;
- }
- if (current > range.upper || !haveUpper) {
- range.upper = current;
- haveUpper = true;
- }
- }
- ++it;
- }
- // determine exact range of bars by including bar width and barsgroup offset:
- if (haveLower && mKeyAxis) {
- double lowerPixelWidth, upperPixelWidth, keyPixel;
- getPixelWidth(range.lower, lowerPixelWidth, upperPixelWidth);
- keyPixel = mKeyAxis.data()->coordToPixel(range.lower) + lowerPixelWidth;
- if (mBarsGroup)
- keyPixel += mBarsGroup->keyPixelOffset(this, range.lower);
- range.lower = mKeyAxis.data()->pixelToCoord(keyPixel);
- }
- if (haveUpper && mKeyAxis) {
- double lowerPixelWidth, upperPixelWidth, keyPixel;
- getPixelWidth(range.upper, lowerPixelWidth, upperPixelWidth);
- keyPixel = mKeyAxis.data()->coordToPixel(range.upper) + upperPixelWidth;
- if (mBarsGroup)
- keyPixel += mBarsGroup->keyPixelOffset(this, range.upper);
- range.upper = mKeyAxis.data()->pixelToCoord(keyPixel);
- }
- foundRange = haveLower && haveUpper;
- return range;
- }
-
- /* inherits documentation from base class */
- QCPRange QCPBars::getValueRange(bool& foundRange, SignDomain inSignDomain) const
- {
- 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
- double current;
-
- QCPBarDataMap::const_iterator it = mData->constBegin();
- while (it != mData->constEnd()) {
- current = it.value().value + getStackedBaseValue(it.value().key, it.value().value >= 0);
- if (inSignDomain == sdBoth || (inSignDomain == sdNegative && current < 0)
- || (inSignDomain == sdPositive && current > 0)) {
- if (current < range.lower || !haveLower) {
- range.lower = current;
- haveLower = true;
- }
- if (current > range.upper || !haveUpper) {
- range.upper = current;
- haveUpper = true;
- }
- }
- ++it;
- }
-
- foundRange = true; // return true because bar charts always have the 0-line visible
- return range;
- }
-
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// 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 individual parameter functions or use \ref setData to set all
- parameters at once. The individual functions are:
- \li \ref setMinimum
- \li \ref setLowerQuartile
- \li \ref setMedian
- \li \ref setUpperQuartile
- \li \ref setMaximum
-
- Additionally you can define a list of outliers, drawn as scatter datapoints:
- \li \ref setOutliers
-
- \section appearance Changing the appearance
-
- The appearance of the box itself is controlled via \ref setPen and \ref setBrush. You may change
- the width of the box with \ref setWidth in plot coordinates (not pixels).
-
- Analog functions exist for the minimum/maximum-whiskers: \ref setWhiskerPen, \ref
- setWhiskerBarPen, \ref setWhiskerWidth. The whisker width is the width of the bar at the top
- (maximum) and bottom (minimum).
-
- The median indicator line has its own pen, \ref setMedianPen.
-
- If the whisker backbone pen is changed, make sure to set the capStyle to Qt::FlatCap. Else, the
- backbone line might exceed the whisker bars by a few pixels due to the pen cap being not
- perfectly flat.
-
- The Outlier data points are drawn as normal scatter points. Their look can be controlled with
- \ref setOutlierStyle
-
- \section usage Usage
-
- Like all data representing objects in QCustomPlot, the QCPStatisticalBox is a plottable.
- Usually, you first create an instance and add it to the customPlot:
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcpstatisticalbox-creation-1
- and then modify the properties of the newly created plottable, e.g.:
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcpstatisticalbox-creation-2
- */
-
- /*!
- 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 constructed statistical box can be added to the plot with QCustomPlot::addPlottable,
- QCustomPlot then takes ownership of the statistical box.
- */
- QCPStatisticalBox::QCPStatisticalBox(QCPAxis* keyAxis, QCPAxis* valueAxis)
- : QCPAbstractPlottable(keyAxis, valueAxis)
- , mKey(0)
- , mMinimum(0)
- , mLowerQuartile(0)
- , mMedian(0)
- , mUpperQuartile(0)
- , mMaximum(0)
- {
- setOutlierStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, Qt::blue, 6));
- setWhiskerWidth(0.2);
- setWidth(0.5);
-
- setPen(QPen(Qt::black));
- setSelectedPen(QPen(Qt::blue, 2.5));
- setMedianPen(QPen(Qt::black, 3, Qt::SolidLine, Qt::FlatCap));
- setWhiskerPen(QPen(Qt::black, 0, Qt::DashLine, Qt::FlatCap));
- setWhiskerBarPen(QPen(Qt::black));
- setBrush(Qt::NoBrush);
- setSelectedBrush(Qt::NoBrush);
- }
-
- /*!
- Sets the key coordinate of the statistical box.
- */
- void QCPStatisticalBox::setKey(double key)
- {
- mKey = key;
- }
-
- /*!
- Sets the parameter "minimum" of the statistical box plot. This is the position of the lower
- whisker, typically the minimum measurement of the sample that's not considered an outlier.
-
- \see setMaximum, setWhiskerPen, setWhiskerBarPen, setWhiskerWidth
- */
- void QCPStatisticalBox::setMinimum(double value)
- {
- mMinimum = value;
- }
-
- /*!
- Sets the parameter "lower Quartile" of the statistical box plot. This is the lower end of the
- box. The lower and the upper quartiles are the two statistical quartiles around the median of the
- sample, they contain 50% of the sample data.
-
- \see setUpperQuartile, setPen, setBrush, setWidth
- */
- void QCPStatisticalBox::setLowerQuartile(double value)
- {
- mLowerQuartile = value;
- }
-
- /*!
- Sets the parameter "median" of the statistical box plot. This is 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).
-
- \see setMedianPen
- */
- void QCPStatisticalBox::setMedian(double value)
- {
- mMedian = value;
- }
-
- /*!
- Sets the parameter "upper Quartile" of the statistical box plot. This is the upper end of the
- box. The lower and the upper quartiles are the two statistical quartiles around the median of the
- sample, they contain 50% of the sample data.
-
- \see setLowerQuartile, setPen, setBrush, setWidth
- */
- void QCPStatisticalBox::setUpperQuartile(double value)
- {
- mUpperQuartile = value;
- }
-
- /*!
- Sets the parameter "maximum" of the statistical box plot. This is the position of the upper
- whisker, typically the maximum measurement of the sample that's not considered an outlier.
-
- \see setMinimum, setWhiskerPen, setWhiskerBarPen, setWhiskerWidth
- */
- void QCPStatisticalBox::setMaximum(double value)
- {
- mMaximum = value;
- }
-
- /*!
- Sets a vector of outlier values that will be drawn as scatters. Any data points in the sample
- that are not within the whiskers (\ref setMinimum, \ref setMaximum) should be considered outliers
- and displayed as such.
-
- \see setOutlierStyle
- */
- void QCPStatisticalBox::setOutliers(const QVector<double>& values)
- {
- mOutliers = values;
- }
-
- /*!
- Sets all parameters of the statistical box plot at once.
-
- \see setKey, setMinimum, setLowerQuartile, setMedian, setUpperQuartile, setMaximum
- */
- void QCPStatisticalBox::setData(double key, double minimum, double lowerQuartile, double median,
- double upperQuartile, double maximum)
- {
- setKey(key);
- setMinimum(minimum);
- setLowerQuartile(lowerQuartile);
- setMedian(median);
- setUpperQuartile(upperQuartile);
- setMaximum(maximum);
- }
-
- /*!
- Sets the width of the box in key coordinates.
-
- \see setWhiskerWidth
- */
- void QCPStatisticalBox::setWidth(double width)
- {
- mWidth = width;
- }
-
- /*!
- Sets the width of the whiskers (\ref setMinimum, \ref setMaximum) in key coordinates.
-
- \see setWidth
- */
- void QCPStatisticalBox::setWhiskerWidth(double width)
- {
- mWhiskerWidth = width;
- }
-
- /*!
- Sets the pen used for drawing the whisker backbone (That's the line parallel to the value axis).
-
- Make sure to set the \a pen capStyle to Qt::FlatCap to prevent the whisker backbone from reaching
- a few pixels past the whisker bars, when using a non-zero pen width.
-
- \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).
-
- \see setWhiskerPen
- */
- void QCPStatisticalBox::setWhiskerBarPen(const QPen& pen)
- {
- mWhiskerBarPen = pen;
- }
-
- /*!
- Sets the pen used for drawing the median indicator line inside the statistical box.
- */
- void QCPStatisticalBox::setMedianPen(const QPen& pen)
- {
- mMedianPen = pen;
- }
-
- /*!
- Sets the appearance of the outlier data points.
-
- \see setOutliers
- */
- void QCPStatisticalBox::setOutlierStyle(const QCPScatterStyle& style)
- {
- mOutlierStyle = style;
- }
-
- /* inherits documentation from base class */
- void QCPStatisticalBox::clearData()
- {
- setOutliers(QVector<double>());
- setKey(0);
- setMinimum(0);
- setLowerQuartile(0);
- setMedian(0);
- setUpperQuartile(0);
- setMaximum(0);
- }
-
- /* inherits documentation from base class */
- double QCPStatisticalBox::selectTest(const QPointF& pos, bool onlySelectable,
- QVariant* details) const
- {
- Q_UNUSED(details)
- if (onlySelectable && !mSelectable)
- return -1;
- if (!mKeyAxis || !mValueAxis) {
- qDebug() << Q_FUNC_INFO << "invalid key or value axis";
- return -1;
- }
-
- if (mKeyAxis.data()->axisRect()->rect().contains(pos.toPoint())) {
- double posKey, posValue;
- pixelsToCoords(pos, posKey, posValue);
- // quartile box:
- QCPRange keyRange(mKey - mWidth * 0.5, mKey + mWidth * 0.5);
- QCPRange valueRange(mLowerQuartile, mUpperQuartile);
- if (keyRange.contains(posKey) && valueRange.contains(posValue))
- return mParentPlot->selectionTolerance() * 0.99;
-
- // min/max whiskers:
- if (QCPRange(mMinimum, mMaximum).contains(posValue))
- return qAbs(mKeyAxis.data()->coordToPixel(mKey)
- - mKeyAxis.data()->coordToPixel(posKey));
- }
- return -1;
- }
-
- /* inherits documentation from base class */
- void QCPStatisticalBox::draw(QCPPainter* painter)
- {
- if (!mKeyAxis || !mValueAxis) {
- qDebug() << Q_FUNC_INFO << "invalid key or value axis";
- return;
- }
-
- // check data validity if flag set:
- #ifdef QCUSTOMPLOT_CHECK_DATA
- if (QCP::isInvalidData(mKey, mMedian) || QCP::isInvalidData(mLowerQuartile, mUpperQuartile)
- || QCP::isInvalidData(mMinimum, mMaximum))
- qDebug() << Q_FUNC_INFO << "Data point at" << mKey << "of drawn range has invalid data."
- << "Plottable name:" << name();
- for (int i = 0; i < mOutliers.size(); ++i)
- if (QCP::isInvalidData(mOutliers.at(i)))
- qDebug() << Q_FUNC_INFO << "Data point outlier at" << mKey << "of drawn range invalid."
- << "Plottable name:" << name();
- #endif
-
- QRectF quartileBox;
- drawQuartileBox(painter, &quartileBox);
-
- painter->save();
- painter->setClipRect(quartileBox, Qt::IntersectClip);
- drawMedian(painter);
- painter->restore();
-
- drawWhiskers(painter);
- drawOutliers(painter);
- }
-
- /* 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);
- }
-
- /*! \internal
-
- Draws the quartile box. \a box is an output parameter that returns the quartile box (in pixel
- coordinates) which is used to set the clip rect of the painter before calling \ref drawMedian (so
- the median doesn't draw outside the quartile box).
- */
- void QCPStatisticalBox::drawQuartileBox(QCPPainter* painter, QRectF* quartileBox) const
- {
- QRectF box;
- box.setTopLeft(coordsToPixels(mKey - mWidth * 0.5, mUpperQuartile));
- box.setBottomRight(coordsToPixels(mKey + mWidth * 0.5, mLowerQuartile));
- applyDefaultAntialiasingHint(painter);
- painter->setPen(mainPen());
- painter->setBrush(mainBrush());
- painter->drawRect(box);
- if (quartileBox)
- *quartileBox = box;
- }
-
- /*! \internal
-
- Draws the median line inside the quartile box.
- */
- void QCPStatisticalBox::drawMedian(QCPPainter* painter) const
- {
- QLineF medianLine;
- medianLine.setP1(coordsToPixels(mKey - mWidth * 0.5, mMedian));
- medianLine.setP2(coordsToPixels(mKey + mWidth * 0.5, mMedian));
- applyDefaultAntialiasingHint(painter);
- painter->setPen(mMedianPen);
- painter->drawLine(medianLine);
- }
-
- /*! \internal
-
- Draws both whisker backbones and bars.
- */
- void QCPStatisticalBox::drawWhiskers(QCPPainter* painter) const
- {
- QLineF backboneMin, backboneMax, barMin, barMax;
- backboneMax.setPoints(coordsToPixels(mKey, mUpperQuartile), coordsToPixels(mKey, mMaximum));
- backboneMin.setPoints(coordsToPixels(mKey, mLowerQuartile), coordsToPixels(mKey, mMinimum));
- barMax.setPoints(coordsToPixels(mKey - mWhiskerWidth * 0.5, mMaximum),
- coordsToPixels(mKey + mWhiskerWidth * 0.5, mMaximum));
- barMin.setPoints(coordsToPixels(mKey - mWhiskerWidth * 0.5, mMinimum),
- coordsToPixels(mKey + mWhiskerWidth * 0.5, mMinimum));
- applyErrorBarsAntialiasingHint(painter);
- painter->setPen(mWhiskerPen);
- painter->drawLine(backboneMin);
- painter->drawLine(backboneMax);
- painter->setPen(mWhiskerBarPen);
- painter->drawLine(barMin);
- painter->drawLine(barMax);
- }
-
- /*! \internal
-
- Draws the outlier scatter points.
- */
- void QCPStatisticalBox::drawOutliers(QCPPainter* painter) const
- {
- applyScattersAntialiasingHint(painter);
- mOutlierStyle.applyTo(painter, mPen);
- for (int i = 0; i < mOutliers.size(); ++i)
- mOutlierStyle.drawShape(painter, coordsToPixels(mKey, mOutliers.at(i)));
- }
-
- /* inherits documentation from base class */
- QCPRange QCPStatisticalBox::getKeyRange(bool& foundRange, SignDomain inSignDomain) const
- {
- foundRange = true;
- if (inSignDomain == sdBoth) {
- return QCPRange(mKey - mWidth * 0.5, mKey + mWidth * 0.5);
- } else if (inSignDomain == sdNegative) {
- if (mKey + mWidth * 0.5 < 0)
- return QCPRange(mKey - mWidth * 0.5, mKey + mWidth * 0.5);
- else if (mKey < 0)
- return QCPRange(mKey - mWidth * 0.5, mKey);
- else {
- foundRange = false;
- return QCPRange();
- }
- } else if (inSignDomain == sdPositive) {
- if (mKey - mWidth * 0.5 > 0)
- return QCPRange(mKey - mWidth * 0.5, mKey + mWidth * 0.5);
- else if (mKey > 0)
- return QCPRange(mKey, mKey + mWidth * 0.5);
- else {
- foundRange = false;
- return QCPRange();
- }
- }
- foundRange = false;
- return QCPRange();
- }
-
- /* inherits documentation from base class */
- QCPRange QCPStatisticalBox::getValueRange(bool& foundRange, SignDomain inSignDomain) const
- {
- QVector<double>
- values; // values that must be considered (i.e. all outliers and the five box-parameters)
- values.reserve(mOutliers.size() + 5);
- values << mMaximum << mUpperQuartile << mMedian << mLowerQuartile << mMinimum;
- values << mOutliers;
- // go through values and find the ones in legal range:
- bool haveUpper = false;
- bool haveLower = false;
- double upper = 0;
- double lower = 0;
- for (int i = 0; i < values.size(); ++i) {
- if ((inSignDomain == sdNegative && values.at(i) < 0)
- || (inSignDomain == sdPositive && values.at(i) > 0) || (inSignDomain == sdBoth)) {
- if (values.at(i) > upper || !haveUpper) {
- upper = values.at(i);
- haveUpper = true;
- }
- if (values.at(i) < lower || !haveLower) {
- lower = values.at(i);
- haveLower = true;
- }
- }
- }
- // return the bounds if we found some sensible values:
- if (haveLower && haveUpper) {
- foundRange = true;
- return QCPRange(lower, upper);
- } else // might happen if all values are in other sign domain
- {
- foundRange = false;
- return QCPRange();
- }
- }
-
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// 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.
-
- 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)
- , mDataModified(true)
- {
- setSize(keySize, valueSize);
- fill(0);
- }
-
- QCPColorMapData::~QCPColorMapData()
- {
- if (mData)
- delete[] mData;
- }
-
- /*!
- 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), mDataModified(true)
- {
- *this = other;
- }
-
- /*!
- Overwrites this color map data instance with the data stored in \a other.
- */
- QCPColorMapData& QCPColorMapData::operator=(const QCPColorMapData& other)
- {
- if (&other != this) {
- const int keySize = other.keySize();
- const int valueSize = other.valueSize();
- setSize(keySize, valueSize);
- setRange(other.keyRange(), other.valueRange());
- if (!mIsEmpty)
- memcpy(mData, other.mData, sizeof(mData[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;
- }
-
- /*!
- 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;
- 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;
- }
- }
-
- /*!
- 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);
- }
-
- /*!
- 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;
- }
-
- /*!
- 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;
- }
-
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// 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 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 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::addPlottable, QCustomPlot::removePlottable, etc.)
-
- Usually, you first create an instance and add it to the customPlot:
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcolormap-creation-1
- and then modify the properties of the newly created color map, 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(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(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 constructed QCPColorMap can be added to the plot with QCustomPlot::addPlottable, QCustomPlot
- then takes ownership of the color map.
- */
- 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);
- }
- }
-
- /*!
- Clears the colormap data by calling \ref QCPColorMapData::clear() on the internal data. This also
- resizes the map to 0x0 cells.
- */
- void QCPColorMap::clearData()
- {
- mMapData->clear();
- }
-
- /* inherits documentation from base class */
- double QCPColorMap::selectTest(const QPointF& pos, bool onlySelectable, QVariant* details) const
- {
- Q_UNUSED(details)
- if (onlySelectable && !mSelectable)
- return -1;
- if (!mKeyAxis || !mValueAxis) {
- qDebug() << Q_FUNC_INFO << "invalid key or value axis";
- 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))
- return mParentPlot->selectionTolerance() * 0.99;
- }
- return -1;
- }
-
- /*! \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 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),
- QImage::Format_RGB32);
- else if (keyAxis->orientation() == Qt::Vertical
- && (mMapImage.width() != valueSize * valueOversamplingFactor
- || mMapImage.height() != keySize * keyOversamplingFactor))
- mMapImage =
- QImage(QSize(valueSize * valueOversamplingFactor, keySize * keyOversamplingFactor),
- QImage::Format_RGB32);
-
- 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), QImage::Format_RGB32);
- else if (keyAxis->orientation() == Qt::Vertical
- && (mUndersampledMapImage.width() != valueSize
- || mUndersampledMapImage.height() != keySize))
- mUndersampledMapImage = QImage(QSize(valueSize, keySize), QImage::Format_RGB32);
- 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;
- 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)
- 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)
- 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):
- 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;
- double mapBufferPixelRatio = 3; // factor by which DPI is increased in embedded bitmaps
- if (useBuffer) {
- 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);
- bool mirrorX =
- (keyAxis()->orientation() == Qt::Horizontal ? keyAxis() : valueAxis())->rangeReversed();
- bool mirrorY =
- (valueAxis()->orientation() == Qt::Vertical ? valueAxis() : keyAxis())->rangeReversed();
- 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));
- */
- }
-
- /* inherits documentation from base class */
- QCPRange QCPColorMap::getKeyRange(bool& foundRange, SignDomain inSignDomain) const
- {
- foundRange = true;
- QCPRange result = mMapData->keyRange();
- result.normalize();
- if (inSignDomain == QCPAbstractPlottable::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 == QCPAbstractPlottable::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, SignDomain inSignDomain) const
- {
- foundRange = true;
- QCPRange result = mMapData->valueRange();
- result.normalize();
- if (inSignDomain == QCPAbstractPlottable::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 == QCPAbstractPlottable::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;
- }
-
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPFinancialData
- ////////////////////////////////////////////////////////////////////////////////////////////////////
-
- /*! \class QCPFinancialData
- \brief Holds the data of one single data point for QCPFinancial.
-
- The container for storing multiple data points is \ref QCPFinancialDataMap.
-
- The stored data is:
- \li \a key: coordinate on the key axis of this data point
- \li \a open: The opening value at the data point
- \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
-
- \see QCPFinancialDataMap
- */
-
- /*!
- 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 is given in plot
- key coordinates. A typical choice is to set it to (or slightly less than) one bin interval width.
-
- \section 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 (\ref setSelectedPen, \ref setSelectedBrush) is used,
- irrespective of whether the chart is single- or two-colored.
-
- */
-
- /* start of documentation of inline functions */
-
- /*! \fn QCPFinancialDataMap *QCPFinancial::data() const
-
- Returns a pointer to the internal data storage of type \ref QCPFinancialDataMap. 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 constructed QCPFinancial can be added to the plot with QCustomPlot::addPlottable, QCustomPlot
- then takes ownership of the financial chart.
- */
- QCPFinancial::QCPFinancial(QCPAxis* keyAxis, QCPAxis* valueAxis)
- : QCPAbstractPlottable(keyAxis, valueAxis)
- , mData(0)
- , mChartStyle(csOhlc)
- , mWidth(0.5)
- , mTwoColored(false)
- , mBrushPositive(QBrush(QColor(210, 210, 255)))
- , mBrushNegative(QBrush(QColor(255, 210, 210)))
- , mPenPositive(QPen(QColor(10, 40, 180)))
- , mPenNegative(QPen(QColor(180, 40, 10)))
- {
- mData = new QCPFinancialDataMap;
-
- setSelectedPen(QPen(QColor(80, 80, 255), 2.5));
- setSelectedBrush(QBrush(QColor(80, 80, 255)));
- }
-
- QCPFinancial::~QCPFinancial()
- {
- delete mData;
- }
-
- /*!
- Replaces the current data with the provided \a data.
-
- If \a copy is set to true, data points in \a data will only be copied. if false, the plottable
- takes ownership of the passed data and replaces the internal data pointer with it. This is
- significantly faster than copying for large datasets.
-
- Alternatively, you can also access and modify the plottable's data via the \ref data method, which
- returns a pointer to the internal \ref QCPFinancialDataMap.
-
- \see timeSeriesToOhlc
- */
- void QCPFinancial::setData(QCPFinancialDataMap* data, bool copy)
- {
- if (mData == data) {
- qDebug() << Q_FUNC_INFO << "The data pointer is already in (and owned by) this plottable"
- << reinterpret_cast<quintptr>(data);
- return;
- }
- if (copy) {
- *mData = *data;
- } else {
- delete mData;
- mData = data;
- }
- }
-
- /*! \overload
-
- Replaces the current data with the provided open/high/low/close data. The provided vectors should
- have equal length. Else, the number of added points will be the size of the smallest vector.
-
- \see timeSeriesToOhlc
- */
- void QCPFinancial::setData(const QVector<double>& key, const QVector<double>& open,
- const QVector<double>& high, const QVector<double>& low,
- const QVector<double>& close)
- {
- mData->clear();
- int n = key.size();
- n = qMin(n, open.size());
- n = qMin(n, high.size());
- n = qMin(n, low.size());
- n = qMin(n, close.size());
- for (int i = 0; i < n; ++i) {
- mData->insertMulti(key[i], QCPFinancialData(key[i], open[i], high[i], low[i], close[i]));
- }
- }
-
- /*!
- 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 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;
- }
-
- /*!
- Adds the provided data points in \a dataMap to the current data.
-
- Alternatively, you can also access and modify the data via the \ref data method, which returns a
- pointer to the internal \ref QCPFinancialDataMap.
-
- \see removeData
- */
- void QCPFinancial::addData(const QCPFinancialDataMap& dataMap)
- {
- mData->unite(dataMap);
- }
-
- /*! \overload
-
- Adds the provided single data point in \a data to the current data.
-
- Alternatively, you can also access and modify the data via the \ref data method, which returns a
- pointer to the internal \ref QCPFinancialData.
-
- \see removeData
- */
- void QCPFinancial::addData(const QCPFinancialData& data)
- {
- mData->insertMulti(data.key, data);
- }
-
- /*! \overload
-
- Adds the provided single data point given by \a key, \a open, \a high, \a low, and \a close to
- the current data.
-
- Alternatively, you can also access and modify the data via the \ref data method, which returns a
- pointer to the internal \ref QCPFinancialData.
-
- \see removeData
- */
- void QCPFinancial::addData(double key, double open, double high, double low, double close)
- {
- mData->insertMulti(key, QCPFinancialData(key, open, high, low, close));
- }
-
- /*! \overload
-
- Adds the provided open/high/low/close data to the current data.
-
- Alternatively, you can also access and modify the data via the \ref data method, which returns a
- pointer to the internal \ref QCPFinancialData.
-
- \see removeData
- */
- void QCPFinancial::addData(const QVector<double>& key, const QVector<double>& open,
- const QVector<double>& high, const QVector<double>& low,
- const QVector<double>& close)
- {
- int n = key.size();
- n = qMin(n, open.size());
- n = qMin(n, high.size());
- n = qMin(n, low.size());
- n = qMin(n, close.size());
- for (int i = 0; i < n; ++i) {
- mData->insertMulti(key[i], QCPFinancialData(key[i], open[i], high[i], low[i], close[i]));
- }
- }
-
- /*!
- Removes all data points with keys smaller than \a key.
-
- \see addData, clearData
- */
- void QCPFinancial::removeDataBefore(double key)
- {
- QCPFinancialDataMap::iterator it = mData->begin();
- while (it != mData->end() && it.key() < key)
- it = mData->erase(it);
- }
-
- /*!
- Removes all data points with keys greater than \a key.
-
- \see addData, clearData
- */
- void QCPFinancial::removeDataAfter(double key)
- {
- if (mData->isEmpty())
- return;
- QCPFinancialDataMap::iterator it = mData->upperBound(key);
- while (it != mData->end())
- it = mData->erase(it);
- }
-
- /*!
- Removes all data points with keys between \a fromKey and \a toKey. if \a fromKey is greater or
- equal to \a toKey, the function does nothing. To remove a single data point with known key, use
- \ref removeData(double key).
-
- \see addData, clearData
- */
- void QCPFinancial::removeData(double fromKey, double toKey)
- {
- if (fromKey >= toKey || mData->isEmpty())
- return;
- QCPFinancialDataMap::iterator it = mData->upperBound(fromKey);
- QCPFinancialDataMap::iterator itEnd = mData->upperBound(toKey);
- while (it != itEnd)
- it = mData->erase(it);
- }
-
- /*! \overload
-
- Removes a single data point at \a key. If the position is not known with absolute precision,
- consider using \ref removeData(double fromKey, double toKey) with a small fuzziness interval
- around the suspected position, depeding on the precision with which the key is known.
-
- \see addData, clearData
- */
- void QCPFinancial::removeData(double key)
- {
- mData->remove(key);
- }
-
- /*!
- Removes all data points.
-
- \see removeData, removeDataAfter, removeDataBefore
- */
- void QCPFinancial::clearData()
- {
- mData->clear();
- }
-
- /* inherits documentation from base class */
- double QCPFinancial::selectTest(const QPointF& pos, bool onlySelectable, QVariant* details) const
- {
- Q_UNUSED(details)
- if (onlySelectable && !mSelectable)
- return -1;
- if (!mKeyAxis || !mValueAxis) {
- qDebug() << Q_FUNC_INFO << "invalid key or value axis";
- return -1;
- }
-
- if (mKeyAxis.data()->axisRect()->rect().contains(pos.toPoint())) {
- // get visible data range:
- QCPFinancialDataMap::const_iterator lower,
- upper; // note that upper is the actual upper point, and not 1 step after the upper
- // point
- getVisibleDataBounds(lower, upper);
- if (lower == mData->constEnd() || upper == mData->constEnd())
- return -1;
- // perform select test according to configured style:
- switch (mChartStyle) {
- case QCPFinancial::csOhlc:
- return ohlcSelectTest(pos, lower, upper + 1);
- break;
- case QCPFinancial::csCandlestick:
- return candlestickSelectTest(pos, lower, upper + 1);
- break;
- }
- }
- return -1;
- }
-
- /*!
- 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 setData.
-
- 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.
- */
- QCPFinancialDataMap QCPFinancial::timeSeriesToOhlc(const QVector<double>& time,
- const QVector<double>& value, double timeBinSize,
- double timeBinOffset)
- {
- QCPFinancialDataMap map;
- int count = qMin(time.size(), value.size());
- if (count == 0)
- return QCPFinancialDataMap();
-
- 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;
- map.insert(currentBinData.key, 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;
- map.insert(currentBinData.key, currentBinData);
- // start next bin:
- currentBinIndex = index;
- currentBinData.open = value.at(i);
- currentBinData.high = value.at(i);
- currentBinData.low = value.at(i);
- }
- }
-
- return map;
- }
-
- /* inherits documentation from base class */
- void QCPFinancial::draw(QCPPainter* painter)
- {
- // get visible data range:
- QCPFinancialDataMap::const_iterator lower,
- upper; // note that upper is the actual upper point, and not 1 step after the upper point
- getVisibleDataBounds(lower, upper);
- if (lower == mData->constEnd() || upper == mData->constEnd())
- return;
-
- // draw visible data range according to configured style:
- switch (mChartStyle) {
- case QCPFinancial::csOhlc:
- drawOhlcPlot(painter, lower, upper + 1);
- break;
- case QCPFinancial::csCandlestick:
- drawCandlestickPlot(painter, lower, upper + 1);
- break;
- }
- }
-
- /* 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 hald 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 hald 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()));
- }
- }
- }
-
- /* inherits documentation from base class */
- QCPRange QCPFinancial::getKeyRange(bool& foundRange,
- QCPAbstractPlottable::SignDomain inSignDomain) const
- {
- QCPRange range;
- bool haveLower = false;
- bool haveUpper = false;
-
- double current;
- QCPFinancialDataMap::const_iterator it = mData->constBegin();
- while (it != mData->constEnd()) {
- current = it.value().key;
- if (inSignDomain == sdBoth || (inSignDomain == sdNegative && current < 0)
- || (inSignDomain == sdPositive && current > 0)) {
- if (current < range.lower || !haveLower) {
- range.lower = current;
- haveLower = true;
- }
- if (current > range.upper || !haveUpper) {
- range.upper = current;
- haveUpper = true;
- }
- }
- ++it;
- }
- // determine exact range by including width of bars/flags:
- if (haveLower && mKeyAxis)
- range.lower = range.lower - mWidth * 0.5;
- if (haveUpper && mKeyAxis)
- range.upper = range.upper + mWidth * 0.5;
- foundRange = haveLower && haveUpper;
- return range;
- }
-
- /* inherits documentation from base class */
- QCPRange QCPFinancial::getValueRange(bool& foundRange,
- QCPAbstractPlottable::SignDomain inSignDomain) const
- {
- QCPRange range;
- bool haveLower = false;
- bool haveUpper = false;
-
- QCPFinancialDataMap::const_iterator it = mData->constBegin();
- while (it != mData->constEnd()) {
- // high:
- if (inSignDomain == sdBoth || (inSignDomain == sdNegative && it.value().high < 0)
- || (inSignDomain == sdPositive && it.value().high > 0)) {
- if (it.value().high < range.lower || !haveLower) {
- range.lower = it.value().high;
- haveLower = true;
- }
- if (it.value().high > range.upper || !haveUpper) {
- range.upper = it.value().high;
- haveUpper = true;
- }
- }
- // low:
- if (inSignDomain == sdBoth || (inSignDomain == sdNegative && it.value().low < 0)
- || (inSignDomain == sdPositive && it.value().low > 0)) {
- if (it.value().low < range.lower || !haveLower) {
- range.lower = it.value().low;
- haveLower = true;
- }
- if (it.value().low > range.upper || !haveUpper) {
- range.upper = it.value().low;
- haveUpper = true;
- }
- }
- ++it;
- }
-
- foundRange = haveLower && haveUpper;
- return range;
- }
-
- /*! \internal
-
- Draws the data from \a begin to \a end 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 QCPFinancialDataMap::const_iterator& begin,
- const QCPFinancialDataMap::const_iterator& end)
- {
- QCPAxis* keyAxis = mKeyAxis.data();
- QCPAxis* valueAxis = mValueAxis.data();
- if (!keyAxis || !valueAxis) {
- qDebug() << Q_FUNC_INFO << "invalid key or value axis";
- return;
- }
-
- QPen linePen;
-
- if (keyAxis->orientation() == Qt::Horizontal) {
- for (QCPFinancialDataMap::const_iterator it = begin; it != end; ++it) {
- if (mSelected)
- linePen = mSelectedPen;
- else if (mTwoColored)
- linePen = it.value().close >= it.value().open ? mPenPositive : mPenNegative;
- else
- linePen = mPen;
- painter->setPen(linePen);
- double keyPixel = keyAxis->coordToPixel(it.value().key);
- double openPixel = valueAxis->coordToPixel(it.value().open);
- double closePixel = valueAxis->coordToPixel(it.value().close);
- // draw backbone:
- painter->drawLine(QPointF(keyPixel, valueAxis->coordToPixel(it.value().high)),
- QPointF(keyPixel, valueAxis->coordToPixel(it.value().low)));
- // draw open:
- double keyWidthPixels =
- keyPixel
- - keyAxis->coordToPixel(
- it.value().key
- - mWidth * 0.5); // sign of this makes sure open/close are on correct sides
- painter->drawLine(QPointF(keyPixel - keyWidthPixels, openPixel),
- QPointF(keyPixel, openPixel));
- // draw close:
- painter->drawLine(QPointF(keyPixel, closePixel),
- QPointF(keyPixel + keyWidthPixels, closePixel));
- }
- } else {
- for (QCPFinancialDataMap::const_iterator it = begin; it != end; ++it) {
- if (mSelected)
- linePen = mSelectedPen;
- else if (mTwoColored)
- linePen = it.value().close >= it.value().open ? mPenPositive : mPenNegative;
- else
- linePen = mPen;
- painter->setPen(linePen);
- double keyPixel = keyAxis->coordToPixel(it.value().key);
- double openPixel = valueAxis->coordToPixel(it.value().open);
- double closePixel = valueAxis->coordToPixel(it.value().close);
- // draw backbone:
- painter->drawLine(QPointF(valueAxis->coordToPixel(it.value().high), keyPixel),
- QPointF(valueAxis->coordToPixel(it.value().low), keyPixel));
- // draw open:
- double keyWidthPixels =
- keyPixel
- - keyAxis->coordToPixel(
- it.value().key
- - mWidth * 0.5); // sign of this makes sure open/close are on correct sides
- painter->drawLine(QPointF(openPixel, keyPixel - keyWidthPixels),
- QPointF(openPixel, keyPixel));
- // draw close:
- painter->drawLine(QPointF(closePixel, keyPixel),
- QPointF(closePixel, keyPixel + keyWidthPixels));
- }
- }
- }
-
- /*! \internal
-
- Draws the data from \a begin to \a end 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 QCPFinancialDataMap::const_iterator& begin,
- const QCPFinancialDataMap::const_iterator& end)
- {
- QCPAxis* keyAxis = mKeyAxis.data();
- QCPAxis* valueAxis = mValueAxis.data();
- if (!keyAxis || !valueAxis) {
- qDebug() << Q_FUNC_INFO << "invalid key or value axis";
- return;
- }
-
- QPen linePen;
- QBrush boxBrush;
-
- if (keyAxis->orientation() == Qt::Horizontal) {
- for (QCPFinancialDataMap::const_iterator it = begin; it != end; ++it) {
- if (mSelected) {
- linePen = mSelectedPen;
- boxBrush = mSelectedBrush;
- } else if (mTwoColored) {
- if (it.value().close >= it.value().open) {
- linePen = mPenPositive;
- boxBrush = mBrushPositive;
- } else {
- linePen = mPenNegative;
- boxBrush = mBrushNegative;
- }
- } else {
- linePen = mPen;
- boxBrush = mBrush;
- }
- painter->setPen(linePen);
- painter->setBrush(boxBrush);
- double keyPixel = keyAxis->coordToPixel(it.value().key);
- double openPixel = valueAxis->coordToPixel(it.value().open);
- double closePixel = valueAxis->coordToPixel(it.value().close);
- // draw high:
- painter->drawLine(QPointF(keyPixel, valueAxis->coordToPixel(it.value().high)),
- QPointF(keyPixel, valueAxis->coordToPixel(
- qMax(it.value().open, it.value().close))));
- // draw low:
- painter->drawLine(QPointF(keyPixel, valueAxis->coordToPixel(it.value().low)),
- QPointF(keyPixel, valueAxis->coordToPixel(
- qMin(it.value().open, it.value().close))));
- // draw open-close box:
- double keyWidthPixels = keyPixel - keyAxis->coordToPixel(it.value().key - mWidth * 0.5);
- painter->drawRect(QRectF(QPointF(keyPixel - keyWidthPixels, closePixel),
- QPointF(keyPixel + keyWidthPixels, openPixel)));
- }
- } else // keyAxis->orientation() == Qt::Vertical
- {
- for (QCPFinancialDataMap::const_iterator it = begin; it != end; ++it) {
- if (mSelected) {
- linePen = mSelectedPen;
- boxBrush = mSelectedBrush;
- } else if (mTwoColored) {
- if (it.value().close >= it.value().open) {
- linePen = mPenPositive;
- boxBrush = mBrushPositive;
- } else {
- linePen = mPenNegative;
- boxBrush = mBrushNegative;
- }
- } else {
- linePen = mPen;
- boxBrush = mBrush;
- }
- painter->setPen(linePen);
- painter->setBrush(boxBrush);
- double keyPixel = keyAxis->coordToPixel(it.value().key);
- double openPixel = valueAxis->coordToPixel(it.value().open);
- double closePixel = valueAxis->coordToPixel(it.value().close);
- // draw high:
- painter->drawLine(
- QPointF(valueAxis->coordToPixel(it.value().high), keyPixel),
- QPointF(valueAxis->coordToPixel(qMax(it.value().open, it.value().close)),
- keyPixel));
- // draw low:
- painter->drawLine(
- QPointF(valueAxis->coordToPixel(it.value().low), keyPixel),
- QPointF(valueAxis->coordToPixel(qMin(it.value().open, it.value().close)),
- keyPixel));
- // draw open-close box:
- double keyWidthPixels = keyPixel - keyAxis->coordToPixel(it.value().key - mWidth * 0.5);
- painter->drawRect(QRectF(QPointF(closePixel, keyPixel - keyWidthPixels),
- QPointF(openPixel, keyPixel + keyWidthPixels)));
- }
- }
- }
-
- /*! \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.
- */
- double QCPFinancial::ohlcSelectTest(const QPointF& pos,
- const QCPFinancialDataMap::const_iterator& begin,
- const QCPFinancialDataMap::const_iterator& end) const
- {
- 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();
- QCPFinancialDataMap::const_iterator it;
- if (keyAxis->orientation() == Qt::Horizontal) {
- for (it = begin; it != end; ++it) {
- double keyPixel = keyAxis->coordToPixel(it.value().key);
- // calculate distance to backbone:
- double currentDistSqr =
- distSqrToLine(QPointF(keyPixel, valueAxis->coordToPixel(it.value().high)),
- QPointF(keyPixel, valueAxis->coordToPixel(it.value().low)), pos);
- if (currentDistSqr < minDistSqr)
- minDistSqr = currentDistSqr;
- }
- } else // keyAxis->orientation() == Qt::Vertical
- {
- for (it = begin; it != end; ++it) {
- double keyPixel = keyAxis->coordToPixel(it.value().key);
- // calculate distance to backbone:
- double currentDistSqr =
- distSqrToLine(QPointF(valueAxis->coordToPixel(it.value().high), keyPixel),
- QPointF(valueAxis->coordToPixel(it.value().low), keyPixel), pos);
- if (currentDistSqr < minDistSqr)
- minDistSqr = currentDistSqr;
- }
- }
- 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.
- */
- double QCPFinancial::candlestickSelectTest(const QPointF& pos,
- const QCPFinancialDataMap::const_iterator& begin,
- const QCPFinancialDataMap::const_iterator& end) const
- {
- 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();
- QCPFinancialDataMap::const_iterator it;
- if (keyAxis->orientation() == Qt::Horizontal) {
- for (it = begin; it != end; ++it) {
- double currentDistSqr;
- // determine whether pos is in open-close-box:
- QCPRange boxKeyRange(it.value().key - mWidth * 0.5, it.value().key + mWidth * 0.5);
- QCPRange boxValueRange(it.value().close, it.value().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.value().key);
- double highLineDistSqr = distSqrToLine(
- QPointF(keyPixel, valueAxis->coordToPixel(it.value().high)),
- QPointF(keyPixel,
- valueAxis->coordToPixel(qMax(it.value().open, it.value().close))),
- pos);
- double lowLineDistSqr = distSqrToLine(
- QPointF(keyPixel, valueAxis->coordToPixel(it.value().low)),
- QPointF(keyPixel,
- valueAxis->coordToPixel(qMin(it.value().open, it.value().close))),
- pos);
- currentDistSqr = qMin(highLineDistSqr, lowLineDistSqr);
- }
- if (currentDistSqr < minDistSqr)
- minDistSqr = currentDistSqr;
- }
- } else // keyAxis->orientation() == Qt::Vertical
- {
- for (it = begin; it != end; ++it) {
- double currentDistSqr;
- // determine whether pos is in open-close-box:
- QCPRange boxKeyRange(it.value().key - mWidth * 0.5, it.value().key + mWidth * 0.5);
- QCPRange boxValueRange(it.value().close, it.value().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.value().key);
- double highLineDistSqr = distSqrToLine(
- QPointF(valueAxis->coordToPixel(it.value().high), keyPixel),
- QPointF(valueAxis->coordToPixel(qMax(it.value().open, it.value().close)),
- keyPixel),
- pos);
- double lowLineDistSqr = distSqrToLine(
- QPointF(valueAxis->coordToPixel(it.value().low), keyPixel),
- QPointF(valueAxis->coordToPixel(qMin(it.value().open, it.value().close)),
- keyPixel),
- pos);
- currentDistSqr = qMin(highLineDistSqr, lowLineDistSqr);
- }
- if (currentDistSqr < minDistSqr)
- minDistSqr = currentDistSqr;
- }
- }
- 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 lower 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 upper returns an iterator to the highest data point. Same as before, \a upper may also lie
- just outside of the visible range.
-
- if the plottable contains no data, both \a lower and \a upper point to constEnd.
-
- \see QCPGraph::getVisibleDataBounds
- */
- void QCPFinancial::getVisibleDataBounds(QCPFinancialDataMap::const_iterator& lower,
- QCPFinancialDataMap::const_iterator& upper) const
- {
- if (!mKeyAxis) {
- qDebug() << Q_FUNC_INFO << "invalid key axis";
- return;
- }
- if (mData->isEmpty()) {
- lower = mData->constEnd();
- upper = mData->constEnd();
- return;
- }
-
- // get visible data range as QMap iterators
- QCPFinancialDataMap::const_iterator lbound = mData->lowerBound(mKeyAxis.data()->range().lower);
- QCPFinancialDataMap::const_iterator ubound = mData->upperBound(mKeyAxis.data()->range().upper);
- bool lowoutlier =
- lbound != mData->constBegin(); // indicates whether there exist points below axis range
- bool highoutlier =
- ubound != mData->constEnd(); // indicates whether there exist points above axis range
-
- lower = (lowoutlier ? lbound - 1 : lbound); // data point range that will be actually drawn
- upper = (highoutlier ? ubound : ubound - 1); // data point range that will be actually drawn
- }
-
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// 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 constructed item can be added to the plot with QCustomPlot::addItem.
- */
- 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 distToStraightLine(QVector2D(point1->pixelPoint()),
- QVector2D(point2->pixelPoint() - point1->pixelPoint()),
- QVector2D(pos));
- }
-
- /* inherits documentation from base class */
- void QCPItemStraightLine::draw(QCPPainter* painter)
- {
- QVector2D start(point1->pixelPoint());
- QVector2D end(point2->pixelPoint());
- // 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
-
- finds the shortest distance of \a point to the straight line defined by the base point \a
- base and the direction vector \a vec.
-
- This is a helper function for \ref selectTest.
- */
- double QCPItemStraightLine::distToStraightLine(const QVector2D& base, const QVector2D& vec,
- const QVector2D& point) const
- {
- return qAbs((base.y() - point.y()) * vec.x() - (base.x() - point.x()) * vec.y()) / vec.length();
- }
-
- /*! \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 QVector2D& base, const QVector2D& 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<QVector2D> 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(QVector2D(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(QVector2D(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(QVector2D(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(QVector2D(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;
- QVector2D 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;
- }
-
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// 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 constructed item can be added to the plot with QCustomPlot::addItem.
- */
- 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(distSqrToLine(start->pixelPoint(), end->pixelPoint(), pos));
- }
-
- /* inherits documentation from base class */
- void QCPItemLine::draw(QCPPainter* painter)
- {
- QVector2D startVec(start->pixelPoint());
- QVector2D endVec(end->pixelPoint());
- if (startVec.toPoint() == endVec.toPoint())
- 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 QVector2D& start, const QVector2D& 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());
-
- QVector2D base = start;
- QVector2D vec = end - start;
- double bx, by;
- double gamma, mu;
- QLineF result;
- QList<QVector2D> 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(QVector2D(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(QVector2D(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(QVector2D(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(QVector2D(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;
- QVector2D 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;
- }
-
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// 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 constructed item can be added to the plot with QCustomPlot::addItem.
- */
- 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->pixelPoint());
- QPointF startDirVec(startDir->pixelPoint());
- QPointF endDirVec(endDir->pixelPoint());
- QPointF endVec(end->pixelPoint());
-
- QPainterPath cubicPath(startVec);
- cubicPath.cubicTo(startDirVec, endDirVec, endVec);
-
- QPolygonF polygon = cubicPath.toSubpathPolygons().first();
- double minDistSqr = std::numeric_limits<double>::max();
- for (int i = 1; i < polygon.size(); ++i) {
- double distSqr = distSqrToLine(polygon.at(i - 1), polygon.at(i), pos);
- if (distSqr < minDistSqr)
- minDistSqr = distSqr;
- }
- return qSqrt(minDistSqr);
- }
-
- /* inherits documentation from base class */
- void QCPItemCurve::draw(QCPPainter* painter)
- {
- QPointF startVec(start->pixelPoint());
- QPointF startDirVec(startDir->pixelPoint());
- QPointF endDirVec(endDir->pixelPoint());
- QPointF endVec(end->pixelPoint());
- if (QVector2D(endVec - startVec).length() > 1e10f) // too large curves cause crash
- return;
-
- QPainterPath cubicPath(startVec);
- cubicPath.cubicTo(startDirVec, endDirVec, endVec);
-
- // 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, QVector2D(startVec),
- M_PI - cubicPath.angleAtPercent(0) / 180.0 * M_PI);
- if (mHead.style() != QCPLineEnding::esNone)
- mHead.draw(painter, QVector2D(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;
- }
-
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// 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 constructed item can be added to the plot with QCustomPlot::addItem.
- */
- 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->pixelPoint(), bottomRight->pixelPoint()).normalized();
- bool filledRect = mBrush.style() != Qt::NoBrush && mBrush.color().alpha() != 0;
- return rectSelectTest(rect, pos, filledRect);
- }
-
- /* inherits documentation from base class */
- void QCPItemRect::draw(QCPPainter* painter)
- {
- QPointF p1 = topLeft->pixelPoint();
- QPointF p2 = bottomRight->pixelPoint();
- 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::anchorPixelPoint(int anchorId) const
- {
- QRectF rect = QRectF(topLeft->pixelPoint(), bottomRight->pixelPoint());
- 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;
- }
-
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// 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 constructed item can be added to the plot with QCustomPlot::addItem.
- */
- 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))
- {
- position->setCoords(0, 0);
-
- setRotation(0);
- setTextAlignment(Qt::AlignTop | Qt::AlignHCenter);
- setPositionAlignment(Qt::AlignCenter);
- setText(QLatin1String("text"));
-
- 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 rectSelectTest function for non-rotated rects:
- QPointF positionPixels(position->pixelPoint());
- 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 rectSelectTest(textBoxRect, rotatedPos, true);
- }
-
- /* inherits documentation from base class */
- void QCPItemText::draw(QCPPainter* painter)
- {
- QPointF pos(position->pixelPoint());
- 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::anchorPixelPoint(int anchorId) const
- {
- // get actual rect points (pretty much copied from draw function):
- QPointF pos(position->pixelPoint());
- 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;
- }
-
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// 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 constructed item can be added to the plot with QCustomPlot::addItem.
- */
- 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;
-
- double result = -1;
- QPointF p1 = topLeft->pixelPoint();
- QPointF p2 = bottomRight->pixelPoint();
- 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));
- 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->pixelPoint();
- QPointF p2 = bottomRight->pixelPoint();
- 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::anchorPixelPoint(int anchorId) const
- {
- QRectF rect = QRectF(topLeft->pixelPoint(), bottomRight->pixelPoint());
- 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;
- }
-
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// 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 constructed item can be added to the plot with QCustomPlot::addItem.
- */
- 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))
- , mScaledPixmapInvalidated(true)
- {
- topLeft->setCoords(0, 1);
- bottomRight->setCoords(1, 0);
-
- setPen(Qt::NoPen);
- setSelectedPen(QPen(Qt::blue));
- setScaled(false, Qt::KeepAspectRatio, Qt::SmoothTransformation);
- }
-
- 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 rectSelectTest(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::anchorPixelPoint(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) {
- if (finalRect.isNull())
- finalRect = getFinalRect(&flipHorz, &flipVert);
- if (mScaledPixmapInvalidated || finalRect.size() != mScaledPixmap.size()) {
- mScaledPixmap = mPixmap.scaled(finalRect.size(), mAspectRatioMode, mTransformationMode);
- if (flipHorz || flipVert)
- mScaledPixmap =
- QPixmap::fromImage(mScaledPixmap.toImage().mirrored(flipHorz, flipVert));
- }
- } 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->pixelPoint().toPoint();
- QPoint p2 = bottomRight->pixelPoint().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();
- scaledSize.scale(newSize, mAspectRatioMode);
- result = QRect(topLeft, scaledSize);
- } else {
- result = QRect(p1, mPixmap.size());
- }
- 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;
- }
-
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// 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 a 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 constructed item can be added to the plot with QCustomPlot::addItem.
- */
- QCPItemTracer::QCPItemTracer(QCustomPlot* parentPlot)
- : QCPAbstractItem(parentPlot), position(createPosition(QLatin1String("position"))), mGraph(0)
- {
- position->setCoords(0, 0);
-
- setBrush(Qt::NoBrush);
- setSelectedBrush(Qt::NoBrush);
- setPen(QPen(Qt::black));
- setSelectedPen(QPen(Qt::blue, 2));
- setStyle(tsCrosshair);
- setSize(6);
- setInterpolating(false);
- setGraphKey(0);
- }
-
- 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->pixelPoint());
- 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(distSqrToLine(center + QPointF(-w, 0), center + QPointF(w, 0), pos),
- distSqrToLine(center + QPointF(0, -w), center + QPointF(0, w), pos)));
- break;
- }
- case tsCrosshair: {
- return qSqrt(qMin(
- distSqrToLine(QPointF(clip.left(), center.y()), QPointF(clip.right(), center.y()), pos),
- distSqrToLine(QPointF(center.x(), clip.top()), QPointF(center.x(), clip.bottom()),
- pos)));
- }
- case tsCircle: {
- if (clip.intersects(QRectF(center - QPointF(w, w), center + QPointF(w, w)).toRect())) {
- // distance to border:
- double centerDist = QVector2D(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 rectSelectTest(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->pixelPoint());
- 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) {
- QCPDataMap::const_iterator first = mGraph->data()->constBegin();
- QCPDataMap::const_iterator last = mGraph->data()->constEnd() - 1;
- if (mGraphKey < first.key())
- position->setCoords(first.key(), first.value().value);
- else if (mGraphKey > last.key())
- position->setCoords(last.key(), last.value().value);
- else {
- QCPDataMap::const_iterator it = mGraph->data()->lowerBound(mGraphKey);
- if (it != first) // mGraphKey is somewhere between iterators
- {
- QCPDataMap::const_iterator prevIt = it - 1;
- if (mInterpolating) {
- // interpolate between iterators around mGraphKey:
- double slope = 0;
- if (!qFuzzyCompare((double)it.key(), (double)prevIt.key()))
- slope = (it.value().value - prevIt.value().value)
- / (it.key() - prevIt.key());
- position->setCoords(mGraphKey, (mGraphKey - prevIt.key()) * slope
- + prevIt.value().value);
- } else {
- // find iterator with key closest to mGraphKey:
- if (mGraphKey < (prevIt.key() + it.key()) * 0.5)
- it = prevIt;
- position->setCoords(it.key(), it.value().value);
- }
- } else // mGraphKey is exactly on first iterator
- position->setCoords(it.key(), it.value().value);
- }
- } else if (mGraph->data()->size() == 1) {
- QCPDataMap::const_iterator it = mGraph->data()->constBegin();
- position->setCoords(it.key(), it.value().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;
- }
-
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// 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 constructed item can be added to the plot with QCustomPlot::addItem.
- */
- QCPItemBracket::QCPItemBracket(QCustomPlot* parentPlot)
- : QCPAbstractItem(parentPlot)
- , left(createPosition(QLatin1String("left")))
- , right(createPosition(QLatin1String("right")))
- , center(createAnchor(QLatin1String("center"), aiCenter))
- {
- left->setCoords(0, 0);
- right->setCoords(1, 1);
-
- setPen(QPen(Qt::black));
- setSelectedPen(QPen(Qt::blue, 2));
- setLength(8);
- setStyle(bsCalligraphic);
- }
-
- 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;
-
- QVector2D leftVec(left->pixelPoint());
- QVector2D rightVec(right->pixelPoint());
- if (leftVec.toPoint() == rightVec.toPoint())
- return -1;
-
- QVector2D widthVec = (rightVec - leftVec) * 0.5f;
- QVector2D lengthVec(-widthVec.y(), widthVec.x());
- lengthVec = lengthVec.normalized() * mLength;
- QVector2D centerVec = (rightVec + leftVec) * 0.5f - lengthVec;
-
- switch (mStyle) {
- case QCPItemBracket::bsSquare:
- case QCPItemBracket::bsRound: {
- double a = distSqrToLine((centerVec - widthVec).toPointF(),
- (centerVec + widthVec).toPointF(), pos);
- double b = distSqrToLine((centerVec - widthVec + lengthVec).toPointF(),
- (centerVec - widthVec).toPointF(), pos);
- double c = distSqrToLine((centerVec + widthVec + lengthVec).toPointF(),
- (centerVec + widthVec).toPointF(), pos);
- return qSqrt(qMin(qMin(a, b), c));
- }
- case QCPItemBracket::bsCurly:
- case QCPItemBracket::bsCalligraphic: {
- double a = distSqrToLine((centerVec - widthVec * 0.75f + lengthVec * 0.15f).toPointF(),
- (centerVec + lengthVec * 0.3f).toPointF(), pos);
- double b =
- distSqrToLine((centerVec - widthVec + lengthVec * 0.7f).toPointF(),
- (centerVec - widthVec * 0.75f + lengthVec * 0.15f).toPointF(), pos);
- double c = distSqrToLine((centerVec + widthVec * 0.75f + lengthVec * 0.15f).toPointF(),
- (centerVec + lengthVec * 0.3f).toPointF(), pos);
- double d =
- distSqrToLine((centerVec + widthVec + lengthVec * 0.7f).toPointF(),
- (centerVec + widthVec * 0.75f + lengthVec * 0.15f).toPointF(), pos);
- return qSqrt(qMin(qMin(a, b), qMin(c, d)));
- }
- }
- return -1;
- }
-
- /* inherits documentation from base class */
- void QCPItemBracket::draw(QCPPainter* painter)
- {
- QVector2D leftVec(left->pixelPoint());
- QVector2D rightVec(right->pixelPoint());
- if (leftVec.toPoint() == rightVec.toPoint())
- return;
-
- QVector2D widthVec = (rightVec - leftVec) * 0.5f;
- QVector2D lengthVec(-widthVec.y(), widthVec.x());
- lengthVec = lengthVec.normalized() * mLength;
- QVector2D centerVec = (rightVec + leftVec) * 0.5f - 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.8f).toPointF(),
- (centerVec + 0.4f * widthVec + lengthVec).toPointF(),
- centerVec.toPointF());
- path.cubicTo((centerVec - 0.4f * widthVec + lengthVec).toPointF(),
- (centerVec - widthVec - lengthVec * 0.8f).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.8f).toPointF(),
- (centerVec + 0.4f * widthVec + 0.8f * lengthVec).toPointF(),
- centerVec.toPointF());
- path.cubicTo((centerVec - 0.4f * widthVec + 0.8f * lengthVec).toPointF(),
- (centerVec - widthVec - lengthVec * 0.8f).toPointF(),
- (centerVec - widthVec + lengthVec).toPointF());
-
- path.cubicTo((centerVec - widthVec - lengthVec * 0.5f).toPointF(),
- (centerVec - 0.2f * widthVec + 1.2f * lengthVec).toPointF(),
- (centerVec + lengthVec * 0.2f).toPointF());
- path.cubicTo((centerVec + 0.2f * widthVec + 1.2f * lengthVec).toPointF(),
- (centerVec + widthVec - lengthVec * 0.5f).toPointF(),
- (centerVec + widthVec + lengthVec).toPointF());
-
- painter->drawPath(path);
- break;
- }
- }
- }
- }
-
- /* inherits documentation from base class */
- QPointF QCPItemBracket::anchorPixelPoint(int anchorId) const
- {
- QVector2D leftVec(left->pixelPoint());
- QVector2D rightVec(right->pixelPoint());
- if (leftVec.toPoint() == rightVec.toPoint())
- return leftVec.toPointF();
-
- QVector2D widthVec = (rightVec - leftVec) * 0.5f;
- QVector2D lengthVec(-widthVec.y(), widthVec.x());
- lengthVec = lengthVec.normalized() * mLength;
- QVector2D centerVec = (rightVec + leftVec) * 0.5f - 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;
- }
|