// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

#undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses

#include <QtCharts/QXYSeries>
#include <QtCharts/QColorAxis>
#include <private/qxyseries_p.h>
#include <private/abstractdomain_p.h>
#include <QtCharts/QValueAxis>
#include <private/xychart_p.h>
#include <QtCharts/QXYLegendMarker>
#include <private/charthelpers_p.h>
#include <private/qchart_p.h>
#include <QtGui/QPainter>
#include <QtMath>

QT_BEGIN_NAMESPACE

/*!
    \class QXYSeries
    \inmodule QtCharts
    \brief The QXYSeries class is a base class for line, spline, and scatter
    series.

    QXYSeries supports displaying best fit line on a chart. Best fit line is a line
    through a chart that expresses the relationship between points.
*/
/*!
    \qmltype XYSeries
    \nativetype QXYSeries
    \inqmlmodule QtCharts

    \inherits AbstractSeries

    \brief A base type for line, spline, and scatter series.

    XYSeries supports displaying best fit line on a chart. Best fit line is a line
    through a chart that expresses the relationship between points.
*/

/*!
    \enum QXYSeries::PointConfiguration

    This enum value describes the particular configuration of a point.

    \value Color
           This enum value can be used to change a point's color. If used together
           with QXYSeries::setPointConfiguration, the configuration's value should
           be a valid QColor.
    \value Size
           This enum value can be used to change a point's size. If used together
           with QXYSeries::setPointConfiguration, the configuration's value should
           be a number, such as \c qreal or \c int.
    \value Visibility
           This enum value can be used to hide or show the point. If used
           together with QXYSeries::setPointConfiguration, the configuration's value
           should be boolean.
    \value LabelVisibility
           This enum value can be used to hide or show the label of the point. If used
           together with QXYSeries::setPointConfiguration, the configuration's value
           should be boolean.
    \value [since 6.5] LabelFormat
           This enum value can be used to set custom label text per-point. If used together with
           QXYSeries::setPointConfiguration, the configuration's value should be a string.
           \note If an empty string is set as the LabelFormat, it will be ignored, and the series
           pointLabelsFormat will be used.
           \sa pointLabelsFormat

    \sa setPointConfiguration()
    \since 6.2
*/

/*!
    \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 @index       \li The index in the series of the data point. [since 6.5]
      \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("@index: (@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
*/

/*!
    \property QXYSeries::selectedColor
    \brief The color of the selected points.

    This is the fill (brush) color of points marked as selected. If not specified,
    value of QXYSeries::color is used as default.
    \sa color
    \since 6.2
*/
/*!
    \qmlproperty color XYSeries::selectedColor
    The color of the selected points. This is the fill (brush) color of points marked
    as selected.
    If not specified, value of QXYSeries::color is used as default.
    \sa color
*/

/*!
    \fn void QXYSeries::markerSizeChanged(qreal size)
    This signal is emitted when the marker size changes to \a size.
*/

/*!
    \fn void QXYSeries::pointLabelsClippingChanged(bool clipping)
    This signal is emitted when the clipping of the data point labels changes to
    \a clipping.
*/
/*!
    \property QXYSeries::bestFitLineVisible
    \brief The visibility of the best fit line.

    This property is \c false by default.

    \sa bestFitLineEquation
    \since 6.2
*/
/*!
    \qmlproperty bool XYSeries::bestFitLineVisible
    The visibility of the best fit line.
    This property is \c false by default.
*/
/*!
    \fn void QXYSeries::bestFitLineVisibilityChanged(bool visible)
    This signal is emitted when the visibility of the best fit line changes to
    \a visible.
*/
/*!
    \property QXYSeries::bestFitLineColor
    \brief The color of best fit line.

    \sa bestFitLineEquation, bestFitLineVisible
    \since 6.2
*/
/*!
    \qmlproperty color XYSeries::bestFitLineColor
    The color of best fit line.
    \sa bestFitLineVisible
*/
/*!
    \fn void QXYSeries::bestFitLineColorChanged(const QColor &color)
    This signal is emitted when the color used for the best fit line changes to
    \a color.
*/

/*!
    \fn void QXYSeries::selectedColorChanged(const QColor &color)
    This signal is emitted when the color of the selected points changes to
    \a color.
*/

/*!
    \fn void QXYSeries::bestFitLinePenChanged(const QPen &pen)
    \internal
*/

/*!
    \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 QXYSeries::selectedPointsChanged()
    This signal is emitted when the points selection changes.
*/

/*!
    \fn void QXYSeries::lightMarkerChanged(const QImage &lightMarker)
    This signal is emitted when the light marker image changes to \a lightMarker.
    \sa QXYSeries::setLightMarker()
    \since 6.2
*/

/*!
    \fn void QXYSeries::selectedLightMarkerChanged(const QImage &selectedLightMarker)
    \internal
*/

/*!
    \fn void QXYSeries::pointsConfigurationChanged(const QXYSeries::PointsConfigurationHash &configuration)
    \internal
*/

/*!
    \fn void QXYSeriesPrivate::seriesUpdated()
    \internal
*/

/*!
    \qmlmethod void XYSeries::append(real x, real y)
    Appends a point with the coordinates \a x and \a y to the series.
*/

/*!
    \qmlmethod void 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 void 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 void XYSeries::remove(int index)
    Removes the point at the position specified by \a index from the series.
*/

/*!
    \qmlmethod void 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 void 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 point 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.size() - 1);
    }
}

/*!
   \overload
   Adds the list of data points specified by \a points to the series.
 */
void QXYSeries::append(const QList<QPointF> &points)
{
    for (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.
  \sa pointsReplaced()
*/
void QXYSeries::replace(const QList<QPointF> &points)
{
    Q_D(QXYSeries);
    const bool needUpdateSelection = d->m_points.size() > points.size();
    d->m_points = points;
    if (needUpdateSelection) {
        d->m_selectedPoints.removeIf([maxIdx = points.size() - 1](int idx) {
            return idx > maxIdx;
        });
    }
    emit pointsReplaced();
    if (needUpdateSelection)
        emit selectedPointsChanged();
}

/*!
    Removes the configuration of a point located at \a index
    and restores the default look derived from the series' settings.

    \note It doesn't affect the configuration of other points.
    \sa clearPointsConfiguration(), setPointConfiguration()
    \since 6.2
*/
void QXYSeries::clearPointConfiguration(const int index)
{
    Q_D(QXYSeries);
    if (d->m_pointsConfiguration.contains(index)) {
        d->m_pointsConfiguration.remove(index);
        emit pointsConfigurationChanged(d->m_pointsConfiguration);
    }
}

/*!
    Removes the configuration property identified by \a key from the point at \a index
    and restores the default look derived from the series' settings.

    Removes the configuration type, such as color or size,
    specified by \a key from the point at \a index with configuration customizations,
    allowing that configuration property to be rendered as the default
    specified in the series' properties.

    \note It doesn't affect the configuration of other points.
    \sa clearPointsConfiguration(), setPointConfiguration()
    \since 6.2
*/
void QXYSeries::clearPointConfiguration(const int index, const QXYSeries::PointConfiguration key)
{
    Q_D(QXYSeries);
    if (d->m_pointsConfiguration.contains(index)) {
        auto &conf = d->m_pointsConfiguration[index];
        if (conf.contains(key)) {
            conf.remove(key);
            d->m_pointsConfiguration[index] = conf;
            emit pointsConfigurationChanged(d->m_pointsConfiguration);
        }
    }
}

/*!
    Removes the configuration of all points in the series and restores
    the default look derived from the series' settings.

    \sa setPointConfiguration()
    \since 6.2
*/
void QXYSeries::clearPointsConfiguration()
{
    Q_D(QXYSeries);
    d->m_pointsConfiguration.clear();
    emit pointsConfigurationChanged(d->m_pointsConfiguration);
}

/*!
    Removes the configuration property identified by \a key from all
    points and restores the default look derived from the series' settings.

    Removes the configuration type, such as color or size,
    specified by \a key from all points with configuration customizations,
    allowing that configuration property to be rendered as the default
    specified in the series properties.

    \sa clearPointsConfiguration(), setPointConfiguration()
    \since 6.2
*/
void QXYSeries::clearPointsConfiguration(const QXYSeries::PointConfiguration key)
{
    Q_D(QXYSeries);
    bool needsUpdate = false;
    for (auto &conf : d->m_pointsConfiguration) {
        if (conf.contains(key)) {
            conf.remove(key);
            needsUpdate = true;
        }
    }

    if (needsUpdate)
        emit pointsConfigurationChanged(d->m_pointsConfiguration);
}

/*!
    Enables customizing the appearance of a point located at \a index with
    desired \a configuration.

    With points configuration you can change various aspects of every point's appearance.

    A point's configuration is represented as a hash map with QXYSeries::pointConfiguration
    keys and QVariant values. For example:
    \code
    QLineSeries *series = new QLineSeries();
    series->setName("Customized series");
    series->setPointsVisible(true);

    *series << QPointF(0, 6) << QPointF(2, 4) << QPointF(3, 6) << QPointF(7, 4) << QPointF(10, 5)
            << QPointF(11, 1) << QPointF(13, 3) << QPointF(17, 6) << QPointF(18, 3)
            << QPointF(20, 2);

    QChart *chart = new QChart();
    chart->addSeries(series);
    chart->createDefaultAxes();

    QHash<QXYSeries::PointConfiguration, QVariant> conf;
    conf[QXYSeries::PointConfiguration::Color] = QColor(Qt::red);
    conf[QXYSeries::PointConfiguration::Size] = 8;
    conf[QXYSeries::PointConfiguration::LabelVisibility] = true;

    series->setPointConfiguration(4, conf);

    conf.remove(QXYSeries::PointConfiguration::Color);
    conf[QXYSeries::PointConfiguration::LabelFormat] = "This Point";
    series->setPointConfiguration(6, conf);
    \endcode

    In this example, you can see a default QLineSeries with 10 points and with changed configuration
    of two points. Both changed points are visibly bigger than the others with a look derived from
    the series configuration.
    By default, points don't have labels, but the point at index 4 has a label thanks to the
    QXYSeries::PointConfiguration::LabelVisibility and QXYSeries::PointConfiguration::LabelFormat
    configuration values.
    The point at index 6 has a custom label \e {This Point} thanks to the
    QXYSeries::PointConfiguration::LabelFormat configuration value.
    Below is an example of a chart created in this way:
    \image xyseries_point_configuration.png {Screenshot showing a line chart with two dots on it,
           one being red and the other being blue}

    \sa pointsConfiguration(), clearPointsConfiguration()
    \since 6.2
*/
void QXYSeries::setPointConfiguration(
        const int index, const QHash<QXYSeries::PointConfiguration, QVariant> &configuration)
{
    Q_D(QXYSeries);
    if (d->m_pointsConfiguration[index] != configuration) {
        d->m_pointsConfiguration[index] = configuration;
        emit pointsConfigurationChanged(d->m_pointsConfiguration);
    }
}

/*!
    Enables customizing a particular aspect of a point's configuration.

    \note Points configuration concept provides a flexible way to configure various aspects
    of a point's appearance. Thus, values need to have an elastic type such as QVariant.
    See QXYSeries::PointConfiguration to see what \a value should be passed
    for certain \a key.
    \sa pointsConfiguration()
    \since 6.2
*/
void QXYSeries::setPointConfiguration(const int index, const QXYSeries::PointConfiguration key,
                                      const QVariant &value)
{
    Q_D(QXYSeries);
    if (d->setPointConfiguration(index, key, value))
        emit pointsConfigurationChanged(d->m_pointsConfiguration);
}

/*!
    Enables customizing the configuration of multiple points as specified
    by \a pointsConfiguration.

    \sa pointsConfiguration()
    \since 6.2
*/
void QXYSeries::setPointsConfiguration(
        const QHash<int, QHash<QXYSeries::PointConfiguration, QVariant>> &pointsConfiguration)
{
    Q_D(QXYSeries);
    if (d->m_pointsConfiguration != pointsConfiguration) {
        d->m_pointsConfiguration = pointsConfiguration;
        emit pointsConfigurationChanged(d->m_pointsConfiguration);
    }
}

/*!
    Returns a map representing the configuration of a point at \a index.

    With points configuration you can change various aspects of each point's look.

    \sa setPointConfiguration()
    \since 6.2
*/
QHash<QXYSeries::PointConfiguration, QVariant> QXYSeries::pointConfiguration(const int index) const
{
    Q_D(const QXYSeries);
    return d->m_pointsConfiguration[index];
}

/*!
    Returns a map with points' indexes as keys and points' configuration as values.

    \sa setPointConfiguration(), pointConfiguration()
    \since 6.2
*/
QHash<int, QHash<QXYSeries::PointConfiguration, QVariant>> QXYSeries::pointsConfiguration() const
{
    Q_D(const QXYSeries);
    return d->m_pointsConfiguration;
}

/*!
    Sets the points' sizes according to a passed list of values. Values from
    \a sourceData are sorted and mapped to a point size which is between \a minSize
    and \a maxSize.

    \note If \a sourceData length is smaller than number of points in the series, then
    size of the points at the end of the series will stay the same.
    \sa setPointConfiguration(), pointConfiguration()
    \since 6.2
*/
void QXYSeries::sizeBy(const QList<qreal> &sourceData, const qreal minSize, const qreal maxSize)
{
    Q_D(QXYSeries);

    Q_ASSERT(minSize <= maxSize);
    Q_ASSERT(minSize >= 0);

    qreal min = std::numeric_limits<qreal>::max();
    qreal max = -std::numeric_limits<qreal>::max();
    for (const auto &p : sourceData) {
        min = qMin(min, p);
        max = qMax(max, p);
    }

    const qreal range = max - min;
    const qreal sizeRange = maxSize - minSize;
    bool changed = false;

    for (int i = 0; i < sourceData.size() && i < d->m_points.size(); ++i) {
        qreal pointSize = minSize;
        if (range != 0) {
            const qreal startValue = sourceData.at(i) - min;
            const qreal percentage = startValue / range;
            pointSize = minSize + (percentage * sizeRange);
        }
        if (d->setPointConfiguration(i, QXYSeries::PointConfiguration::Size, pointSize))
            changed = true;
    }

    if (changed)
        emit pointsConfigurationChanged(d->m_pointsConfiguration);
}

/*!
    Sets the points' color according to a passed list of values. Values from
    \a sourceData are sorted and mapped to the \a gradient.

    If the series has a QColorAxis attached, then a gradient from the axis
    is going to be used.

    \sa setPointConfiguration(), pointConfiguration(), QColorAxis
    \since 6.2
*/
void QXYSeries::colorBy(const QList<qreal> &sourceData, const QLinearGradient &gradient)
{
    Q_D(QXYSeries);

    d->m_colorByData = sourceData;
    if (d->m_colorByData.isEmpty())
        return;

    const qreal imgSize = 100.0;

    qreal min = std::numeric_limits<qreal>::max();
    qreal max = std::numeric_limits<qreal>::min();
    for (const auto &p : sourceData) {
        min = qMin(min, p);
        max = qMax(max, p);
    }

    qreal range = max - min;

    QLinearGradient usedGradient = gradient;

    // Gradient will be taked from the first attached color axis.
    // If there are more color axis, they will have just changed range.
    bool axisFound = false;
    const auto axes = attachedAxes();
    for (const auto &axis : axes) {
        if (axis->type() == QAbstractAxis::AxisTypeColor) {
            QColorAxis *colorAxis = static_cast<QColorAxis *>(axis);
            if (!axisFound) {
                usedGradient = QLinearGradient(QPointF(0,0), QPointF(0, imgSize));
                const auto stops = colorAxis->gradient().stops();
                for (const auto &stop : stops)
                    usedGradient.setColorAt(stop.first, stop.second);

                if (!colorAxis->autoRange()) {
                    min = colorAxis->min();
                    max = colorAxis->max();
                    range = max - min;
                }

                axisFound = true;
            }

            if (colorAxis->autoRange())
                colorAxis->setRange(min, max);
        }
    }

    QImage image(imgSize, imgSize, QImage::Format_ARGB32);
    QPainter painter(&image);
    painter.fillRect(image.rect(), usedGradient);

    // To ensure that negative values will be well handled, distance from min to 0
    // will be added to min and every single value. This will move entire values
    // list to positive only values.
    const qreal diff = min < 0 ? qAbs(min) : 0;
    min += diff;

    bool changed = false;

    for (int i = 0; i < sourceData.size() && i < d->m_points.size(); ++i) {
        const qreal startValue = qMax(0.0, sourceData.at(i) + diff - min);
        const qreal percentage = startValue / range;
        QColor color = image.pixelColor(0, qMin(percentage * imgSize, imgSize - 1));
        if (d->setPointConfiguration(i, QXYSeries::PointConfiguration::Color, color))
            changed = true;
    }

    if (changed)
        emit pointsConfigurationChanged(d->m_pointsConfiguration);
}

/*!
   Returns true if point at given \a index is among selected points and false otherwise.
   \note Selected points are drawn using the selected color if it was specified.
   \sa selectedPoints(), setPointSelected(), setSelectedColor()
   \since 6.2
 */
bool QXYSeries::isPointSelected(int index)
{
    Q_D(QXYSeries);
    return d->isPointSelected(index);
}

/*!
  Marks point at \a index as selected.
  \note Emits QXYSeries::selectedPointsChanged
  \sa setPointSelected()
  \since 6.2
 */
void QXYSeries::selectPoint(int index)
{
    setPointSelected(index, true);
}

/*!
  Deselects point at given \a index.
  \note Emits QXYSeries::selectedPointsChanged
  \sa setPointSelected()
  \since 6.2
 */
void QXYSeries::deselectPoint(int index)
{
    setPointSelected(index, false);
}

/*!
  Marks point at given \a index as either selected or deselected as specified by \a selected.
  \note Selected points are drawn using the selected color if it was specified. Emits QXYSeries::selectedPointsChanged
  \sa setPointSelected(), setSelectedColor()
  \since 6.2
 */
void QXYSeries::setPointSelected(int index, bool selected)
{
    Q_D(QXYSeries);

    bool callSignal = false;
    d->setPointSelected(index, selected, callSignal);

    if (callSignal)
        emit selectedPointsChanged();
}

/*!
  Marks all points in the series as selected,
  \note Emits QXYSeries::selectedPointsChanged
  \sa setPointSelected()
  \since 6.2
 */
void QXYSeries::selectAllPoints()
{
    Q_D(QXYSeries);

    bool callSignal = false;
    for (int i = 0; i < d->m_points.size(); ++i)
        d->setPointSelected(i, true, callSignal);

    if (callSignal)
        emit selectedPointsChanged();
}

/*!
  Deselects all points in the series.
  \note Emits QXYSeries::selectedPointsChanged
  \sa setPointSelected()
  \since 6.2
 */
void QXYSeries::deselectAllPoints()
{
    Q_D(QXYSeries);

    bool callSignal = false;
    for (int i = 0; i < d->m_points.size(); ++i)
        d->setPointSelected(i, false, callSignal);

    if (callSignal)
        emit selectedPointsChanged();
}

/*!
  Marks multiple points passed in a \a indexes list as selected.
  \note Emits QXYSeries::selectedPointsChanged
  \sa setPointSelected()
  \since 6.2
 */
void QXYSeries::selectPoints(const QList<int> &indexes)
{
    Q_D(QXYSeries);

    bool callSignal = false;
    for (const int &index : indexes)
        d->setPointSelected(index, true, callSignal);

    if (callSignal)
        emit selectedPointsChanged();
}

/*!
  Marks multiple points passed in a \a indexes list as deselected.
  \note Emits QXYSeries::selectedPointsChanged
  \sa setPointSelected()
  \since 6.2
 */
void QXYSeries::deselectPoints(const QList<int> &indexes)
{
    Q_D(QXYSeries);

    bool callSignal = false;
    for (const int &index : indexes)
        d->setPointSelected(index, false, callSignal);

    if (callSignal)
        emit selectedPointsChanged();
}

/*!
  Changes selection state of points at given \a indexes to the opposite one. Makes
  \note Emits QXYSeries::selectedPointsChanged
  \sa setPointSelected()
  \since 6.2
 */
void QXYSeries::toggleSelection(const QList<int> &indexes)
{
    Q_D(QXYSeries);

    bool callSignal = false;
    for (const int &index : indexes)
        d->setPointSelected(index, !isPointSelected(index), callSignal);

    if (callSignal)
        emit selectedPointsChanged();
}

/*!
  Returns a list of points indexes marked as selected.
  Selected points are visible regardless of points visibility.
  \sa setPointSelected(), pointsVisible()
  \since 6.2
 */
QList<int> QXYSeries::selectedPoints() const
{
    Q_D(const QXYSeries);
    return QList<int>(d->m_selectedPoints.begin(), d->m_selectedPoints.end());
}

/*!
  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);

    bool callSignal = false;
    d->setPointSelected(index, false, callSignal);

    emit pointRemoved(index);
    if (callSignal)
        emit selectedPointsChanged();
}

/*!
  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);

        bool callSignal = false;
        if (!d->m_selectedPoints.empty()) {
            QSet<int> selectedAfterRemoving;

            for (const int &selectedPointIndex : std::as_const(d->m_selectedPoints)) {
                if (selectedPointIndex < index) {
                    selectedAfterRemoving << selectedPointIndex;
                } else if (selectedPointIndex >= index + count) {
                    selectedAfterRemoving << selectedPointIndex - count;
                    callSignal = true;
                } else {
                    callSignal = true;
                }
            }

            d->m_selectedPoints = selectedAfterRemoving;
        }

        emit pointsRemoved(index, count);
        if (callSignal)
            emit selectedPointsChanged();
    }
}

/*!
  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);

        bool callSignal = false;
        if (!d->m_selectedPoints.isEmpty()) {
            // if point was inserted we need to move already selected points by 1
            QSet<int> selectedAfterInsert;
            for (const auto &value : std::as_const(d->m_selectedPoints)) {
                if (value >= index) {
                    selectedAfterInsert << value + 1;
                    callSignal = true;
                } else {
                    selectedAfterInsert << value;
                }
            }
            d->m_selectedPoints = selectedAfterInsert;
        }

        emit pointAdded(index);
        if (callSignal)
            emit selectedPointsChanged();
    }
}

/*!
  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.
*/
QList<QPointF> QXYSeries::points() const
{
    Q_D(const QXYSeries);
    return d->m_points;
}

#if QT_DEPRECATED_SINCE(6, 0)
/*!
    \deprecated

    Use points() instead.
    Returns the points in the series.

*/
QList<QPointF> QXYSeries::pointsVector() const
{
    Q_D(const QXYSeries);
    return d->m_points;
}
#endif

/*!
    Returns the data point at the position specified by \a index in the internal
    series of points.
*/
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.size();
}


/*!
    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->seriesUpdated();
        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->seriesUpdated();
    }
}

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::setSelectedColor(const QColor &color)
{
    Q_D(QXYSeries);
    if (selectedColor() != color) {
        d->m_selectedColor = color;
        emit selectedColorChanged(color);
    }
}

QColor QXYSeries::selectedColor() const
{
    Q_D(const QXYSeries);
    return d->m_selectedColor;
}

void QXYSeries::setPointsVisible(bool visible)
{
    Q_D(QXYSeries);
    if (d->m_pointsVisible != visible) {
        d->m_pointsVisible = visible;
        emit d->seriesUpdated();
    }
}

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;
}

/*!
    Sets the image used for drawing markers on each point of the series as
    the value of \a lightMarker.

    The default value is a default-QImage() (QImage::isNull() == true), meaning no light marker
    will be painted.
    You can reset back to default (disabled) by calling this function with a null QImage (QImage()).

    The light markers visualize the data points of this series and as such are an alternative
    to \c setPointsVisible(true).
    If a light marker is set with this method, visible points as set with \c setPointsVisible(true)
    are not displayed.

    Unlike the elements of \l {QScatterSeries}{QScatterSeries} the light markers
    are not represented by QGraphicsItem, but are just painted (no objects created).
    However, the mouse-event-signals of QXYSeries behave the same way,
    meaning that you'll get the exact domain value of the point if you click/press/hover
    the light marker. You'll still get the in between domain value if you click on the line.
    The light markers are above the line in terms of painting as well as events.

    \sa QXYSeries::lightMarker()
    \since 6.2
*/
void QXYSeries::setLightMarker(const QImage &lightMarker)
{
    Q_D(QXYSeries);
    if (d->m_lightMarker == lightMarker)
        return;

    d->m_lightMarker = lightMarker;
    emit d->seriesUpdated();
    emit lightMarkerChanged(d->m_lightMarker);
}

/*!
    Gets the image used for drawing markers on each point of the series.

    The default value is QImage(), meaning no light marker will be painted.

    The light markers visualize the data points of this series and as such are an alternative
    to setPointsVisible(true).
    Both features can be enabled independently from each other.

    Unlike the elements of \l {QScatterSeries}{QScatterSeries} the light markers
    are not represented by QGraphicsItem, but are just painted (no objects created).
    However, the mouse-event-signals of QXYSeries behave the same way,
    meaning that you'll get the exact domain value of the point if you click/press/hover
    the light marker. You'll still get the in between domain value if you click on the line.
    The light markers are above the line in terms of painting as well as events.
    \sa QXYSeries::setLightMarker()
    \since 6.2
*/
const QImage &QXYSeries::lightMarker() const
{
    Q_D(const QXYSeries);
    return d->m_lightMarker;
}

/*!
    Sets the image used for drawing markers on selected series's points to \a selectedLightMarker.

    The default value is QImage(), meaning usual lightMarker() will be painted.

    This is an equivalent for \l{selectedColor} if you prefer light markers over
    normal points, but still want to distinguish selected points.

    \sa lightMarker(), selectedColor, setPointSelected()
    \since 6.2
*/
void QXYSeries::setSelectedLightMarker(const QImage &selectedLightMarker)
{
    Q_D(QXYSeries);
    if (d->m_selectedLightMarker == selectedLightMarker)
        return;

    d->m_selectedLightMarker = selectedLightMarker;
    emit d->seriesUpdated();
    emit selectedLightMarkerChanged(d->m_selectedLightMarker);
}

/*!
    Returns the image used for drawing markers on selected series' points.

    The default value is QImage(), meaning usual lightMarker() will be painted.

    This is equivalent to \l{selectedColor} if you prefer light markers over
    normal points, but still want to distinguish selected points.

    \sa lightMarker(), selectedColor, setPointSelected()
    \since 6.2
*/
const QImage &QXYSeries::selectedLightMarker() const
{
    Q_D(const QXYSeries);
    return d->m_selectedLightMarker;
}

/*!
    Sets the \a size of the marker used to render points in the series.

    The default size is 15.0.
    \sa QScatterSeries::markerSize
    \since 6.2
*/
void QXYSeries::setMarkerSize(qreal size)
{
    Q_D(QXYSeries);

    if (!qFuzzyCompare(d->m_markerSize, size)) {
        d->m_markerSizeDefault = false;
        d->setMarkerSize(size);
        emit d->seriesUpdated();
        emit markerSizeChanged(size);
    }
}

/*!
    Gets the size of the marker used to render points in the series.

    The default size depends on the specific QXYSeries type.
    QScatterSeries has a default of 15.0
    QLineSeries has a default of the series pen size * 1.5
    \sa QScatterSeries::markerSize
    \since 6.2
*/
qreal QXYSeries::markerSize() const
{
    Q_D(const QXYSeries);
    return d->m_markerSize;
}

void QXYSeries::setBestFitLineVisible(bool visible)
{
    Q_D(QXYSeries);
    if (d->m_bestFitLineVisible != visible) {
        d->m_bestFitLineVisible = visible;
        emit bestFitLineVisibilityChanged(visible);
        emit d->seriesUpdated();
    }
}

bool QXYSeries::bestFitLineVisible() const
{
    Q_D(const QXYSeries);
    return d->m_bestFitLineVisible;
}

/*!
    Returns a pair of numbers where the first number is a slope factor
    and the second number is intercept of a linear function for a best fit line.

    Those factors are calculated using Least Squares Method based
    on points passed to the series.

    Parameter \a ok is used to report a failure by setting its value to \c false
    and to report a success by setting its value to \c true.

    \sa QXYSeries::bestFitLineVisible()
    \since 6.2
*/
QPair<qreal, qreal> QXYSeries::bestFitLineEquation(bool &ok) const
{
    Q_D(const QXYSeries);
    return d->bestFitLineEquation(ok);
}

void QXYSeries::setBestFitLinePen(const QPen &pen)
{
    Q_D(QXYSeries);
    if (d->m_bestFitLinePen != pen) {
        bool emitColorChanged = d->m_bestFitLinePen.color() != pen.color();
        d->m_bestFitLinePen = pen;
        emit d->seriesUpdated();
        if (emitColorChanged)
            bestFitLineColorChanged(pen.color());
        emit bestFitLinePenChanged(pen);
    }
}

QPen QXYSeries::bestFitLinePen() const
{
    Q_D(const QXYSeries);
    if (d->m_bestFitLinePen == QChartPrivate::defaultPen())
        return QPen();
    else
        return d->m_bestFitLinePen;
}

/*!
    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<QPointF>& 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),
      m_bestFitLinePen(QChartPrivate::defaultPen()),
      m_bestFitLineVisible(false),
      m_markerSize(15.0)
{
}

void QXYSeriesPrivate::initializeDomain()
{
    qreal minX(0);
    qreal minY(0);
    qreal maxX(1);
    qreal maxY(1);

    Q_Q(QXYSeries);

    const QList<QPointF> &points = q->points();

    if (!points.isEmpty()) {
        minX = points[0].x();
        minY = points[0].y();
        maxX = minX;
        maxY = minY;

        for (int i = 0; i < points.size(); 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<QLegendMarker*> QXYSeriesPrivate::createLegendMarkers(QLegend* legend)
{
    Q_Q(QXYSeries);
    QList<QLegendMarker*> 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(QChart::AnimationOptions options,
                                            int duration, QEasingCurve &curve)
{
    XYChart *item = static_cast<XYChart *>(m_item.get());
    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::drawPointLabels(QPainter *painter, const QList<QPointF> &allPoints,
                                       const int offset)
{
    if (m_pointLabelsVisible || !m_pointsConfiguration.isEmpty()) {
        if (m_pointLabelsClipping)
            painter->setClipping(true);
        else
            painter->setClipping(false);

        QList<int> pointsToSkip;
        QHash<int, QString> labelFormats;
        QHash<int, int> offsets;

        if (!m_pointsConfiguration.isEmpty()) {
            for (int i = 0; i < allPoints.size(); ++i) {
                bool drawLabel = m_pointLabelsVisible;
                if (m_pointsConfiguration.contains(i)) {
                    const auto &conf = m_pointsConfiguration[i];
                    auto key = QXYSeries::PointConfiguration::LabelVisibility;
                    if (conf.contains(key)) {
                        drawLabel = m_pointsConfiguration[i][key].toBool();
                        key = QXYSeries::PointConfiguration::Size;
                        if (drawLabel && conf.contains(key))
                            offsets[i] = conf[key].toReal();
                    }
                    key = QXYSeries::PointConfiguration::LabelFormat;
                    if (conf.contains(key) && !conf[key].toString().isEmpty())
                        labelFormats[i] = conf[key].toString();
                }

                if (!drawLabel)
                    pointsToSkip << i;
            }
        }

        drawSeriesPointLabels(painter, allPoints, offset, offsets, pointsToSkip, labelFormats);
    }
}

void QXYSeriesPrivate::drawSeriesPointLabels(QPainter *painter, const QList<QPointF> &points,
                                             const int offset, const QHash<int, int> &offsets,
                                             const QList<int> &indexesToSkip,
                                             const QHash<int, QString> &labelFormats)
{
    if (points.size() == 0)
        return;

    static const QString xPointTag(QLatin1String("@xPoint"));
    static const QString yPointTag(QLatin1String("@yPoint"));
    static const QString indexTag(QLatin1String("@index"));

    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++) {
        if (indexesToSkip.contains(i))
            continue;

        QString pointLabel = labelFormats.contains(i) ? labelFormats[i] : m_pointLabelsFormat;
        pointLabel.replace(xPointTag, presenter()->numberToString(m_points.at(i).x()));
        pointLabel.replace(yPointTag, presenter()->numberToString(m_points.at(i).y()));
        pointLabel.replace(indexTag, presenter()->numberToString(i));

        int currOffset = offset;
        if (offsets.contains(i))
            currOffset = offsets[i];

        const int labelOffset = currOffset + 2;

        // Position text in relation to the point
        int pointLabelWidth = fm.horizontalAdvance(pointLabel);
        QPointF position(points.at(i));
        position.setX(position.x() - pointLabelWidth / 2);
        position.setY(position.y() - labelOffset);

        painter->drawText(position, pointLabel);
    }
}

void QXYSeriesPrivate::drawBestFitLine(QPainter *painter, const QRectF &clipRect)
{
    bool ok = false;
    const auto &bestFitLineParams = bestFitLineEquation(ok);

    if (!ok)
        return;

    auto *domain = this->domain();
    const auto clipOriginX = domain->isReverseX() ? clipRect.right() : clipRect.left();
    const auto clipOriginY = domain->isReverseY() ? clipRect.top() : clipRect.bottom();
    const auto domainOrigin = domain->calculateDomainPoint({clipOriginX, clipOriginY});

    const qreal x1 = domainOrigin.x();
    const qreal y1 = bestFitLineParams.first * x1 + bestFitLineParams.second;
    QPointF p1 = domain->calculateGeometryPoint(QPointF(x1, y1), ok);

    const qreal x2 = domainOrigin.x() + 1;
    const qreal y2 = bestFitLineParams.first * x2 + bestFitLineParams.second;
    QPointF p2 = domain->calculateGeometryPoint(QPointF(x2, y2), ok);

    if (ok) {
        QLineF bestFitLine { p1, p2 };
        // maxLength is length of the diagonal, no line can be longer
        const qreal maxLength = qSqrt(qPow(clipRect.width(), 2) * qPow(clipRect.height(), 2));
        bestFitLine.setLength(maxLength);

        painter->save();
        painter->setPen(m_bestFitLinePen);
        painter->drawLine(bestFitLine);
        painter->restore();
    }
}

void QXYSeries::setBestFitLineColor(const QColor &color)
{
    QPen p = bestFitLinePen();
    if (p.color() != color) {
        p.setColor(color);
        setBestFitLinePen(p);
    }
}

QColor QXYSeries::bestFitLineColor() const
{
    return bestFitLinePen().color();
}

QPair<qreal, qreal> QXYSeriesPrivate::bestFitLineEquation(bool &ok) const
{
    if (m_points.size() <= 1) {
        ok = false;
        return { 0, 0 };
    }

    ok = true;
    qreal xSum = 0.0, x2Sum = 0.0, ySum = 0.0, xySum = 0.0;
    for (const auto &point : m_points) {
        xSum += point.x();
        ySum += point.y();
        x2Sum += qPow(point.x(), 2);
        xySum += point.x() * point.y();
    }

    const qreal divisor = m_points.size() * x2Sum - xSum * xSum;
    // To prevent crashes in unusual cases
    if (divisor == 0.0) {
        ok = false;
        return { 0, 0 };
    }

    qreal a = (m_points.size() * xySum - xSum * ySum) / divisor;
    qreal b = (x2Sum * ySum - xSum * xySum) / divisor;

    return { a, b };
}

void QXYSeriesPrivate::setPointSelected(int index, bool selected, bool &callSignal)
{
    if (index < 0 || index > m_points.size() - 1)
        return;

    if (selected) {
        if (!isPointSelected(index)) {
            m_selectedPoints << index;
            callSignal = true;
        }
    } else {
        if (isPointSelected(index)) {
            m_selectedPoints.remove(index);
            callSignal = true;
        }
    }
}

bool QXYSeriesPrivate::isPointSelected(int index)
{
    return m_selectedPoints.contains(index);
}

bool QXYSeriesPrivate::isMarkerSizeDefault()
{
    return m_markerSizeDefault;
}

void QXYSeriesPrivate::setMarkerSize(qreal markerSize)
{
    m_markerSize = markerSize;
}

QList<qreal> QXYSeriesPrivate::colorByData() const
{
    return m_colorByData;
}

bool QXYSeriesPrivate::setPointConfiguration(const int index,
                                             const QXYSeries::PointConfiguration key,
                                             const QVariant &value)
{
    QHash<QXYSeries::PointConfiguration, QVariant> conf;
    if (m_pointsConfiguration.contains(index))
        conf = m_pointsConfiguration[index];

    bool changed = false;
    if (conf.contains(key)) {
        if (conf[key] != value)
            changed = true;
    } else {
        changed = true;
    }

    conf[key] = value;
    m_pointsConfiguration[index] = conf;

    return changed;
}

QT_END_NAMESPACE

#include "moc_qxyseries.cpp"
#include "moc_qxyseries_p.cpp"
