Revision control

++ // Copyright (C) 2024 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++
++ #include <QtGraphs/qpieseries.h>
++ #include <QtGraphs/qpieslice.h>
++ #include <QtQuick/private/qquicktaphandler_p.h>
++ #include <QtQuick/private/qquicktext_p.h>
++ #include "pierenderer_p.h"
++ #include <limits>
++ #include <private/pierenderer_p.h>
++ #include <private/qabstractseries_p.h>
++ #include <private/qgraphsview_p.h>
++ #include <private/qpieseries_p.h>
++ #include <private/qpieslice_p.h>
++ #include <private/qquickdraghandler_p.h>
++ #if QT_CONFIG(graphs_2d_high_quality_backend)
++ #include <private/qquickshape_p.h>
++ #endif
++ #if QT_CONFIG(graphs_2d_high_performance_backend)
++ #include <QtCanvasPainter/QCanvasPainter>
++ #endif
++
++ #include <qtgraphs_tracepoints_p.h>
++
++ QT_BEGIN_NAMESPACE
++
++ Q_TRACE_PREFIX(qtgraphs,
++ "QT_BEGIN_NAMESPACE" \
++ "class PieRenderer;" \
++ "QT_END_NAMESPACE"
++ )
++
++ Q_TRACE_POINT(qtgraphs, QGraphs2DPieRendererAfterPolish_entry, int cleanupSeriesCount);
++ Q_TRACE_POINT(qtgraphs, QGraphs2DPieRendererAfterPolish_exit);
++
++ Q_TRACE_POINT(qtgraphs, QGraphs2DPieRendererHandlePolish_entry, int sliceCount);
++ Q_TRACE_POINT(qtgraphs, QGraphs2DPieRendererHandlePolish_exit);
++
++ constexpr qreal qrealMax = std::numeric_limits<qreal>::max();
++
++ PieRenderer::PieRenderer(QGraphsView *graph, bool clipPlotArea)
++ : QQuickItem(graph)
++ , m_graph(graph)
++ , m_painterPath()
++ {
++ setFlag(QQuickItem::ItemHasContents);
++ setClip(clipPlotArea);
++
++ #if QT_CONFIG(graphs_2d_high_quality_backend)
++ m_shape = new QQuickShape(this);
++ m_shape->setParentItem(this);
++ m_shape->setPreferredRendererType(QQuickShape::CurveRenderer);
++ #endif
++
++ m_tapHandler = new QQuickTapHandler(this);
++ connect(m_tapHandler, &QQuickTapHandler::singleTapped, this, &PieRenderer::onSingleTapped);
++ connect(m_tapHandler, &QQuickTapHandler::doubleTapped, this, &PieRenderer::onDoubleTapped);
++ connect(m_tapHandler, &QQuickTapHandler::pressedChanged, this, &PieRenderer::onPressedChanged);
++
++ m_dragHandler = new QQuickDragHandler(this);
++ m_dragHandler->setTarget(nullptr);
++ connect(m_dragHandler, &QQuickDragHandler::grabChanged, this, &PieRenderer::onGrabChanged);
++ connect(m_dragHandler,
++ &QQuickDragHandler::translationChanged,
++ this,
++ &PieRenderer::onTranslationChanged);
++ }
++
++ PieRenderer::~PieRenderer() {}
++
++ void PieRenderer::setSize(QSizeF size)
++ {
++ QQuickItem::setSize(size);
++ }
++
++ #if QT_CONFIG(graphs_2d_high_performance_backend)
++ void PieRenderer::paintSnapshot(const PaintSnapshot &paintSnapshot, QCanvasPainter *p)
++ {
++ for (auto it = paintSnapshot.cbegin(); it != paintSnapshot.cend(); it++) {
++ const auto &data = it.value();
++
++ if (data.seriesVisible) {
++ // border color
++ QColor borderColor = data.resolvedBorderColor;
++
++ // border width
++ qreal borderWidth = data.resolvedBorderWidth;
++
++ // color
++ QColor color = data.resolvedColor;
++
++ p->setFillStyle(color);
++ p->setStrokeStyle(borderColor);
++ p->setLineWidth(borderWidth);
++ p->beginPath();
++ p->addPath(data.shapePainterPath);
++ p->fill();
++ p->stroke();
++ }
++
++ QColor labelTextColor = data.resolvedLabelTextColor;
++
++ if (data.seriesVisible && data.labelPathVisible) {
++ p->setStrokeStyle(labelTextColor);
++ p->beginPath();
++ p->addPath(data.labelPainterPath);
++ p->stroke();
++ }
++ }
++ }
++
++ void PieRenderer::synchronizeData()
++ {
++ m_paintSnapshot = m_activeSlices;
++ }
++
++ PieRenderer::PaintSnapshot PieRenderer::paintSnapshot() const
++ {
++ return m_paintSnapshot;
++ }
++ #endif
++
++ void PieRenderer::updateActiveSlices(QPieSeries *series, QList<QPieSlice *> slicelist)
++ {
++ for (QPieSlice *slice : std::as_const(slicelist)) {
++ QPieSlicePrivate *d = slice->d_func();
++ #if QT_CONFIG(graphs_2d_high_quality_backend)
++ QQuickShapePath *shapePath = d->m_shapePath;
++ QQuickShapePath *labelPath = d->m_labelPath;
++ auto labelElements = labelPath->pathElements();
++ auto pathElements = shapePath->pathElements();
++ #endif
++ auto labelItem = d->m_labelItem;
++
++ auto it = m_activeSlices.find(slice);
++
++ if (it == m_activeSlices.end()) {
++ #if QT_CONFIG(graphs_2d_high_quality_backend)
++ auto data = m_shape->data();
++ data.append(&data, shapePath);
++ #endif
++ SliceData sliceData{};
++ sliceData.initialized = false;
++ #if QT_CONFIG(graphs_2d_high_quality_backend)
++ sliceData.shapePath = shapePath;
++ #endif
++
++ it = m_activeSlices.insert(slice, sliceData);
++ }
++
++ it->seriesVisible = series->isVisible();
++
++ #if QT_CONFIG(graphs_2d_high_quality_backend)
++ QQuickShape *labelShape = d->m_labelShape;
++ labelShape->setVisible(series->isVisible() && d->m_isLabelVisible);
++ #endif
++ labelItem->setVisible(series->isVisible() && d->m_isLabelVisible);
++
++ updateActiveSlices(series, slice->subSlices());
++
++ if (!series->isVisible()) {
++ #if QT_CONFIG(graphs_2d_high_quality_backend)
++ pathElements.clear(&pathElements);
++ labelElements.clear(&labelElements);
++ #endif
++ continue;
++ }
++
++ #if QT_CONFIG(graphs_2d_high_quality_backend)
++ if (!shapePath->parent())
++ shapePath->setParent(m_shape);
++ #endif
++
++ if (!d->m_labelItem->parent()) {
++ d->m_labelItem->setParent(this);
++ d->m_labelItem->setParentItem(this);
++ }
++
++ #if QT_CONFIG(graphs_2d_high_quality_backend)
++ if (!labelShape->parent()) {
++ labelShape->setParent(this);
++ labelShape->setParentItem(this);
++ }
++ #endif
++ }
++ }
++
++ void PieRenderer::handlePolish(QPieSeries *series)
++ {
++ #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
++ auto slices = series->slices();
++ updateActiveSlices(series, slices);
++
++ if (!series->isVisible())
++ return;
++
++ QPointF center = QPointF(size().width() * series->horizontalPosition(),
++ size().height() * series->verticalPosition());
++ qreal radius = size().width() > size().height() ? size().height() : size().width();
++ radius *= (.5 * series->pieSize());
++
++ QGraphsTheme *theme = m_graph->theme();
++
++ if (!theme) {
++ qCCritical(lcCritical2D, "Theme not found.");
++ return;
++ }
++
++ if (m_colorIndex < 0)
++ m_colorIndex = m_graph->graphSeriesCount();
++ m_graph->setGraphSeriesCount(m_colorIndex + series->slices().size());
++
++ QList<QLegendData> legendDataList;
++ auto slicelist = series->slices();
++ Q_TRACE(QGraphs2DPieRendererHandlePolish_entry, static_cast<int>(slicelist.count()));
++ handleSlicesPolish(series, legendDataList, slicelist, series->startAngle(), series->endAngle(),
++ center, radius, 1.0);
++ Q_TRACE(QGraphs2DPieRendererHandlePolish_exit);
++
++ series->d_func()->setLegendData(legendDataList);
++ }
++
++ void PieRenderer::handleSlicesPolish(QPieSeries *series,
++ QList<QLegendData> &legendDataList,
++ QList<QPieSlice *> slicelist,
++ qreal startAngle,
++ qreal endAngle,
++ QPointF center,
++ qreal radius,
++ qreal radiusRatio)
++ {
++ QGraphsTheme *theme = m_graph->theme();
++
++ int sliceIndex = 0;
++ qreal sliceAngle = startAngle;
++ for (QPieSlice *slice : std::as_const(slicelist)) {
++ m_painterPath.clear();
++
++ auto *d = QPieSlicePrivate::get(slice);
++ d->setStartAngle(sliceAngle);
++ d->setAngleSpan((endAngle - startAngle) * slice->percentage()
++ * series->valuesMultiplier());
++
++ // update slice
++ #if QT_CONFIG(graphs_2d_high_quality_backend)
++ QQuickShapePath *shapePath = d->m_shapePath;
++ #endif
++ // border color
++ const auto &borderColors = theme->borderColors();
++ int index = sliceIndex % borderColors.size();
++ QColor borderColor = borderColors.at(index);
++ if (d->m_borderColor.isValid() && d->m_borderColor.alpha() != 0)
++ borderColor = d->m_borderColor;
++
++ // color
++ const auto &seriesColors = theme->seriesColors();
++ index = sliceIndex % seriesColors.size();
++ QColor color = seriesColors.at(index);
++ if (d->m_color.isValid() && d->m_color.alpha() != 0)
++ color = d->m_color;
++
++ // border width
++ qreal borderWidth = theme->borderWidth();
++ if (d->m_borderWidth >= 1.0)
++ borderWidth = d->m_borderWidth;
++
++ #if QT_CONFIG(graphs_2d_high_quality_backend)
++ shapePath->setStrokeColor(borderColor);
++ shapePath->setFillColor(color);
++ shapePath->setStrokeWidth(borderWidth);
++ #endif
++
++ QColor labelTextColor = theme->labelTextColor();
++ if (d->m_labelColor.isValid())
++ labelTextColor = d->m_labelColor;
++ d->m_labelItem->setColor(labelTextColor);
++ #if QT_CONFIG(graphs_2d_high_quality_backend)
++ d->m_labelPath->setStrokeColor(labelTextColor);
++ #endif
++
++ if (!m_activeSlices.contains(slice))
++ continue;
++
++ m_activeSlices[slice].sliceIndex = sliceIndex;
++ m_activeSlices[slice].resolvedColor = color;
++ m_activeSlices[slice].resolvedBorderColor = borderColor;
++ m_activeSlices[slice].resolvedBorderWidth = borderWidth;
++ m_activeSlices[slice].resolvedLabelTextColor = labelTextColor;
++ m_activeSlices[slice].labelPathVisible = d->m_isLabelPathVisible;
++
++ qreal radian = qDegreesToRadians(slice->startAngle());
++ qreal startBigX = radius * qSin(radian) * radiusRatio;
++ qreal startBigY = radius * qCos(radian) * radiusRatio;
++
++ qreal startSmallX = startBigX * series->holeSize();
++ qreal startSmallY = startBigY * series->holeSize();
++
++ qreal explodeDistance = .0;
++ if (slice->isExploded())
++ explodeDistance = slice->explodeDistanceFactor() * radius;
++ radian = qDegreesToRadians(slice->startAngle() + (slice->angleSpan() * .5));
++ qreal xShift = center.x() + (explodeDistance * qSin(radian));
++ qreal yShift = center.y() - (explodeDistance * qCos(radian));
++
++ qreal pointX = startBigY * qSin(radian) + startBigX * qCos(radian);
++ qreal pointY = startBigY * qCos(radian) - startBigX * qSin(radian);
++
++ qreal newCenterX = center.x() + (explodeDistance * qSin(radian));
++ qreal newCenterY = center.y() - (explodeDistance * qCos(radian));
++ QRectF rect(newCenterX - radius * radiusRatio,
++ newCenterY - radius * radiusRatio,
++ radius * 2 * radiusRatio,
++ radius * 2 * radiusRatio);
++
++ #if QT_CONFIG(graphs_2d_high_quality_backend)
++ shapePath->setStartX(center.x());
++ shapePath->setStartY(center.y());
++ #endif
++
++ if (series->holeSize() > 0) {
++ QRectF insideRect(center.x() - series->holeSize() * radius
++ + (explodeDistance * qSin(radian)),
++ center.y() - series->holeSize() * radius
++ - (explodeDistance * qCos(radian)),
++ series->holeSize() * radius * 2,
++ series->holeSize() * radius * 2);
++
++ m_painterPath.arcMoveTo(rect, -slice->startAngle() + 90);
++ m_painterPath.arcTo(rect, -slice->startAngle() + 90, -slice->angleSpan());
++ m_painterPath.arcTo(insideRect,
++ -slice->startAngle() + 90 - slice->angleSpan(),
++ slice->angleSpan());
++ m_painterPath.closeSubpath();
++ } else {
++ m_painterPath.moveTo(rect.center());
++ m_painterPath.arcTo(rect, -slice->startAngle() + 90, -slice->angleSpan());
++ m_painterPath.closeSubpath();
++ }
++
++ radian = qDegreesToRadians(slice->angleSpan());
++
++ pointX = startSmallY * qSin(radian) + startSmallX * qCos(radian);
++ pointY = startSmallY * qCos(radian) - startSmallX * qSin(radian);
++
++ d->m_largeArc = {xShift + pointX, yShift - pointY};
++
++ #if QT_CONFIG(graphs_2d_high_quality_backend)
++ if (!m_graph->useCanvasPainter())
++ shapePath->setPath(m_painterPath);
++ else
++ #endif
++ d->m_shapePainterPath = m_painterPath;
++
++ m_activeSlices[slice].shapePainterPath = std::exchange(m_painterPath, {});
++
++ radian = qDegreesToRadians(slice->startAngle() + (slice->angleSpan() * .5));
++ startBigX = radius * qSin(radian) * radiusRatio;
++ startBigY = radius * qCos(radian) * radiusRatio;
++
++ pointX = radius * (1.0 + d->m_labelArmLengthFactor) * qSin(radian);
++ pointY = radius * (1.0 + d->m_labelArmLengthFactor) * qCos(radian);
++
++ m_painterPath.moveTo(xShift + startBigX, yShift - startBigY);
++ m_painterPath.lineTo(xShift + pointX, yShift - pointY);
++
++ d->m_centerLine = {xShift + pointX, yShift - pointY};
++
++ d->m_labelArm = {xShift + pointX, yShift - pointY};
++
++ auto labelWidth = radian > M_PI ? -d->m_labelItem->width() : d->m_labelItem->width();
++ m_painterPath.lineTo(d->m_labelArm.x() + labelWidth, d->m_labelArm.y());
++
++ d->setLabelPosition(d->m_labelPosition);
++
++ #if QT_CONFIG(graphs_2d_high_quality_backend)
++ if (!m_graph->useCanvasPainter())
++ d->m_labelPath->setPath(m_painterPath);
++ else
++ #endif
++ d->m_labelPainterPath = m_painterPath;
++
++ m_activeSlices[slice].labelPainterPath = m_painterPath;
++
++ sliceAngle += slice->angleSpan();
++ sliceIndex++;
++
++ handleSlicesPolish(series,
++ legendDataList,
++ slice->subSlices(),
++ slice->startAngle(),
++ slice->startAngle() + slice->angleSpan(),
++ {newCenterX, newCenterY},
++ radius,
++ radiusRatio * slice->subSlicesRatio());
++
++ legendDataList.push_back({color, borderColor, d->m_labelText});
++ }
++ }
++
++ void PieRenderer::afterPolish(QList<QAbstractSeries *> &cleanupSeries)
++ {
++ Q_TRACE(QGraphs2DPieRendererAfterPolish_entry, static_cast<int>(cleanupSeries.count()));
++ Q_UNUSED(cleanupSeries);
++ }
++
++ void PieRenderer::handleSlicesAfterPolish(QList<QPieSlice *> slicelist)
++ {
++ for (QPieSlice *slice : std::as_const(slicelist)) {
++ const auto *d = QPieSlicePrivate::get(slice);
++ #if QT_CONFIG(graphs_2d_high_quality_backend)
++ auto labelElements = d->m_labelPath->pathElements();
++ auto shapeElements = d->m_shapePath->pathElements();
++
++ labelElements.clear(&labelElements);
++ shapeElements.clear(&shapeElements);
++ #endif
++
++ slice->deleteLater();
++ freeSlice(d);
++
++ m_activeSlices.remove(slice);
++
++ handleSlicesAfterPolish(slice->subSlices());
++ }
++ Q_TRACE(QGraphs2DPieRendererAfterPolish_exit);
++ }
++
++ void PieRenderer::updateSeries(QPieSeries *series)
++ {
++ auto needPolish = false;
++
++ for (auto &sliceData : m_activeSlices) {
++ if (!sliceData.initialized) {
++ sliceData.initialized = true;
++ needPolish = true;
++ }
++ }
++
++ if (needPolish)
++ handlePolish(series);
++ }
++
++ void PieRenderer::seriesAboutToBeRemoved(QAbstractSeries *series)
++ {
++ if (auto pieSeries = qobject_cast<QPieSeries *>(series)) {
++ handleSlicesAfterPolish(pieSeries->slices());
++ #if QT_CONFIG(graphs_2d_high_quality_backend)
++ rebuildShapePaths();
++ #endif
++ }
++ }
++
++ void PieRenderer::afterUpdate(QList<QAbstractSeries *> &cleanupSeries)
++ {
++ Q_UNUSED(cleanupSeries);
++ }
++
++ void PieRenderer::markedDeleted(QList<QPieSlice *> deleted)
++ {
++ auto emptyPath = QPainterPath{};
++
++ for (auto slice : deleted) {
++ const auto d = QPieSlicePrivate::get(slice);
++ #if QT_CONFIG(graphs_2d_high_quality_backend)
++ d->m_shapePath->setPath(emptyPath);
++ d->m_labelPath->setPath(emptyPath);
++ #endif
++ d->m_shapePainterPath.clear();
++ d->m_labelPainterPath.clear();
++ freeSlice(d);
++ m_activeSlices.remove(slice);
++ }
++ // We could mark m_currentHoverSlice null only if
++ // it matches to a deleted slice, but as removals
++ // affect other slices positions it is probably
++ // better to just always disable current hovering.
++ m_currentHoverSlice = nullptr;
++ #if QT_CONFIG(graphs_2d_high_quality_backend)
++ rebuildShapePaths();
++ #endif
++ }
++
++ bool PieRenderer::isPointInSlice(QPointF point, QPieSlice *slice, qreal *angle)
++ {
++ QPieSeries* series = slice->series();
++ QPointF center = QPointF(size().width() * series->horizontalPosition(),
++ size().height() * series->verticalPosition());
++ qreal radius = size().width() > size().height() ? size().height() : size().width();
++ radius *= (.5 * series->pieSize());
++
++ qreal explodeDistance = .0;
++ if (slice->isExploded())
++ explodeDistance = slice->explodeDistanceFactor() * radius;
++ qreal radian = qDegreesToRadians(slice->startAngle() + (slice->angleSpan() * .5));
++ qreal xShift = center.x() + explodeDistance * qSin(radian);
++ qreal yShift = center.y() - explodeDistance * qCos(radian);
++
++ QPointF adjustedPosition = QPointF(point.x() - xShift,
++ point.y() - yShift);
++ qreal foundAngle = qRadiansToDegrees(qAtan2(adjustedPosition.y(), adjustedPosition.x())) + 90;
++ if (foundAngle < 0)
++ foundAngle += 360;
++
++ if (angle)
++ *angle = foundAngle;
++
++ // Check if we are in a sub slice
++ if (isPointInSubSlices(point, slice))
++ return false;
++
++ QPieSlicePrivate *d = slice->d_func();
++ QPainterPath painterPath;
++ #if QT_CONFIG(graphs_2d_high_quality_backend)
++ QQuickShapePath *shapePath = d->m_shapePath;
++ if (!m_graph->useCanvasPainter())
++ painterPath = shapePath->path();
++ else
++ #endif
++ painterPath = d->m_shapePainterPath;
++ return painterPath.contains(point);
++ }
++
++ bool PieRenderer::isPointInSubSlices(QPointF point, QPieSlice *slice)
++ {
++ auto slices = slice->subSlices();
++ for (const auto &subSlice : std::as_const(slices)) {
++ QPieSlicePrivate *d = subSlice->d_func();
++ QPainterPath painterPath;
++
++ #if QT_CONFIG(graphs_2d_high_quality_backend)
++ QQuickShapePath *shapePath = d->m_shapePath;
++ if (!m_graph->useCanvasPainter())
++ painterPath = shapePath->path();
++ else
++ #endif
++ painterPath = d->m_shapePainterPath;
++
++ if (painterPath.contains(point))
++ return true;
++ if (isPointInSubSlices(point, subSlice))
++ return true;
++ }
++ return false;
++ }
++
++ qreal PieRenderer::distanceToSegment(const QVector2D p, const QVector2D segmentStart,
++ const QVector2D segmentEnd)
++ {
++ static qreal maxdistance = 15.0;
++ QVector2D line(segmentEnd - segmentStart);
++ QVector2D startToP(p - segmentStart);
++ qreal t = QVector2D::dotProduct(startToP, line) / line.lengthSquared();
++
++ t = qBound(0.0, t, 1.0);
++ QVector2D proj = segmentStart + t * line;
++ qreal distance = (p - proj).length();
++ return distance <= maxdistance ? distance : qrealMax;
++ }
++
++ void PieRenderer::freeSlice(const QPieSlicePrivate *privSlice)
++ {
++ #if QT_CONFIG(graphs_2d_high_quality_backend)
++ privSlice->m_labelShape->deleteLater();
++ privSlice->m_shapePath->deleteLater();
++ #endif
++ privSlice->m_labelItem->deleteLater();
++ }
++
++ #if QT_CONFIG(graphs_2d_high_quality_backend)
++ // QQuickShape keeps raw pointers to its shape paths and never drops a path that gets destroyed,
++ // so a path freed by freeSlice() would stay in the shape's list and be dereferenced on the next
++ // path change. There is no way to remove a single path, so reset the list and re-add the paths
++ // of the slices that are still active. The shape draws its paths in list order, so re-add them
++ // in series and slice order to keep the original stacking.
++ void PieRenderer::rebuildShapePaths()
++ {
++ auto data = m_shape->data();
++ data.clear(&data);
++ for (QObject *object : std::as_const(m_graph->m_seriesList)) {
++ if (auto pieSeries = qobject_cast<QPieSeries *>(object))
++ appendShapePaths(data, pieSeries->slices());
++ }
++ }
++
++ void PieRenderer::appendShapePaths(QQmlListProperty<QObject> &data,
++ const QList<QPieSlice *> &slicelist)
++ {
++ for (QPieSlice *slice : slicelist) {
++ auto it = m_activeSlices.constFind(slice);
++ if (it != m_activeSlices.cend())
++ data.append(&data, it->shapePath);
++ appendShapePaths(data, slice->subSlices());
++ }
++ }
++ #endif
++
++ bool PieRenderer::handleHoverMove(QHoverEvent *event)
++ {
++ bool handled = false;
++ const QPointF &position = event->position();
++
++ bool hovering = false;
++ QList<QPieSlice *> list = m_activeSlices.keys();
++ for (const auto &slice : std::as_const(list)) {
++ if (!slice->series()->isHoverable())
++ continue;
++
++ qreal angle;
++ if (isPointInSlice(position, slice, &angle)) {
++ const QString &name = slice->series()->name();
++ const QPointF value(slice->startAngle(), angle);
++
++ if (!m_currentHoverSlice) {
++ m_currentHoverSlice = slice;
++ slice->series()->setHovered(true);
++ emit slice->series()->hoverEnter(name, position, value);
++ }
++ if (m_currentHoverSlice != slice) {
++ slice->series()->setHovered(true);
++ emit m_currentHoverSlice->series()->hoverExit(name, position);
++ emit slice->series()->hoverEnter(name, position, value);
++ m_currentHoverSlice = slice;
++ }
++
++ emit slice->series()->hover(name, position, value);
++ hovering = true;
++ handled = true;
++ }
++ }
++
++ if (!hovering && m_currentHoverSlice) {
++ m_currentHoverSlice->series()->setHovered(false);
++ emit m_currentHoverSlice->series()->
++ hoverExit(m_currentHoverSlice->series()->name(), position);
++ m_currentHoverSlice = nullptr;
++ handled = true;
++ }
++ return handled;
++ }
++
++ void PieRenderer::onSingleTapped(QEventPoint eventPoint, Qt::MouseButton button)
++ {
++ Q_UNUSED(button)
++
++ QList<QPieSlice *> list = m_activeSlices.keys();
++ for (const auto &pieSlice : std::as_const(list)) {
++ if (!pieSlice->series()->isSelectable())
++ continue;
++
++ if (isPointInSlice(eventPoint.position(), pieSlice)) {
++ emit pieSlice->series()->clicked(pieSlice);
++ return;
++ }
++ }
++ }
++
++ void PieRenderer::onDoubleTapped(QEventPoint eventPoint, Qt::MouseButton button)
++ {
++ Q_UNUSED(button)
++
++ QList<QPieSlice *> list = m_activeSlices.keys();
++ for (const auto &pieSlice : std::as_const(list)) {
++ if (!pieSlice->series()->isSelectable())
++ continue;
++
++ if (isPointInSlice(eventPoint.position(), pieSlice)) {
++ emit pieSlice->series()->doubleClicked(pieSlice);
++ return;
++ }
++ }
++ }
++
++ void PieRenderer::onPressedChanged()
++ {
++ QList<QPieSlice *> list = m_activeSlices.keys();
++ for (const auto &pieSlice : std::as_const(list)) {
++ if (!pieSlice->series()->isSelectable())
++ continue;
++
++ if (isPointInSlice(m_tapHandler->point().position(), pieSlice)) {
++ if (m_tapHandler->isPressed())
++ emit pieSlice->series()->pressed(pieSlice);
++ else
++ emit pieSlice->series()->released(pieSlice);
++ return;
++ }
++ }
++ }
++
++ void PieRenderer::onGrabChanged(QPointingDevice::GrabTransition transition, QEventPoint eventPoint)
++ {
++ QList<QPieSlice *> slices = m_activeSlices.keys();
++ if (transition == QPointingDevice::GrabTransition::GrabPassive) {
++ const qreal radiusRatio = 1.0;
++ for (const auto &slice : std::as_const(slices)) {
++ if (isPointInSlice(eventPoint.position(), slice)) {
++ m_dragSlice = slice;
++ // Slice side length
++ qreal radius = size().width() > size().height() ? size().height() : size().width();
++ radius *= (.5 * slice->series()->pieSize());
++ m_dragState.dragSeriesRadius = radius;
++ qreal startAngle = -slice->startAngle() + 90;
++ qreal startRadian = qDegreesToRadians(startAngle);
++ qreal endRadian = qDegreesToRadians(startAngle - slice->angleSpan());
++ QVector2D center(size().width() * slice->series()->horizontalPosition(),
++ size().height() * slice->series()->verticalPosition());
++ m_dragState.dragSeriesCenter = center;
++ QVector2D leftEndPoint(center.x() + (radius * radiusRatio * qCos(startRadian)),
++ center.y() - (radius * radiusRatio * qSin(startRadian)));
++ QVector2D rightEndPoint(center.x() + (radius * radiusRatio * qCos(endRadian)),
++ center.y() - (radius * radiusRatio * qSin(endRadian)));
++
++ qreal d_left =
++ distanceToSegment(QVector2D(eventPoint.position()), center, leftEndPoint);
++ qreal d_right =
++ distanceToSegment(QVector2D(eventPoint.position()), center, rightEndPoint);
++
++ if (d_left < qrealMax && d_left < d_right)
++ m_closerEdge = Qt::LeftEdge;
++ else if (d_right < qrealMax && d_left > d_right)
++ m_closerEdge = Qt::RightEdge;
++ else
++ return;
++
++ m_dragState.dragging = true;
++ break;
++ }
++ }
++ } else if (transition == QPointingDevice::UngrabPassive) {
++ m_dragSlice = nullptr;
++ m_dragState.dragging = false;
++ }
++ }
++
++ void PieRenderer::onTranslationChanged(QVector2D delta)
++ {
++ if (!m_dragState.dragging)
++ return;
++
++ qreal startAngle = -m_dragSlice->startAngle() + 90;
++ qreal startRadian = qDegreesToRadians(startAngle);
++ qreal endRadian = qDegreesToRadians(startAngle - m_dragSlice->angleSpan());
++ QVector2D leftEndPoint(
++ m_dragState.dragSeriesCenter.x() + (m_dragState.dragSeriesRadius * qCos(startRadian)),
++ m_dragState.dragSeriesCenter.y() - (m_dragState.dragSeriesRadius * qSin(startRadian)));
++ QVector2D rightEndPoint(
++ m_dragState.dragSeriesCenter.x() + (m_dragState.dragSeriesRadius * qCos(endRadian)),
++ m_dragState.dragSeriesCenter.y() - (m_dragState.dragSeriesRadius * qSin(endRadian)));
++ // Calculate the edge
++ QVector2D edgeCenter;
++ if (m_closerEdge == Qt::LeftEdge)
++ edgeCenter = (leftEndPoint - m_dragState.dragSeriesCenter) / 2;
++ else if (m_closerEdge == Qt::RightEdge)
++ edgeCenter = (rightEndPoint - m_dragState.dragSeriesCenter) / 2;
++
++ QVector2D perpendicular(-edgeCenter.y(), edgeCenter.x());
++ perpendicular.normalize();
++ delta.normalize();
++ float dragAmount = QVector2D::dotProduct(delta, perpendicular);
++ if (qAbs(dragAmount) < qCos((qDegreesToRadians(45))))
++ return;
++
++ const float sensitivity = 0.1f;
++ qreal change = dragAmount * sensitivity;
++ change = m_closerEdge == Qt::RightEdge ? change : change * -1;
++ m_dragSlice->setValue(m_dragSlice->value() + change);
++ }
++
++ QT_END_NAMESPACE
++
++ #include "moc_pierenderer_p.cpp"