/**************************************************************************** ** ** Copyright (C) 2017 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Charts module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:GPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 or (at your option) any later version ** approved by the KDE Free Qt Foundation. The licenses are as published by ** the Free Software Foundation and appearing in the file LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include #include #include #include #include #include #include QT_CHARTS_BEGIN_NAMESPACE /*! \class QXYSeries \inmodule QtCharts \brief The QXYSeries class is a base class for line, spline, and scatter series. */ /*! \qmltype XYSeries \instantiates QXYSeries \inqmlmodule QtCharts \inherits AbstractSeries \brief A base type for line, spline, and scatter series. */ /*! \qmlproperty AbstractAxis XYSeries::axisX The x-axis used for the series. If you leave both axisX and axisXTop undefined, a value axis is created for the series. \sa axisXTop, ValueAxis */ /*! \qmlproperty AbstractAxis XYSeries::axisY The y-axis used for the series. If you leave both axisY and axisYRight undefined, a value axis is created for the series. \sa axisYRight, ValueAxis */ /*! \qmlproperty AbstractAxis XYSeries::axisXTop The x-axis used for the series, drawn on top of the chart view. \note You can only provide either axisX or axisXTop, not both. \sa axisX */ /*! \qmlproperty AbstractAxis XYSeries::axisYRight The y-axis used for the series, drawn to the right on the chart view. \note You can only provide either axisY or axisYRight, not both. \sa axisY */ /*! \qmlproperty AbstractAxis XYSeries::axisAngular The angular axis used for the series, drawn around the polar chart view. \sa axisX */ /*! \qmlproperty AbstractAxis XYSeries::axisRadial The radial axis used for the series, drawn inside the polar chart view. \sa axisY */ /*! \property QXYSeries::pointsVisible \brief Whether the data points are visible and should be drawn. */ /*! \qmlproperty bool XYSeries::pointsVisible Whether the data points are visible and should be drawn. */ /*! \fn QPen QXYSeries::pen() const Returns the pen used to draw the outline of the data points for the series. \sa setPen() */ /*! \fn QBrush QXYSeries::brush() const Returns the brush used to fill the data points for the series. \sa setBrush() */ /*! \property QXYSeries::color \brief The color of the series. This is the line (pen) color in case of QLineSeries or QSplineSeries and the fill (brush) color in case of QScatterSeries or QAreaSeries. \sa pen(), brush() */ /*! \qmlproperty color XYSeries::color The color of the series. This is the line (pen) color in case of LineSeries or SplineSeries and the fill (brush) color in case of ScatterSeries or AreaSeries. */ /*! \property QXYSeries::pointLabelsFormat \brief The format used for showing labels with data points. QXYSeries supports the following format tags: \table \row \li @xPoint \li The x-coordinate of the data point. \row \li @yPoint \li The y-coordinate of the data point. \endtable For example, the following usage of the format tags would produce labels that display the data point shown inside brackets separated by a comma (x, y): \code series->setPointLabelsFormat("(@xPoint, @yPoint)"); \endcode By default, the labels' format is set to \c {@xPoint, @yPoint}. The labels are shown on the plot area, and the labels on the edge of the plot area are cut. If the points are close to each other, the labels may overlap. \sa pointLabelsVisible, pointLabelsFont, pointLabelsColor */ /*! \qmlproperty string XYSeries::pointLabelsFormat The format used for showing labels with data points. \sa pointLabelsVisible, pointLabelsFont, pointLabelsColor */ /*! \fn void QXYSeries::pointLabelsFormatChanged(const QString &format) This signal is emitted when the format of data point labels changes to \a format. */ /*! \property QXYSeries::pointLabelsVisible \brief The visibility of data point labels. This property is \c false by default. \sa pointLabelsFormat, pointLabelsClipping */ /*! \qmlproperty bool XYSeries::pointLabelsVisible The visibility of data point labels. This property is \c false by default. \sa pointLabelsFormat, pointLabelsClipping */ /*! \fn void QXYSeries::pointLabelsVisibilityChanged(bool visible) This signal is emitted when the visibility of the data point labels changes to \a visible. */ /*! \property QXYSeries::pointLabelsFont \brief The font used for data point labels. \sa pointLabelsFormat */ /*! \qmlproperty font XYSeries::pointLabelsFont The font used for data point labels. \sa pointLabelsFormat */ /*! \fn void QXYSeries::pointLabelsFontChanged(const QFont &font); This signal is emitted when the font used for data point labels changes to \a font. */ /*! \property QXYSeries::pointLabelsColor \brief The color used for data point labels. By default, the color is the color of the brush defined in theme for labels. \sa pointLabelsFormat */ /*! \qmlproperty font XYSeries::pointLabelsColor The color used for data point labels. By default, the color is the color of the brush defined in theme for labels. \sa pointLabelsFormat */ /*! \fn void QXYSeries::pointLabelsColorChanged(const QColor &color); This signal is emitted when the color used for data point labels changes to \a color. */ /*! \property QXYSeries::pointLabelsClipping \brief The clipping for data point labels. This property is \c true by default. The labels on the edge of the plot area are cut when clipping is enabled. \sa pointLabelsVisible */ /*! \qmlproperty bool XYSeries::pointLabelsClipping The clipping for data point labels. This property is \c true by default. The labels on the edge of the plot area are cut when clipping is enabled. \sa pointLabelsVisible */ /*! \fn void QXYSeries::pointLabelsClippingChanged(bool clipping) This signal is emitted when the clipping of the data point labels changes to \a clipping. */ /*! \fn void QXYSeries::clicked(const QPointF& point) This signal is emitted when the user triggers a mouse event by clicking the point \a point in the chart. \sa pressed(), released(), doubleClicked() */ /*! \qmlsignal XYSeries::clicked(point point) This signal is emitted when the user triggers a mouse event by clicking the point \a point in the chart. For example: \code LineSeries { XYPoint { x: 0; y: 0 } XYPoint { x: 1.1; y: 2.1 } onClicked: console.log("onClicked: " + point.x + ", " + point.y); } \endcode The corresponding signal handler is \c onClicked(). \sa pressed(), released(), doubleClicked() */ /*! \fn void QXYSeries::hovered(const QPointF &point, bool state) This signal is emitted when a mouse is hovered over the point \a point in the chart. When the mouse moves over the point, \a state turns \c true, and when the mouse moves away again, it turns \c false. */ /*! \qmlsignal XYSeries::hovered(point point, bool state) This signal is emitted when a mouse is hovered over the point \a point in the chart. When the mouse moves over the point, \a state turns \c true, and when the mouse moves away again, it turns \c false. The corresponding signal handler is \c onHovered(). */ /*! \fn void QXYSeries::pressed(const QPointF& point) This signal is emitted when the user presses the data point \a point in the chart and holds down the mouse button. \sa clicked(), released(), doubleClicked() */ /*! \qmlsignal XYSeries::pressed(point point) This signal is emitted when the user presses the data point \a point in the chart and holds down the mouse button. For example: \code LineSeries { XYPoint { x: 0; y: 0 } XYPoint { x: 1.1; y: 2.1 } onPressed: console.log("onPressed: " + point.x + ", " + point.y); } \endcode The corresponding signal handler is \c onPressed(). \sa clicked(), released(), doubleClicked() */ /*! \fn void QXYSeries::released(const QPointF& point) This signal is emitted when the user releases the mouse press on the data point specified by \a point. \sa pressed(), clicked(), doubleClicked() */ /*! \qmlsignal XYSeries::released(point point) This signal is emitted when the user releases the mouse press on the data point specified by \a point. For example: \code LineSeries { XYPoint { x: 0; y: 0 } XYPoint { x: 1.1; y: 2.1 } onReleased: console.log("onReleased: " + point.x + ", " + point.y); } \endcode The corresponding signal handler is \c onReleased(). \sa pressed(), clicked(), doubleClicked() */ /*! \fn void QXYSeries::doubleClicked(const QPointF& point) This signal is emitted when the user double-clicks the data point \a point in the chart. The \a point is the point where the first press was triggered. \sa pressed(), released(), clicked() */ /*! \qmlsignal XYSeries::doubleClicked(point point) This signal is emitted when the user double-clicks the data point \a point in the chart. The \a point is the point where the first press was triggered. For example: \code LineSeries { XYPoint { x: 0; y: 0 } XYPoint { x: 1.1; y: 2.1 } onDoubleClicked: console.log("onDoubleClicked: " + point.x + ", " + point.y); } \endcode The corresponding signal handler is \c onDoubleClicked(). \sa pressed(), released(), clicked() */ /*! \fn void QXYSeries::pointReplaced(int index) This signal is emitted when a point is replaced at the position specified by \a index. \sa replace() */ /*! \qmlsignal XYSeries::pointReplaced(int index) This signal is emitted when a point is replaced at the position specified by \a index. The corresponding signal handler is \c onPointReplaced(). */ /*! \fn void QXYSeries::pointsReplaced() This signal is emitted when all points are replaced with other points. \sa replace() */ /*! \qmlsignal XYSeries::pointsReplaced() This signal is emitted when all points are replaced with other points. The corresponding signal handler is \c onPointsReplaced(). */ /*! \fn void QXYSeries::pointAdded(int index) This signal is emitted when a point is added at the position specified by \a index. \sa append(), insert() */ /*! \qmlsignal XYSeries::pointAdded(int index) This signal is emitted when a point is added at the position specified by \a index. The corresponding signal handler is \c onPointAdded(). */ /*! \fn void QXYSeries::pointRemoved(int index) This signal is emitted when a point is removed from the position specified by \a index. \sa remove() */ /*! \qmlsignal XYSeries::pointRemoved(int index) This signal is emitted when a point is removed from the position specified by \a index. The corresponding signal handler is \c onPointRemoved(). */ /*! \fn void QXYSeries::pointsRemoved(int index, int count) This signal is emitted when the number of points specified by \a count is removed starting at the position specified by \a index. \sa removePoints(), clear() */ /*! \qmlsignal XYSeries::pointsRemoved(int index, int count) This signal is emitted when the number of points specified by \a count is removed starting at the position specified by \a index. The corresponding signal handler is \c onPointRemoved(). */ /*! \fn void QXYSeries::colorChanged(QColor color) This signal is emitted when the line (pen) color changes to \a color. */ /*! \fn void QXYSeries::penChanged(const QPen &pen) This signal is emitted when the pen changes to \a pen. */ /*! \fn void QXYSeriesPrivate::updated() \internal */ /*! \qmlmethod XYSeries::append(real x, real y) Appends a point with the coordinates \a x and \a y to the series. */ /*! \qmlmethod XYSeries::replace(real oldX, real oldY, real newX, real newY) Replaces the point with the coordinates \a oldX and \a oldY with the point with the coordinates \a newX and \a newY. Does nothing if the old point does not exist. */ /*! \qmlmethod XYSeries::remove(real x, real y) Removes the point with the coordinates \a x and \a y from the series. Does nothing if the point does not exist. */ /*! \qmlmethod XYSeries::remove(int index) Removes the point at the position specified by \a index from the series. */ /*! \qmlmethod XYSeries::removePoints(int index, int count) Removes the number of points specified by \a count from the series starting at the position specified by \a index. */ /*! \qmlmethod XYSeries::insert(int index, real x, real y) Inserts a point with the coordinates \a x and \a y to the position specified by \a index in the series. If the index is 0 or less than 0, the point is prepended to the list of points. If the index is equal to or greater than than the number of points in the series, the point is appended to the list of points. */ /*! \qmlmethod QPointF XYSeries::at(int index) Returns the point at the position specified by \a index. Returns (0, 0) if the index is not valid. */ /*! \internal Constructs an empty series object that is a child of \a parent. When the series object is added to QChart, instance ownerships is transferred. */ QXYSeries::QXYSeries(QXYSeriesPrivate &d, QObject *parent) : QAbstractSeries(d, parent) { } /*! Deletes the series. Series added to QChart instances are owned by them, and are deleted when the QChart instances are deleted. */ QXYSeries::~QXYSeries() { } /*! Adds the data point with the coordinates \a x and \a y to the series. */ void QXYSeries::append(qreal x, qreal y) { append(QPointF(x, y)); } /*! \overload Adds the data point \a point to the series. */ void QXYSeries::append(const QPointF &point) { Q_D(QXYSeries); if (isValidValue(point)) { d->m_points << point; emit pointAdded(d->m_points.count() - 1); } } /*! \overload Adds the list of data points specified by \a points to the series. */ void QXYSeries::append(const QList &points) { foreach (const QPointF &point , points) append(point); } /*! Replaces the point with the coordinates \a oldX and \a oldY with the point with the coordinates \a newX and \a newY. Does nothing if the old point does not exist. \sa pointReplaced() */ void QXYSeries::replace(qreal oldX, qreal oldY, qreal newX, qreal newY) { replace(QPointF(oldX, oldY), QPointF(newX, newY)); } /*! Replaces the point specified by \a oldPoint with the one specified by \a newPoint. \sa pointReplaced() */ void QXYSeries::replace(const QPointF &oldPoint, const QPointF &newPoint) { Q_D(QXYSeries); int index = d->m_points.indexOf(oldPoint); if (index == -1) return; replace(index, newPoint); } /*! Replaces the point at the position specified by \a index with the point that has the coordinates \a newX and \a newY. \sa pointReplaced() */ void QXYSeries::replace(int index, qreal newX, qreal newY) { replace(index, QPointF(newX, newY)); } /*! Replaces the point at the position specified by \a index with the point specified by \a newPoint. \sa pointReplaced() */ void QXYSeries::replace(int index, const QPointF &newPoint) { Q_D(QXYSeries); if (isValidValue(newPoint)) { d->m_points[index] = newPoint; emit pointReplaced(index); } } /*! Replaces the current points with the points specified by \a points. \note This is much faster than replacing data points one by one, or first clearing all data, and then appending the new data. Emits QXYSeries::pointsReplaced() when the points have been replaced. However, note that using the overload that takes \c{QVector} as parameter is faster than using this overload. \sa pointsReplaced() */ void QXYSeries::replace(QList points) { replace(points.toVector()); } /*! Replaces the current points with the points specified by \a points. \note This is much faster than replacing data points one by one, or first clearing all data, and then appending the new data. Emits QXYSeries::pointsReplaced() when the points have been replaced. \sa pointsReplaced() */ void QXYSeries::replace(QVector points) { Q_D(QXYSeries); d->m_points = points; emit pointsReplaced(); } /*! Removes the point that has the coordinates \a x and \a y from the series. \sa pointRemoved() */ void QXYSeries::remove(qreal x, qreal y) { remove(QPointF(x, y)); } /*! Removes the data point \a point from the series. \sa pointRemoved() */ void QXYSeries::remove(const QPointF &point) { Q_D(QXYSeries); int index = d->m_points.indexOf(point); if (index == -1) return; remove(index); } /*! Removes the point at the position specified by \a index from the series. \sa pointRemoved() */ void QXYSeries::remove(int index) { Q_D(QXYSeries); d->m_points.remove(index); emit pointRemoved(index); } /*! Removes the number of points specified by \a count from the series starting at the position specified by \a index. \sa pointsRemoved() */ void QXYSeries::removePoints(int index, int count) { // This function doesn't overload remove as there is chance for it to get mixed up with // remove(qreal, qreal) overload in some implicit casting cases. Q_D(QXYSeries); if (count > 0) { d->m_points.remove(index, count); emit pointsRemoved(index, count); } } /*! Inserts the data point \a point in the series at the position specified by \a index. \sa pointAdded() */ void QXYSeries::insert(int index, const QPointF &point) { Q_D(QXYSeries); if (isValidValue(point)) { index = qMax(0, qMin(index, d->m_points.size())); d->m_points.insert(index, point); emit pointAdded(index); } } /*! Removes all points from the series. \sa pointsRemoved() */ void QXYSeries::clear() { Q_D(QXYSeries); removePoints(0, d->m_points.size()); } /*! Returns the points in the series as a list. Use pointsVector() for better performance. */ QList QXYSeries::points() const { Q_D(const QXYSeries); return d->m_points.toList(); } /*! Returns the points in the series as a vector. This is more efficient than calling points(). */ QVector QXYSeries::pointsVector() const { Q_D(const QXYSeries); return d->m_points; } /*! Returns the data point at the position specified by \a index in the internal points vector. */ const QPointF &QXYSeries::at(int index) const { Q_D(const QXYSeries); return d->m_points.at(index); } /*! Returns the number of data points in a series. */ int QXYSeries::count() const { Q_D(const QXYSeries); return d->m_points.count(); } /*! Sets the pen used for drawing points on the chart to \a pen. If the pen is not defined, the pen from the chart theme is used. \sa QChart::setTheme() */ void QXYSeries::setPen(const QPen &pen) { Q_D(QXYSeries); if (d->m_pen != pen) { bool emitColorChanged = d->m_pen.color() != pen.color(); d->m_pen = pen; emit d->updated(); if (emitColorChanged) emit colorChanged(pen.color()); emit penChanged(pen); } } QPen QXYSeries::pen() const { Q_D(const QXYSeries); if (d->m_pen == QChartPrivate::defaultPen()) return QPen(); else return d->m_pen; } /*! Sets the brush used for drawing points on the chart to \a brush. If the brush is not defined, the brush from the chart theme setting is used. \sa QChart::setTheme() */ void QXYSeries::setBrush(const QBrush &brush) { Q_D(QXYSeries); if (d->m_brush != brush) { d->m_brush = brush; emit d->updated(); } } QBrush QXYSeries::brush() const { Q_D(const QXYSeries); if (d->m_brush == QChartPrivate::defaultBrush()) return QBrush(); else return d->m_brush; } void QXYSeries::setColor(const QColor &color) { QPen p = pen(); if (p.color() != color) { p.setColor(color); setPen(p); } } QColor QXYSeries::color() const { return pen().color(); } void QXYSeries::setPointsVisible(bool visible) { Q_D(QXYSeries); if (d->m_pointsVisible != visible) { d->m_pointsVisible = visible; emit d->updated(); } } bool QXYSeries::pointsVisible() const { Q_D(const QXYSeries); return d->m_pointsVisible; } void QXYSeries::setPointLabelsFormat(const QString &format) { Q_D(QXYSeries); if (d->m_pointLabelsFormat != format) { d->m_pointLabelsFormat = format; emit pointLabelsFormatChanged(format); } } QString QXYSeries::pointLabelsFormat() const { Q_D(const QXYSeries); return d->m_pointLabelsFormat; } void QXYSeries::setPointLabelsVisible(bool visible) { Q_D(QXYSeries); if (d->m_pointLabelsVisible != visible) { d->m_pointLabelsVisible = visible; emit pointLabelsVisibilityChanged(visible); } } bool QXYSeries::pointLabelsVisible() const { Q_D(const QXYSeries); return d->m_pointLabelsVisible; } void QXYSeries::setPointLabelsFont(const QFont &font) { Q_D(QXYSeries); if (d->m_pointLabelsFont != font) { d->m_pointLabelsFont = font; emit pointLabelsFontChanged(font); } } QFont QXYSeries::pointLabelsFont() const { Q_D(const QXYSeries); return d->m_pointLabelsFont; } void QXYSeries::setPointLabelsColor(const QColor &color) { Q_D(QXYSeries); if (d->m_pointLabelsColor != color) { d->m_pointLabelsColor = color; emit pointLabelsColorChanged(color); } } QColor QXYSeries::pointLabelsColor() const { Q_D(const QXYSeries); if (d->m_pointLabelsColor == QChartPrivate::defaultPen().color()) return QPen().color(); else return d->m_pointLabelsColor; } void QXYSeries::setPointLabelsClipping(bool enabled) { Q_D(QXYSeries); if (d->m_pointLabelsClipping != enabled) { d->m_pointLabelsClipping = enabled; emit pointLabelsClippingChanged(enabled); } } bool QXYSeries::pointLabelsClipping() const { Q_D(const QXYSeries); return d->m_pointLabelsClipping; } /*! Stream operator for adding the data point \a point to the series. \sa append() */ QXYSeries &QXYSeries::operator<< (const QPointF &point) { append(point); return *this; } /*! Stream operator for adding the list of data points specified by \a points to the series. \sa append() */ QXYSeries &QXYSeries::operator<< (const QList& points) { append(points); return *this; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// QXYSeriesPrivate::QXYSeriesPrivate(QXYSeries *q) : QAbstractSeriesPrivate(q), m_pen(QChartPrivate::defaultPen()), m_brush(QChartPrivate::defaultBrush()), m_pointsVisible(false), m_pointLabelsFormat(QLatin1String("@xPoint, @yPoint")), m_pointLabelsVisible(false), m_pointLabelsFont(QChartPrivate::defaultFont()), m_pointLabelsColor(QChartPrivate::defaultPen().color()), m_pointLabelsClipping(true) { } void QXYSeriesPrivate::initializeDomain() { qreal minX(0); qreal minY(0); qreal maxX(1); qreal maxY(1); Q_Q(QXYSeries); const QVector &points = q->pointsVector(); if (!points.isEmpty()) { minX = points[0].x(); minY = points[0].y(); maxX = minX; maxY = minY; for (int i = 0; i < points.count(); i++) { qreal x = points[i].x(); qreal y = points[i].y(); minX = qMin(minX, x); minY = qMin(minY, y); maxX = qMax(maxX, x); maxY = qMax(maxY, y); } } domain()->setRange(minX, maxX, minY, maxY); } QList QXYSeriesPrivate::createLegendMarkers(QLegend* legend) { Q_Q(QXYSeries); QList list; return list << new QXYLegendMarker(q,legend); } void QXYSeriesPrivate::initializeAxes() { } QAbstractAxis::AxisType QXYSeriesPrivate::defaultAxisType(Qt::Orientation orientation) const { Q_UNUSED(orientation); return QAbstractAxis::AxisTypeValue; } QAbstractAxis* QXYSeriesPrivate::createDefaultAxis(Qt::Orientation orientation) const { Q_UNUSED(orientation); return new QValueAxis; } void QXYSeriesPrivate::initializeAnimations(QtCharts::QChart::AnimationOptions options, int duration, QEasingCurve &curve) { XYChart *item = static_cast(m_item.data()); Q_ASSERT(item); if (item->animation()) item->animation()->stopAndDestroyLater(); if (options.testFlag(QChart::SeriesAnimations)) item->setAnimation(new XYAnimation(item, duration, curve)); else item->setAnimation(0); QAbstractSeriesPrivate::initializeAnimations(options, duration, curve); } void QXYSeriesPrivate::drawSeriesPointLabels(QPainter *painter, const QVector &points, const int offset) { if (points.size() == 0) return; static const QString xPointTag(QLatin1String("@xPoint")); static const QString yPointTag(QLatin1String("@yPoint")); const int labelOffset = offset + 2; QFont f(m_pointLabelsFont); f.setPixelSize(QFontInfo(m_pointLabelsFont).pixelSize()); painter->setFont(f); painter->setPen(QPen(m_pointLabelsColor)); QFontMetrics fm(painter->font()); // m_points is used for the label here as it has the series point information // points variable passed is used for positioning because it has the coordinates const int pointCount = qMin(points.size(), m_points.size()); for (int i(0); i < pointCount; i++) { QString pointLabel = m_pointLabelsFormat; pointLabel.replace(xPointTag, presenter()->numberToString(m_points.at(i).x())); pointLabel.replace(yPointTag, presenter()->numberToString(m_points.at(i).y())); // Position text in relation to the point int pointLabelWidth = fm.width(pointLabel); QPointF position(points.at(i)); position.setX(position.x() - pointLabelWidth / 2); position.setY(position.y() - labelOffset); painter->drawText(position, pointLabel); } } #include "moc_qxyseries.cpp" #include "moc_qxyseries_p.cpp" QT_CHARTS_END_NAMESPACE