Revision control
++ // Copyright (C) 2023 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++ #include <private/qgraphsglobal_p.h>
++ #if QT_CONFIG(graphs_2d_line) || QT_CONFIG(graphs_2d_area)
++ #include <QtGraphs/qlineseries.h>
++ #endif
++ #if QT_CONFIG(graphs_2d_scatter)
++ #include <QtGraphs/qscatterseries.h>
++ #endif
++ #if QT_CONFIG(graphs_2d_spline)
++ #include <QtGraphs/qsplineseries.h>
++ #endif
++ #if QT_CONFIG(graphs_2d_high_performance_backend)
++ #include <QtCanvasPainter/QCanvasPainter>
++ #endif
++ #include <QtCore/qmath.h>
++ #include <QtQuick/private/qquickdraghandler_p.h>
++ #include <QtQuick/private/qquicktaphandler_p.h>
++ #include <private/axisrenderer_p.h>
++ #include <private/charthelpers_p.h>
++ #include <private/pointrenderer_p.h>
++ #include <private/qabstractseries_p.h>
++ #include <private/qgraphsview_p.h>
++ #include <private/qpolarview_p.h>
++ #include <private/qxyseries_p.h>
++
++ #include <qtgraphs_tracepoints_p.h>
++
++ QT_BEGIN_NAMESPACE
++ Q_TRACE_PREFIX(qtgraphs,
++ "QT_BEGIN_NAMESPACE" \
++ "class PointRenderer;" \
++ "QT_END_NAMESPACE"
++ )
++
++ Q_TRACE_POINT(qtgraphs, QGraphs2DPointRendererAfterPolish_entry, int cleanupSeriesCount);
++ Q_TRACE_POINT(qtgraphs, QGraphs2DPointRendererAfterPolish_exit);
++
++ Q_TRACE_POINT(qtgraphs, QGraphs2DPointRendererUpdateScatterSeries_entry);
++ Q_TRACE_POINT(qtgraphs, QGraphs2DPointRendererUpdateScatterSeries_exit);
++
++ Q_TRACE_POINT(qtgraphs, QGraphs2DPointRendererUpdateLineSeries_entry);
++ Q_TRACE_POINT(qtgraphs, QGraphs2DPointRendererUpdateLineSeries_exit);
++
++ Q_TRACE_POINT(qtgraphs, QGraphs2DPointRendererUpdateSplineSeries_entry);
++ Q_TRACE_POINT(qtgraphs, QGraphs2DPointRendererUpdateSplineSeries_exit);
++
++ static const char *TAG_POINT_COLOR = "pointColor";
++ static const char *TAG_POINT_BORDER_COLOR = "pointBorderColor";
++ static const char *TAG_POINT_BORDER_WIDTH = "pointBorderWidth";
++ static const char *TAG_POINT_SELECTED_COLOR = "pointSelectedColor";
++ static const char *TAG_POINT_SELECTED = "pointSelected";
++ static const char *TAG_POINT_VALUE_X = "pointValueX";
++ static const char *TAG_POINT_VALUE_Y = "pointValueY";
++ static const char *TAG_POINT_INDEX = "pointIndex";
++
++ PointRenderer::PointRenderer(QGraphsView *graph, bool clipPlotArea)
++ : QQuickItem(graph)
++ , m_graph(graph)
++ , m_polarView(qobject_cast<QPolarView *>(graph))
++ {
++ setFlag(QQuickItem::ItemHasContents);
++ setClip(clipPlotArea);
++
++ #if QT_CONFIG(graphs_2d_high_quality_backend)
++ m_shape.setParentItem(this);
++ m_shape.setPreferredRendererType(QQuickShape::CurveRenderer);
++ #endif
++
++ m_tempMarker = new QQmlComponent(qmlEngine(m_graph), this);
++ m_tempMarker->loadFromModule(u"QtGraphs2D.impl"_s, u"PointMarker"_s);
++
++ m_tapHandler = new QQuickTapHandler(this);
++ connect(m_tapHandler, &QQuickTapHandler::singleTapped,
++ this, &PointRenderer::onSingleTapped);
++ connect(m_tapHandler, &QQuickTapHandler::doubleTapped,
++ this, &PointRenderer::onDoubleTapped);
++ connect(m_tapHandler, &QQuickTapHandler::pressedChanged, this, &PointRenderer::onPressedChanged);
++ }
++
++ PointRenderer::~PointRenderer()
++ {
++ qDeleteAll(m_groups);
++ }
++
++ void PointRenderer::resetShapePathCount()
++ {
++ m_currentShapePathIndex = 0;
++ #if QT_CONFIG(graphs_2d_high_quality_backend)
++ // The shape's paths are only refreshed while the canvas painter is off, so leaving it
++ // visible after a switch to the canvas painter would keep drawing the geometry from the
++ // last polish underneath that backend's output.
++ m_shape.setVisible(!m_graph->useCanvasPainter());
++ #endif
++ }
++
++ #if QT_CONFIG(graphs_2d_high_performance_backend)
++ void PointRenderer::paintSnapshot(const PaintSnapshot &snapshot, QCanvasPainter *p) {
++
++ p->setLineJoin(QCanvasPainter::LineJoin::Round);
++
++ for (const auto &pointSnapshot : snapshot) {
++ p->setStrokeStyle(pointSnapshot.strokeColor);
++ p->setLineWidth(pointSnapshot.lineWidth);
++ p->setLineCap(pointSnapshot.lineCap);
++ p->beginPath();
++ p->addPath(pointSnapshot.painterPath);
++ p->stroke();
++ }
++ }
++
++ void PointRenderer::synchronizeData()
++ {
++ m_pointPaintSnapshot.clear();
++ for (const auto &group : std::as_const(m_groups)) {
++ if (group->painterPath.elementCount() == 0)
++ continue;
++
++ const auto style = getSeriesStyle(group);
++
++ qreal width = 1.0;
++ QCanvasPainter::LineCap capStyle = QCanvasPainter::LineCap::Round;
++ #if QT_CONFIG(graphs_2d_line) || QT_CONFIG(graphs_2d_area)
++ if (auto line = qobject_cast<QLineSeries *>(group->series)) {
++ width = line->width();
++ switch (line->capStyle()) {
++ case Qt::PenCapStyle::FlatCap:
++ capStyle = QCanvasPainter::LineCap::Butt;
++ break;
++ case Qt::PenCapStyle::RoundCap:
++ capStyle = QCanvasPainter::LineCap::Round;
++ break;
++ case Qt::PenCapStyle::SquareCap:
++ capStyle = QCanvasPainter::LineCap::Square;
++ break;
++ default:
++ break;
++ }
++ }
++ #endif
++ #if QT_CONFIG(graphs_2d_spline)
++ #if QT_CONFIG(graphs_2d_line)
++ else
++ #endif
++ if (auto spline = qobject_cast<QSplineSeries *>(group->series)) {
++ width = spline->width();
++ switch (spline->capStyle()) {
++ case Qt::PenCapStyle::FlatCap:
++ capStyle = QCanvasPainter::LineCap::Butt;
++ break;
++ case Qt::PenCapStyle::RoundCap:
++ capStyle = QCanvasPainter::LineCap::Round;
++ break;
++ case Qt::PenCapStyle::SquareCap:
++ capStyle = QCanvasPainter::LineCap::Square;
++ break;
++ default:
++ break;
++ }
++ }
++ #endif
++ m_pointPaintSnapshot.append({group->painterPath, style.color, width, capStyle});
++ }
++ }
++
++ PointRenderer::PaintSnapshot PointRenderer::paintSnapshot() const
++ {
++ return m_pointPaintSnapshot;
++ }
++
++ #endif
++
++ qreal PointRenderer::defaultSize(QXYSeries *series)
++ {
++ qreal size = 16.0;
++ if (series != nullptr) {
++ #if QT_CONFIG(graphs_2d_line) || QT_CONFIG(graphs_2d_area)
++ if (auto line = qobject_cast<QLineSeries *>(series))
++ size = qMax(size, line->width());
++ #endif
++ #if (QT_CONFIG(graphs_2d_line) || QT_CONFIG(graphs_2d_area)) && QT_CONFIG(graphs_2d_spline)
++ else
++ #endif
++ #if QT_CONFIG(graphs_2d_spline)
++ if (auto spline = qobject_cast<QSplineSeries *>(series))
++ size = qMax(size, spline->width());
++ #endif
++ }
++ return size;
++ }
++
++ void PointRenderer::calculateRenderCoordinates(AxisRenderer *axisRenderer,
++ QAbstractSeries *series,
++ qreal origX,
++ qreal origY,
++ qreal *renderX,
++ qreal *renderY)
++ {
++ auto &axisX = axisRenderer->getAxisX(series);
++ auto &axisY = axisRenderer->getAxisY(series);
++
++ qreal x = origX;
++ qreal y = origY;
++
++ if (axisX.isLogarithmic) {
++ float logBase = log(axisX.logBase);
++ x = log(origX) / logBase;
++ }
++
++ if (axisY.isLogarithmic) {
++ float logBase = log(axisY.logBase);
++ y = log(origY) / logBase;
++ }
++
++ if (m_polarView) {
++ qreal angleFrac = !qFuzzyIsNull(axisX.valueRange) ? (x - axisX.minValue) / axisX.valueRange : 0;
++ qreal yClamped = std::max(y, axisY.minValue);
++ qreal radiusFrac = !qFuzzyIsNull(axisY.valueRange) ? (yClamped - axisY.minValue) / axisY.valueRange
++ : 0;
++ qreal radius = radiusFrac * m_polarView->polarRadius();
++ qreal rad = qDegreesToRadians(angleFrac * 360.0);
++ const QPointF center = m_polarView->polarCenter();
++ *renderX = center.x() + qSin(rad) * radius;
++ *renderY = center.y() - qCos(rad) * radius;
++ return;
++ }
++
++ if (m_graph->orientation() != Qt::Vertical) {
++ std::swap(x, y);
++ y = axisY.maxValue - y;
++ }
++
++ auto flipX = axisX.maxValue < axisX.minValue ? -1 : 1;
++ auto flipY = axisY.maxValue < axisY.minValue ? -1 : 1;
++
++ *renderX = m_areaWidth * flipX * x * m_maxHorizontal - m_horizontalOffset;
++ *renderY = m_areaHeight - m_areaHeight * flipY * y * m_maxVertical
++ + m_verticalOffset;
++ }
++
++ void PointRenderer::reverseRenderCoordinates(AxisRenderer *axisRenderer,
++ QAbstractSeries *series,
++ qreal renderX,
++ qreal renderY,
++ qreal *origX,
++ qreal *origY)
++ {
++ auto &axisX = axisRenderer->getAxisX(series);
++ auto &axisY = axisRenderer->getAxisY(series);
++
++ if (m_polarView) {
++ const QPointF center = m_polarView->polarCenter();
++ const qreal dx = renderX - center.x();
++ const qreal dy = renderY - center.y();
++ const qreal radius = std::hypot(dx, dy);
++ qreal angleDeg = qRadiansToDegrees(std::atan2(dx, -dy));
++ if (angleDeg < 0)
++ angleDeg += 360.0;
++
++ const qreal polarRadius = m_polarView->polarRadius();
++ qreal x = axisX.minValue + (angleDeg / 360.0) * axisX.valueRange;
++ qreal y = axisY.minValue
++ + (polarRadius != 0 ? radius / polarRadius : 0) * axisY.valueRange;
++
++ if (axisX.isLogarithmic)
++ x = pow(axisX.logBase, x);
++ if (axisY.isLogarithmic)
++ y = pow(axisY.logBase, y);
++
++ *origX = x;
++ *origY = y;
++ return;
++ }
++
++ if (m_graph->orientation() != Qt::Vertical) {
++ std::swap(renderX, renderY);
++ renderY = m_areaHeight - renderY;
++ }
++
++ auto flipX = axisX.maxValue < axisX.minValue ? -1 : 1;
++ auto flipY = axisY.maxValue < axisY.minValue ? -1 : 1;
++
++ float x = (renderX + m_horizontalOffset) / (m_areaWidth * flipX * m_maxHorizontal);
++ float y = (renderY - m_areaHeight - m_verticalOffset)
++ / (-1 * m_areaHeight * flipY * m_maxVertical);
++
++ if (axisX.isLogarithmic)
++ x = pow(axisX.logBase, x);
++
++ if (axisY.isLogarithmic)
++ y = pow(axisY.logBase, y);
++
++ *origX = x;
++ *origY = y;
++
++ }
++
++ PointRenderer::SeriesStyle PointRenderer::getSeriesStyle(PointGroup *group)
++ {
++ auto theme = m_graph->theme();
++
++ const auto &seriesColors = theme->seriesColors();
++ const auto &borderColors = theme->borderColors();
++
++ qsizetype index = group->colorIndex % seriesColors.size();
++ QColor color = group->series->color().alpha() != 0 ? group->series->color() : seriesColors.at(index);
++ color.setAlpha(color.alpha() * group->series->opacity());
++
++ QColor selectedColor = group->series->selectedColor().alpha() != 0
++ ? group->series->selectedColor()
++ : m_graph->theme()->singleHighlightColor();
++ selectedColor.setAlpha(selectedColor.alpha() * group->series->opacity());
++
++ index = group->colorIndex % borderColors.size();
++ QColor borderColor = borderColors.at(index);
++ borderColor.setAlpha(borderColor.alpha() * group->series->opacity());
++ qreal borderWidth = theme->borderWidth();
++
++ return {
++ color,
++ selectedColor,
++ borderColor,
++ borderWidth
++ };
++ }
++
++ void PointRenderer::updatePointDelegate(
++ QXYSeries *series, PointGroup *group, qsizetype pointIndex, qreal x, qreal y)
++ {
++ const auto style = getSeriesStyle(group);
++
++ auto marker = group->markers[pointIndex];
++ auto &rect = group->rects[pointIndex];
++
++ if (marker->property(TAG_POINT_SELECTED).isValid())
++ marker->setProperty(TAG_POINT_SELECTED, series->isPointSelected(pointIndex));
++ if (marker->property(TAG_POINT_COLOR).isValid())
++ marker->setProperty(TAG_POINT_COLOR, style.color);
++ if (marker->property(TAG_POINT_BORDER_COLOR).isValid())
++ marker->setProperty(TAG_POINT_BORDER_COLOR, style.borderColor);
++ if (marker->property(TAG_POINT_BORDER_WIDTH).isValid())
++ marker->setProperty(TAG_POINT_BORDER_WIDTH, style.borderWidth);
++ if (marker->property(TAG_POINT_SELECTED_COLOR).isValid())
++ marker->setProperty(TAG_POINT_SELECTED_COLOR, style.selectedColor);
++ const auto point = series->points().at(pointIndex);
++ if (marker->property(TAG_POINT_VALUE_X).isValid())
++ marker->setProperty(TAG_POINT_VALUE_X, point.x());
++ if (marker->property(TAG_POINT_VALUE_Y).isValid())
++ marker->setProperty(TAG_POINT_VALUE_Y, point.y());
++ if (marker->property(TAG_POINT_INDEX).isValid())
++ marker->setProperty(TAG_POINT_INDEX, pointIndex);
++
++ marker->setX(x - marker->width() / 2.0);
++ marker->setY(y - marker->height() / 2.0);
++ marker->setVisible(isValidValue(x) && isValidValue(y));
++
++ rect = QRectF(x - marker->width() / 2.0,
++ y - marker->height() / 2.0,
++ marker->width(),
++ marker->height());
++ }
++
++ void PointRenderer::hidePointDelegates(QXYSeries *series)
++ {
++ auto *group = m_groups.value(series);
++ if (group->currentMarker) {
++ for (int i = 0; i < group->markers.size(); ++i) {
++ auto *marker = group->markers[i];
++ marker->setVisible(false);
++ }
++ }
++ group->rects.clear();
++ }
++
++ void PointRenderer::destroyGroupMarkers(PointGroup *group, qsizetype keepCount)
++ {
++ for (qsizetype i = keepCount; i < group->markers.size(); ++i)
++ group->markers.at(i)->deleteLater();
++ group->markers.resize(keepCount);
++ group->dragHandlers.resize(keepCount);
++
++ if (group == m_pressedGroup && m_pressedPointIndex >= keepCount)
++ forgetPressedPoint(group->series);
++
++ if (group == m_currentHoverGroup && m_currentHoverPointIndex >= keepCount) {
++ emit group->series->hovered(false, m_currentHoverPoint);
++ m_currentHoverGroup = nullptr;
++ m_currentHoverPointIndex = -1;
++ }
++ }
++
++ void PointRenderer::updateLegendData(QXYSeries *series, QLegendData &legendData)
++ {
++ QList<QLegendData> legendDataList = {legendData};
++ series->d_func()->setLegendData(legendDataList);
++ }
++
++ void PointRenderer::onSingleTapped(QEventPoint eventPoint, Qt::MouseButton button)
++ {
++ Q_UNUSED(button)
++
++ for (auto &&group : m_groups) {
++ if (!group->series->isVisible())
++ continue;
++
++ if (!group->series->isSelectable() && !group->series->isDraggable())
++ continue;
++
++ int index = 0;
++ for (auto &&rect : group->rects) {
++ if (rect.contains(eventPoint.position())) {
++ emit group->series->clicked(group->series->at(index).toPoint());
++ return;
++ }
++ index++;
++ }
++ }
++ }
++
++ void PointRenderer::onDoubleTapped(QEventPoint eventPoint, Qt::MouseButton button)
++ {
++ Q_UNUSED(button)
++
++ for (auto &&group : m_groups) {
++ if (!group->series->isVisible())
++ continue;
++
++ if (!group->series->isSelectable() && !group->series->isDraggable())
++ continue;
++
++ int index = 0;
++ for (auto &&rect : group->rects) {
++ if (rect.contains(eventPoint.position())) {
++ emit group->series->doubleClicked(group->series->at(index).toPoint());
++ return;
++ }
++ index++;
++ }
++ }
++ }
++
++ void PointRenderer::onPressedChanged()
++ {
++ if (m_tapHandler->isPressed()) {
++ for (auto &&group : m_groups) {
++ if (!group->series->isVisible())
++ continue;
++
++ if (!group->series->isSelectable() && !group->series->isDraggable())
++ continue;
++
++ int index = 0;
++ for (auto &&rect : group->rects) {
++ if (rect.contains(m_tapHandler->point().position())) {
++ m_pressedGroup = group;
++ m_pressedPointIndex = index;
++ emit group->series->pressed(m_pressedGroup->series->at(index).toPoint());
++ }
++ index++;
++ }
++ }
++ } else {
++ if (m_pressedGroup
++ && m_pressedGroup->series->isSelectable()
++ && m_pressedGroup->series->isVisible()) {
++ if (m_pressedGroup->rects[m_pressedPointIndex].contains(
++ m_tapHandler->point().position())) {
++ if (m_pressedGroup->series->isPointSelected(m_pressedPointIndex))
++ m_pressedGroup->series->deselectPoint(m_pressedPointIndex);
++ else
++ m_pressedGroup->series->selectPoint(m_pressedPointIndex);
++ m_previousDelta = QPoint(0, 0);
++ emit m_pressedGroup->series->released(
++ m_pressedGroup->series->at(m_pressedPointIndex).toPoint());
++ }
++ }
++ }
++ }
++
++ void PointRenderer::adjustPressedPointIndexForRemoval(QXYSeries *series,
++ qsizetype removedIndex,
++ qsizetype removedCount)
++ {
++ if (!m_pressedGroup || m_pressedGroup->series != series)
++ return;
++
++ if (m_pressedPointIndex >= removedIndex + removedCount) {
++ m_pressedPointIndex -= removedCount;
++ } else if (m_pressedPointIndex >= removedIndex) {
++ m_pressedGroup = nullptr;
++ m_pressedPointIndex = 0;
++ }
++ }
++
++ void PointRenderer::adjustPressedPointIndexForInsertion(QXYSeries *series,
++ qsizetype insertedIndex,
++ qsizetype insertedCount)
++ {
++ if (!m_pressedGroup || m_pressedGroup->series != series)
++ return;
++
++ if (m_pressedPointIndex >= insertedIndex)
++ m_pressedPointIndex += insertedCount;
++ }
++
++ void PointRenderer::forgetPressedPoint(QXYSeries *series)
++ {
++ if (m_pressedGroup && m_pressedGroup->series == series) {
++ m_pressedGroup = nullptr;
++ m_pressedPointIndex = 0;
++ }
++ }
++
++ #if QT_CONFIG(graphs_2d_scatter)
++ void PointRenderer::updateScatterSeries(QScatterSeries *series, QLegendData &legendData)
++ {
++ auto group = m_groups.value(series);
++ const auto style = getSeriesStyle(group);
++ Q_TRACE(QGraphs2DPointRendererUpdateScatterSeries_entry);
++
++ if (series->isVisible()) {
++ auto &&points = series->points();
++ group->rects.resize(points.size());
++ for (int i = 0; i < points.size(); ++i) {
++ qreal x, y;
++ calculateRenderCoordinates(m_graph->m_axisRenderer,
++ series,
++ points[i].x(),
++ points[i].y(),
++ &x,
++ &y);
++ y *= series->valuesMultiplier();
++ if (group->currentMarker) {
++ updatePointDelegate(series, group, i, x, y);
++ } else {
++ auto &rect = group->rects[i];
++ qreal size = defaultSize(series);
++ rect = QRectF(x - size / 2.0, y - size / 2.0, size, size);
++ }
++ }
++ } else {
++ hidePointDelegates(series);
++ }
++ Q_TRACE(QGraphs2DPointRendererUpdateScatterSeries_exit);
++
++ legendData = { style.color, style.borderColor, series->name() };
++ }
++ #endif
++
++ #if QT_CONFIG(graphs_2d_line) || QT_CONFIG(graphs_2d_area)
++ void PointRenderer::updateLineSeries(QLineSeries *series, QLegendData &legendData)
++ {
++ auto group = m_groups.value(series);
++ const auto style = getSeriesStyle(group);
++
++ #if QT_CONFIG(graphs_2d_high_quality_backend)
++ if (!m_graph->useCanvasPainter()) {
++ group->shapePath->setStrokeColor(style.color);
++ group->shapePath->setStrokeWidth(series->width());
++ group->shapePath->setFillColor(QColorConstants::Transparent);
++
++ Qt::PenCapStyle capStyle = series->capStyle();
++ if (capStyle == Qt::PenCapStyle::SquareCap)
++ group->shapePath->setCapStyle(QQuickShapePath::CapStyle::SquareCap);
++ else if (capStyle == Qt::PenCapStyle::FlatCap)
++ group->shapePath->setCapStyle(QQuickShapePath::CapStyle::FlatCap);
++ else if (capStyle == Qt::PenCapStyle::RoundCap)
++ group->shapePath->setCapStyle(QQuickShapePath::CapStyle::RoundCap);
++
++ Qt::PenJoinStyle joinStyle = series->joinStyle();
++ if (joinStyle == Qt::PenJoinStyle::BevelJoin)
++ group->shapePath->setJoinStyle(QQuickShapePath::JoinStyle::BevelJoin);
++ else if (joinStyle == Qt::PenJoinStyle::MiterJoin)
++ group->shapePath->setJoinStyle(QQuickShapePath::JoinStyle::MiterJoin);
++ else if (joinStyle == Qt::PenJoinStyle::RoundJoin)
++ group->shapePath->setJoinStyle(QQuickShapePath::JoinStyle::RoundJoin);
++
++ group->shapePath->setStrokeStyle(QQuickShapePath::StrokeStyle(series->strokeStyle()));
++ group->shapePath->setDashOffset(series->dashOffset());
++ group->shapePath->setDashPattern(series->dashPattern());
++ }
++ #endif
++
++ auto &painterPath = group->painterPath;
++ painterPath.clear();
++
++ QPointF previousRenderCoordinates;
++
++ Q_TRACE(QGraphs2DPointRendererUpdateLineSeries_entry);
++ if (series->isVisible()) {
++ auto &&points = series->points();
++ group->rects.resize(points.size());
++ for (int i = 0; i < points.size(); ++i) {
++ qreal x, y;
++ calculateRenderCoordinates(m_graph->m_axisRenderer,
++ series,
++ points[i].x(),
++ points[i].y(),
++ &x,
++ &y);
++ y *= series->valuesMultiplier();
++ if (i == 0)
++ painterPath.moveTo(x, y);
++ else {
++ if (!isValidValue(previousRenderCoordinates.x()) || !isValidValue(previousRenderCoordinates.y())) {
++ if (isValidValue(x) && isValidValue(y))
++ painterPath.moveTo(x, y);
++ } else if (isValidValue(x) && isValidValue(y)) {
++ switch (series->lineStyle()) {
++ case QLineSeries::LineStyle::StepLeft:
++ painterPath.lineTo(x, previousRenderCoordinates.y());
++ painterPath.lineTo(x, y);
++ break;
++ case QLineSeries::LineStyle::StepRight:
++ painterPath.lineTo(previousRenderCoordinates.x(), y);
++ painterPath.lineTo(x, y);
++ break;
++ case QLineSeries::LineStyle::StepCenter: {
++ qreal half = (x + previousRenderCoordinates.x()) / 2;
++ painterPath.lineTo(half, previousRenderCoordinates.y());
++ painterPath.lineTo(half, y);
++ painterPath.lineTo(x, y);
++ } break;
++ default:
++ if (m_polarView && i > 0) {
++ // A direct chord between points more than half a
++ // turn apart is meaningless on a polar plot; draw
++ // via the center instead.
++ auto &axisX = m_graph->m_axisRenderer->getAxisX(series);
++ // Only the angular distance between the two points matters,
++ // and axisX.minValue cancels out of the difference, so derive
++ // it from the x delta rather than from two absolute angles.
++ const qreal angleDelta = axisX.valueRange != 0
++ ? qAbs((points[i].x() - points[i - 1].x()) / axisX.valueRange
++ * 360.0)
++ : 0;
++ if (angleDelta > 180.0) {
++ const QPointF center = m_polarView->polarCenter();
++ painterPath.lineTo(center.x(), center.y());
++ }
++ }
++ painterPath.lineTo(x, y);
++ break;
++ }
++ }
++ }
++ previousRenderCoordinates = {x, y};
++
++ if (group->currentMarker) {
++ updatePointDelegate(series, group, i, x, y);
++ } else {
++ auto &rect = group->rects[i];
++ qreal size = defaultSize(series);
++ rect = QRectF(x - size / 2.0, y - size / 2.0, size, size);
++ }
++ }
++ } else {
++ hidePointDelegates(series);
++ }
++
++ #if QT_CONFIG(graphs_2d_high_quality_backend)
++ if (!m_graph->useCanvasPainter())
++ group->shapePath->setPath(painterPath);
++ #endif
++
++ Q_TRACE(QGraphs2DPointRendererUpdateLineSeries_exit);
++ legendData = { style.color, style.borderColor, series->name() };
++ }
++ #endif
++
++ #if QT_CONFIG(graphs_2d_spline)
++ void PointRenderer::updateSplineSeries(QSplineSeries *series, QLegendData &legendData)
++ {
++ auto group = m_groups.value(series);
++ const auto style = getSeriesStyle(group);
++
++ #if QT_CONFIG(graphs_2d_high_quality_backend)
++ if (!m_graph->useCanvasPainter()) {
++ group->shapePath->setStrokeColor(style.color);
++ group->shapePath->setStrokeWidth(series->width());
++ group->shapePath->setFillColor(QColorConstants::Transparent);
++
++ Qt::PenCapStyle capStyle = series->capStyle();
++ if (capStyle == Qt::PenCapStyle::SquareCap)
++ group->shapePath->setCapStyle(QQuickShapePath::CapStyle::SquareCap);
++ else if (capStyle == Qt::PenCapStyle::FlatCap)
++ group->shapePath->setCapStyle(QQuickShapePath::CapStyle::FlatCap);
++ else if (capStyle == Qt::PenCapStyle::RoundCap)
++ group->shapePath->setCapStyle(QQuickShapePath::CapStyle::RoundCap);
++ }
++ #endif
++
++ auto &painterPath = group->painterPath;
++ painterPath.clear();
++
++ if (series->isVisible()) {
++ auto &&points = series->points();
++ group->rects.resize(points.size());
++ auto fittedPoints = series->getControlPoints();
++
++ Q_TRACE(QGraphs2DPointRendererUpdateSplineSeries_entry);
++ for (int i = 0, j = 0; i < points.size(); ++i, ++j) {
++ qreal x, y;
++ calculateRenderCoordinates(m_graph->m_axisRenderer,
++ series,
++ points[i].x(),
++ points[i].y(),
++ &x,
++ &y);
++
++ qreal valuesMultiplier = series->valuesMultiplier();
++ y *= valuesMultiplier;
++ if (i == 0) {
++ painterPath.moveTo(x, y);
++ } else {
++ qreal x1, y1, x2, y2;
++ calculateRenderCoordinates(m_graph->m_axisRenderer,
++ series,
++ fittedPoints[j - 1].x(),
++ fittedPoints[j - 1].y(),
++ &x1,
++ &y1);
++ calculateRenderCoordinates(m_graph->m_axisRenderer,
++ series,
++ fittedPoints[j].x(),
++ fittedPoints[j].y(),
++ &x2,
++ &y2);
++
++ y1 *= valuesMultiplier;
++ y2 *= valuesMultiplier;
++ painterPath.cubicTo(x1, y1, x2, y2, x, y);
++ ++j;
++ }
++
++ if (group->currentMarker) {
++ updatePointDelegate(series, group, i, x, y);
++ } else {
++ auto &rect = group->rects[i];
++ qreal size = defaultSize(series);
++ rect = QRectF(x - size / 2.0, y - size / 2.0, size, size);
++ }
++ }
++ Q_TRACE(QGraphs2DPointRendererUpdateSplineSeries_exit);
++ } else {
++ hidePointDelegates(series);
++ }
++
++ #if QT_CONFIG(graphs_2d_high_quality_backend)
++ if (!m_graph->useCanvasPainter())
++ group->shapePath->setPath(painterPath);
++ #endif
++
++ legendData = { style.color, style.borderColor, series->name() };
++ }
++ #endif
++
++ void PointRenderer::handlePolish(QXYSeries *series)
++ {
++ auto theme = m_graph->theme();
++ if (!theme) {
++ qCCritical(lcCritical2D, "Theme not found");
++ return;
++ }
++
++ if (!m_graph->m_axisRenderer) {
++ qCCritical(lcCritical2D, "Axis renderer not found.");
++ return;
++ }
++
++ if (series->points().isEmpty()) {
++ auto group = m_groups.value(series);
++
++ if (group) {
++ #if QT_CONFIG(graphs_2d_high_quality_backend)
++ if (group->shapePath) {
++ auto &painterPath = group->painterPath;
++ painterPath.clear();
++ group->shapePath->setPath(painterPath);
++ }
++ #endif
++
++ destroyGroupMarkers(group);
++ }
++
++ return;
++ }
++
++ if (width() <= 0 || height() <= 0)
++ return;
++
++ m_areaWidth = width();
++ m_areaHeight = height();
++
++ auto &axisX = m_graph->m_axisRenderer->getAxisX(series);
++ auto &axisY = m_graph->m_axisRenderer->getAxisY(series);
++
++ m_maxVertical = axisY.valueRange > 0 ? 1.0 / axisY.valueRange : 100.0;
++ m_maxHorizontal = axisX.valueRange > 0 ? 1.0 / axisX.valueRange : 100.0;
++
++ auto vmin = axisY.minValue > axisY.maxValue ? std::abs(axisY.minValue) : axisY.minValue;
++ m_verticalOffset = (vmin / axisY.valueRange) * m_areaHeight;
++
++ auto hmin = axisX.minValue > axisX.maxValue ? std::abs(axisX.minValue) : axisX.minValue;
++ m_horizontalOffset = (hmin / axisX.valueRange) * m_areaWidth;
++
++ if (!m_groups.contains(series)) {
++ PointGroup *group = new PointGroup();
++ group->series = series;
++ m_groups.insert(series, group);
++
++ connect(series, &QXYSeries::pointRemoved, this, [this, series](qsizetype index) {
++ adjustPressedPointIndexForRemoval(series, index, 1);
++ });
++ connect(series, &QXYSeries::pointsRemoved, this,
++ [this, series](qsizetype index, qsizetype count) {
++ adjustPressedPointIndexForRemoval(series, index, count);
++ });
++ connect(series, &QXYSeries::pointAdded, this, [this, series](qsizetype index) {
++ adjustPressedPointIndexForInsertion(series, index, 1);
++ });
++ connect(series, &QXYSeries::pointsAdded, this,
++ [this, series](qsizetype start, qsizetype end) {
++ adjustPressedPointIndexForInsertion(series, start, end - start + 1);
++ });
++ connect(series, &QXYSeries::pointsReplaced, this, [this, series]() {
++ forgetPressedPoint(series);
++ });
++
++ #if QT_CONFIG(graphs_2d_high_quality_backend)
++ if (series->type() != QAbstractSeries::SeriesType::Scatter) {
++ group->shapePath = new QQuickShapePath(&m_shape);
++ group->shapePath->setAsynchronous(true);
++ auto data = m_shape.data();
++ data.append(&data, group->shapePath);
++ }
++ #endif
++ }
++
++ auto group = m_groups.value(series);
++
++ if (series->type() != QAbstractSeries::SeriesType::Scatter) {
++ #if QT_CONFIG(graphs_2d_high_quality_backend)
++ auto data = m_shape.data();
++ group->shapePath = qobject_cast<QQuickShapePath *>(data.at(&data, m_currentShapePathIndex));
++ #endif
++
++ m_currentShapePathIndex++;
++ }
++
++ qsizetype pointCount = series->points().size();
++
++ if ((series->type() == QAbstractSeries::SeriesType::Scatter) && !series->pointDelegate())
++ group->currentMarker = m_tempMarker;
++ else if (series->pointDelegate())
++ group->currentMarker = series->pointDelegate();
++ else
++ group->currentMarker = nullptr;
++
++ if (group->currentMarker != group->previousMarker)
++ destroyGroupMarkers(group);
++
++ group->previousMarker = group->currentMarker;
++
++ if (group->currentMarker) {
++ qsizetype markerCount = group->markers.size();
++ if (markerCount < pointCount) {
++ auto size = defaultSize();
++ for (qsizetype i = markerCount; i < pointCount; ++i) {
++ QQuickItem *item = qobject_cast<QQuickItem *>(
++ group->currentMarker->createWithInitialProperties({{"implicitWidth", size}, {"implicitHeight", size}},
++ group->currentMarker->creationContext()));
++ item->setParent(this);
++ item->setParentItem(this);
++
++ QQuickDragHandler *handler = new QQuickDragHandler(item);
++ handler->setEnabled(series->isDraggable());
++ connect(series, &QXYSeries::draggableChanged, this, [handler, series]() {
++ handler->setEnabled(series->isDraggable());
++ });
++ group->markers << item;
++ group->dragHandlers << handler;
++
++ connect(handler, &QQuickDragHandler::translationChanged, this, [&]() {
++ if (m_pressedGroup) {
++ float w = width();
++ float h = height();
++ double maxVertical
++ = axisY.valueRange > 0
++ ? 1.0 / axisY.valueRange
++ : 100.0;
++ double maxHorizontal
++ = axisX.valueRange > 0
++ ? 1.0 / axisX.valueRange
++ : 100.0;
++
++ if (m_pressedPointIndex < m_pressedGroup->dragHandlers.size()) {
++ QPoint currentDelta = m_pressedGroup->dragHandlers
++ .at(m_pressedPointIndex)
++ ->activeTranslation()
++ .toPoint();
++ QPoint delta = currentDelta - m_previousDelta;
++ m_previousDelta = currentDelta;
++
++ qreal deltaX = delta.x() / w / maxHorizontal;
++ qreal deltaY = -delta.y() / h / maxVertical;
++
++ QPointF point = m_pressedGroup->series->at(m_pressedPointIndex)
++ + QPointF(deltaX, deltaY);
++ m_pressedGroup->series->replace(m_pressedPointIndex, point);
++ }
++ }
++ });
++ connect(handler, &QQuickDragHandler::grabChanged, this,
++ [&](QPointingDevice::GrabTransition transition, QEventPoint point) {
++ Q_UNUSED(point)
++
++ if (transition == QPointingDevice::UngrabExclusive ||
++ transition == QPointingDevice::UngrabPassive) {
++ m_previousDelta = QPoint(0, 0);
++ }
++ });
++ }
++ } else if (markerCount > pointCount) {
++ destroyGroupMarkers(group, pointCount);
++ }
++ } else if (group->markers.size() > 0) {
++ destroyGroupMarkers(group);
++ }
++
++ for (auto &&marker : group->markers)
++ marker->setZ(group->series->zValue());
++
++ if (group->colorIndex < 0) {
++ group->colorIndex = m_graph->graphSeriesCount();
++ m_graph->setGraphSeriesCount(group->colorIndex + 1);
++ }
++
++ QLegendData legendData;
++ #if QT_CONFIG(graphs_2d_scatter)
++ if (auto scatter = qobject_cast<QScatterSeries *>(series))
++ updateScatterSeries(scatter, legendData);
++ #endif
++ #if QT_CONFIG(graphs_2d_scatter) && (QT_CONFIG(graphs_2d_line) || QT_CONFIG(graphs_2d_area))
++ else
++ #endif
++ #if QT_CONFIG(graphs_2d_line) || QT_CONFIG(graphs_2d_area)
++ if (auto line = qobject_cast<QLineSeries *>(series))
++ updateLineSeries(line, legendData);
++ #endif
++ #if (QT_CONFIG(graphs_2d_line) || QT_CONFIG(graphs_2d_area)) && QT_CONFIG(graphs_2d_spline)
++ else
++ #endif
++ #if QT_CONFIG(graphs_2d_spline)
++ if (auto spline = qobject_cast<QSplineSeries *>(series))
++ updateSplineSeries(spline, legendData);
++ #endif
++
++ updateLegendData(series, legendData);
++ }
++
++ void PointRenderer::afterPolish(QList<QAbstractSeries *> &cleanupSeries)
++ {
++ Q_TRACE_SCOPE(QGraphs2DPointRendererAfterPolish, static_cast<int>(cleanupSeries.count()));
++ Q_UNUSED(cleanupSeries);
++
++ #if QT_CONFIG(graphs_2d_high_quality_backend)
++ auto data = m_shape.data();
++ for (qsizetype i = m_currentShapePathIndex, count = data.count(&data); i < count; ++i) {
++ auto shapePath = qobject_cast<QQuickShapePath *>(data.at(&data, i));
++ if (shapePath) {
++ QPainterPath empty;
++ shapePath->setPath(empty);
++ }
++ }
++ #endif
++ }
++
++ void PointRenderer::updateSeries(QXYSeries *series)
++ {
++ Q_UNUSED(series);
++ }
++
++ void PointRenderer::seriesAboutToBeRemoved(QAbstractSeries *series)
++ {
++ if (auto *xySeries = qobject_cast<QXYSeries *>(series)) {
++ auto iter = m_groups.find(xySeries);
++
++ if (iter != m_groups.end()) {
++ auto group = (*iter);
++
++ disconnect(xySeries, &QXYSeries::pointRemoved, this, nullptr);
++ disconnect(xySeries, &QXYSeries::pointsRemoved, this, nullptr);
++ disconnect(xySeries, &QXYSeries::pointAdded, this, nullptr);
++ disconnect(xySeries, &QXYSeries::pointsAdded, this, nullptr);
++ disconnect(xySeries, &QXYSeries::pointsReplaced, this, nullptr);
++
++ destroyGroupMarkers(group);
++
++ delete *iter;
++ m_groups.erase(iter);
++ }
++ }
++ }
++
++ void PointRenderer::afterUpdate(QList<QAbstractSeries *> &cleanupSeries)
++ {
++ Q_UNUSED(cleanupSeries);
++ }
++
++ bool PointRenderer::handleHoverMove(QHoverEvent *event)
++ {
++ bool handled = false;
++ const QPointF &position = event->position();
++
++ PointGroup *hoveredGroup = nullptr;
++ qsizetype hoveredPointIndex = -1;
++ qreal closestPointDistance = -1;
++
++ for (auto &&group : m_groups) {
++ if (!group->series->isHoverable() || !group->series->isVisible())
++ continue;
++
++ qsizetype pointIndex = 0;
++ for (auto &&rect : group->rects) {
++ if (rect.contains(position.toPoint())) {
++ const QPointF &delta = position - rect.center();
++ const qreal distance = qSqrt(delta.x() * delta.x() + delta.y() * delta.y());
++ if (!hoveredGroup || distance < closestPointDistance) {
++ closestPointDistance = distance;
++ hoveredGroup = group;
++ hoveredPointIndex = pointIndex;
++ }
++ }
++ pointIndex++;
++ }
++
++ auto axisRenderer = group->series->graph()->m_axisRenderer;
++ auto &axisX = axisRenderer->getAxisX(group->series);
++ auto &axisY = axisRenderer->getAxisY(group->series);
++
++ bool isHNegative = axisX.maxValue < axisX.minValue;
++ bool isVNegative = axisY.maxValue < axisY.minValue;
++
++ if (group->series->type() == QAbstractSeries::SeriesType::Scatter) {
++ const QString &name = group->series->name();
++
++ bool hovering = false;
++
++ int index = 0;
++ for (auto &&rect : group->rects) {
++ if (rect.contains(position.toPoint())) {
++ if (!group->hover) {
++ group->hover = true;
++ emit group->series->hoverEnter(name, position, group->series->at(index));
++ }
++ emit group->series->hover(name, position, group->series->at(index));
++ hovering = true;
++ }
++ index++;
++ }
++
++ if (!hovering && group->hover) {
++ group->hover = false;
++ emit group->series->hoverExit(name, position);
++ }
++ } else {
++ const qreal x0 = event->position().x();
++ const qreal y0 = event->position().y();
++
++ const qreal hoverSize = defaultSize(group->series) / 2.0;
++ const QString &name = group->series->name();
++ auto &&points = group->series->points();
++ // True when line, false when spline
++ const bool isLine = group->series->type() == QAbstractSeries::SeriesType::Line;
++ if (points.size() >= 2) {
++ bool hovering = false;
++ auto subpath = group->painterPath.toSubpathPolygons();
++
++ if (group->painterPath.elementCount() != points.size())
++ m_graph->ensurePolished();
++
++ for (int i = 0; i < points.size() - 1; i++) {
++ qreal x1, y1, x2, y2;
++ if (i == 0) {
++ auto element1 = group->painterPath.elementAt(0);
++ auto element2 = group->painterPath.elementAt(isLine ? 1 : 3);
++ x1 = isHNegative ? element2.x : element1.x;
++ y1 = element1.y;
++ x2 = isHNegative ? element1.x : element2.x;
++ y2 = element2.y;
++ } else {
++ bool n = isVNegative | isHNegative;
++ // Each Spline (cubicTo) has 3 elements where third one is the x & y.
++ // So content of elements are:
++ // With Spline:
++ // [0] : MoveToElement
++ // [1] : 1. CurveToElement (c1x, c1y)
++ // [2] : 1. CurveToDataElement (c2x, c2y)
++ // [3] : 1. CurveToDataElement (x, y)
++ // [4] : 2. CurveToElement (c1x, c1y)
++ // ...
++ // With Line:
++ // [0] : MoveToElement
++ // [1] : 1. LineToElement (x, y)
++ // [2] : 2. LineToElement (x, y)
++ // ...
++ int element1Index = n ? (i + 1) : i;
++ int element2Index = n ? i : (i + 1);
++ element1Index = isLine ? element1Index : element1Index * 3;
++ element2Index = isLine ? element2Index : element2Index * 3;
++ auto element1 = group->painterPath.elementAt(element1Index);
++ auto element2 = group->painterPath.elementAt(element2Index);
++ x1 = element1.x;
++ y1 = element1.y;
++ x2 = element2.x;
++ y2 = element2.y;
++ }
++
++ if (isLine) {
++ qreal denominator = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1);
++ qreal hoverDistance = qAbs((x2 - x1) * (y1 - y0) - (x1 - x0) * (y2 - y1))
++ / qSqrt(denominator);
++
++ if (hoverDistance < hoverSize) {
++ qreal alpha = 0;
++ qreal extrapolation = 0;
++ if (x2 - x1 >= y2 - y1) {
++ if (x2 - x1 != 0) {
++ alpha = ((x2 - x1) - (x0 - x1)) / qAbs(x2 - x1);
++ extrapolation = hoverSize / qAbs(x2 - x1);
++ }
++ } else {
++ if (y2 - y1 != 0) {
++ alpha = ((y2 - y1) - (y0 - y1)) / qAbs(y2 - y1);
++ extrapolation = hoverSize / qAbs(y2 - y1);
++ }
++ }
++
++ if (alpha >= -extrapolation && alpha <= 1.0 + extrapolation) {
++ bool n = isVNegative | isHNegative;
++
++ const QPointF &point1 = points[n ? i + 1 : i];
++ const QPointF &point2 = points[n ? i : i + 1];
++
++ QPointF point = (point2 * (1.0 - alpha)) + (point1 * alpha);
++
++ if (!group->hover) {
++ group->hover = true;
++ group->series->setHovered(true);
++ emit group->series->hoverEnter(name, position, point);
++ }
++
++ emit group->series->hover(name, position, point);
++ hovering = true;
++ handled = true;
++ }
++ }
++ } else { // Spline
++ auto segments = subpath[0];
++
++ for (auto it = segments.begin(); it != segments.end(); ++it) {
++ if (std::next(it, 1) == segments.end())
++ break;
++
++ auto it2 = std::next(it, 1);
++
++ qreal denominator = (it2->x() - it->x()) * (it2->x() - it->x())
++ + (it2->y() - it->y()) * (it2->y() - it->y());
++ qreal hoverDistance = qAbs((it2->x() - it->x()) * (it->y() - y0)
++ - (it->x() - x0) * (it2->y() - it->y()))
++ / qSqrt(denominator);
++
++ if (hoverDistance < hoverSize) {
++ qreal alpha = 0;
++ qreal extrapolation = 0;
++ if (it2->x() - it->x() >= it2->y() - it->y()) {
++ if (it2->x() - it->x() != 0) {
++ alpha = ((it2->x() - it->x()) - (x0 - it->x()))
++ / qAbs(it2->x() - it->x());
++ extrapolation = hoverSize / qAbs(it2->x() - it->x());
++ }
++ } else {
++ if (it2->y() - it->y() != 0) {
++ alpha = ((it2->y() - it->y()) - (y0 - it->y()))
++ / qAbs(it2->y() - it->y());
++ extrapolation = hoverSize / qAbs(it2->y() - it->y());
++ }
++ }
++
++ if (alpha >= -extrapolation && alpha <= 1.0 + extrapolation) {
++ qreal cx1, cy1, cx2, cy2;
++
++ reverseRenderCoordinates(axisRenderer,
++ group->series,
++ it->x(),
++ it->y(),
++ &cx1,
++ &cy1);
++ reverseRenderCoordinates(axisRenderer,
++ group->series,
++ it2->x(),
++ it2->y(),
++ &cx2,
++ &cy2);
++
++ const QPointF &point1 = {cx1, cy1};
++ const QPointF &point2 = {cx2, cy2};
++
++ QPointF point = (point2 * (1.0 - alpha)) + (point1 * alpha);
++
++ if (!group->hover) {
++ group->hover = true;
++ emit group->series->hoverEnter(name, position, point);
++ }
++
++ emit group->series->hover(name, position, point);
++ hovering = true;
++ handled = true;
++ }
++ }
++ }
++ }
++ }
++
++ if (!hovering && group->hover) {
++ group->hover = false;
++ group->series->setHovered(false);
++ emit group->series->hoverExit(name, position);
++ handled = true;
++ }
++ }
++ }
++ }
++
++ if (hoveredGroup != m_currentHoverGroup || hoveredPointIndex != m_currentHoverPointIndex) {
++ if (m_currentHoverGroup)
++ emit m_currentHoverGroup->series->hovered(false, m_currentHoverPoint);
++ if (hoveredGroup) {
++ m_currentHoverPoint = hoveredGroup->series->at(hoveredPointIndex);
++ emit hoveredGroup->series->hovered(true, m_currentHoverPoint);
++ }
++ m_currentHoverGroup = hoveredGroup;
++ m_currentHoverPointIndex = hoveredPointIndex;
++ handled = true;
++ }
++
++ return handled;
++ }
++
++
++ QPointF PointRenderer::reverseRenderCoordinates(QAbstractSeries *series, qreal x, qreal y)
++ {
++ m_areaWidth = width();
++ m_areaHeight = height();
++
++ auto &axisX = m_graph->m_axisRenderer->getAxisX(series);
++ auto &axisY = m_graph->m_axisRenderer->getAxisY(series);
++
++ m_maxVertical = axisY.valueRange > 0 ? 1.0 / axisY.valueRange : 100.0;
++ m_maxHorizontal = axisX.valueRange > 0 ? 1.0 / axisX.valueRange : 100.0;
++
++ auto vmin = axisY.minValue > axisY.maxValue ? std::abs(axisY.minValue) : axisY.minValue;
++ m_verticalOffset = (vmin / axisY.valueRange) * m_areaHeight;
++
++ auto hmin = axisX.minValue > axisX.maxValue ? std::abs(axisX.minValue) : axisX.minValue;
++ m_horizontalOffset = (hmin / axisX.valueRange) * m_areaWidth;
++
++ qreal x0;
++ qreal y0;
++
++ reverseRenderCoordinates(m_graph->m_axisRenderer, series, x, y, &x0, &y0);
++
++ return QPointF(x0, y0);
++ }
++
++ QT_END_NAMESPACE
++
++ #include "moc_pointrenderer_p.cpp"