diff options
Diffstat (limited to 'src/charts/piechart/piesliceitem.cpp')
-rw-r--r-- | src/charts/piechart/piesliceitem.cpp | 319 |
1 files changed, 319 insertions, 0 deletions
diff --git a/src/charts/piechart/piesliceitem.cpp b/src/charts/piechart/piesliceitem.cpp new file mode 100644 index 00000000..70f03a94 --- /dev/null +++ b/src/charts/piechart/piesliceitem.cpp @@ -0,0 +1,319 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the Qt Enterprise Charts Add-on. +** +** $QT_BEGIN_LICENSE$ +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "piesliceitem_p.h" +#include "piechartitem_p.h" +#include "qpieseries.h" +#include "qpieslice.h" +#include "chartpresenter_p.h" +#include <QPainter> +#include <qmath.h> +#include <QGraphicsSceneEvent> +#include <QTime> +#include <QTextDocument> +#include <QDebug> + +QT_CHARTS_BEGIN_NAMESPACE + +QPointF offset(qreal angle, qreal length) +{ + qreal dx = qSin(angle * (M_PI / 180)) * length; + qreal dy = qCos(angle * (M_PI / 180)) * length; + return QPointF(dx, -dy); +} + +PieSliceItem::PieSliceItem(QGraphicsItem *parent) + : QGraphicsObject(parent), + m_hovered(false) +{ + setAcceptHoverEvents(true); + setAcceptedMouseButtons(Qt::MouseButtonMask); + setZValue(ChartPresenter::PieSeriesZValue); + m_labelItem = new QGraphicsTextItem(this); + m_labelItem->document()->setDocumentMargin(1.0); +} + +PieSliceItem::~PieSliceItem() +{ + // If user is hovering over the slice and it gets destroyed we do + // not get a hover leave event. So we must emit the signal here. + if (m_hovered) + emit hovered(false); +} + +QRectF PieSliceItem::boundingRect() const +{ + return m_boundingRect; +} + +QPainterPath PieSliceItem::shape() const +{ + // Don't include the label and label arm. + // This is used to detect a mouse clicks. We do not want clicks from label. + return m_slicePath; +} + +void PieSliceItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) +{ + painter->save(); + painter->setClipRect(parentItem()->boundingRect()); + painter->setPen(m_data.m_slicePen); + painter->setBrush(m_data.m_sliceBrush); + painter->drawPath(m_slicePath); + painter->restore(); + + if (m_data.m_isLabelVisible) { + painter->save(); + + // Pen for label arm not defined in the QPieSeries api, let's use brush's color instead + painter->setBrush(m_data.m_labelBrush); + + if (m_data.m_labelPosition == QPieSlice::LabelOutside) { + painter->setClipRect(parentItem()->boundingRect()); + painter->strokePath(m_labelArmPath, m_data.m_labelBrush.color()); + } + + painter->restore(); + } +} + +void PieSliceItem::hoverEnterEvent(QGraphicsSceneHoverEvent * /*event*/) +{ + m_hovered = true; + emit hovered(true); +} + +void PieSliceItem::hoverLeaveEvent(QGraphicsSceneHoverEvent * /*event*/) +{ + m_hovered = false; + emit hovered(false); +} + +void PieSliceItem::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + emit clicked(event->buttons()); +} + +void PieSliceItem::setLayout(const PieSliceData &sliceData) +{ + m_data = sliceData; + updateGeometry(); + update(); +} + +void PieSliceItem::updateGeometry() +{ + if (m_data.m_radius <= 0) + return; + + prepareGeometryChange(); + + // slice path + qreal centerAngle; + QPointF armStart; + m_slicePath = slicePath(m_data.m_center, m_data.m_radius, m_data.m_startAngle, m_data.m_angleSpan, ¢erAngle, &armStart); + + m_labelItem->setVisible(m_data.m_isLabelVisible); + + if (m_data.m_isLabelVisible) { + // text rect + m_labelTextRect = ChartPresenter::textBoundingRect(m_data.m_labelFont, + m_data.m_labelText, + 0); + + QString label(m_data.m_labelText); + m_labelItem->setDefaultTextColor(m_data.m_labelBrush.color()); + m_labelItem->setFont(m_data.m_labelFont); + + // text position + if (m_data.m_labelPosition == QPieSlice::LabelOutside) { + setFlag(QGraphicsItem::ItemClipsChildrenToShape, false); + + // label arm path + QPointF labelTextStart; + m_labelArmPath = labelArmPath(armStart, centerAngle, + m_data.m_radius * m_data.m_labelArmLengthFactor, + m_labelTextRect.width(), &labelTextStart); + + m_labelTextRect.moveBottomLeft(labelTextStart); + if (m_labelTextRect.left() < 0) + m_labelTextRect.setLeft(0); + else if (m_labelTextRect.left() < parentItem()->boundingRect().left()) + m_labelTextRect.setLeft(parentItem()->boundingRect().left()); + if (m_labelTextRect.right() > parentItem()->boundingRect().right()) + m_labelTextRect.setRight(parentItem()->boundingRect().right()); + + label = ChartPresenter::truncatedText(m_data.m_labelFont, m_data.m_labelText, + qreal(0.0), m_labelTextRect.width(), + m_labelTextRect.height(), m_labelTextRect); + m_labelArmPath = labelArmPath(armStart, centerAngle, + m_data.m_radius * m_data.m_labelArmLengthFactor, + m_labelTextRect.width(), &labelTextStart); + m_labelTextRect.moveBottomLeft(labelTextStart); + + m_labelItem->setTextWidth(m_labelTextRect.width() + + m_labelItem->document()->documentMargin()); + m_labelItem->setHtml(label); + m_labelItem->setRotation(0); + m_labelItem->setPos(m_labelTextRect.x(), m_labelTextRect.y() + 1.0); + } else { + // label inside + setFlag(QGraphicsItem::ItemClipsChildrenToShape); + m_labelItem->setTextWidth(m_labelTextRect.width() + + m_labelItem->document()->documentMargin()); + m_labelItem->setHtml(label); + + QPointF textCenter; + if (m_data.m_holeRadius > 0) { + textCenter = m_data.m_center + offset(centerAngle, m_data.m_holeRadius + + (m_data.m_radius + - m_data.m_holeRadius) / 2); + } else { + textCenter = m_data.m_center + offset(centerAngle, m_data.m_radius / 2); + } + m_labelItem->setPos(textCenter.x() - m_labelItem->boundingRect().width() / 2, + textCenter.y() - m_labelTextRect.height() / 2); + + QPointF labelCenter = m_labelItem->boundingRect().center(); + m_labelItem->setTransformOriginPoint(labelCenter); + + if (m_data.m_labelPosition == QPieSlice::LabelInsideTangential) { + m_labelItem->setRotation(m_data.m_startAngle + m_data.m_angleSpan / 2); + } else if (m_data.m_labelPosition == QPieSlice::LabelInsideNormal) { + if (m_data.m_startAngle + m_data.m_angleSpan / 2 < 180) + m_labelItem->setRotation(m_data.m_startAngle + m_data.m_angleSpan / 2 - 90); + else + m_labelItem->setRotation(m_data.m_startAngle + m_data.m_angleSpan / 2 + 90); + } else { + m_labelItem->setRotation(0); + } + } + // Hide label if it's outside the bounding rect of parent item + QRectF labelRect(m_labelItem->boundingRect()); + labelRect.moveTopLeft(m_labelItem->pos()); + if ((parentItem()->boundingRect().left() + < (labelRect.left() + m_labelItem->document()->documentMargin() + 1.0)) + && (parentItem()->boundingRect().right() + > (labelRect.right() - m_labelItem->document()->documentMargin() - 1.0)) + && (parentItem()->boundingRect().top() + < (labelRect.top() + m_labelItem->document()->documentMargin() + 1.0)) + && (parentItem()->boundingRect().bottom() + > (labelRect.bottom() - m_labelItem->document()->documentMargin() - 1.0))) + m_labelItem->show(); + else + m_labelItem->hide(); + } + + // bounding rect + if (m_data.m_isLabelVisible) + m_boundingRect = m_slicePath.boundingRect().united(m_labelArmPath.boundingRect()).united(m_labelTextRect); + else + m_boundingRect = m_slicePath.boundingRect(); + + // Inflate bounding rect by 2/3 pen width to make sure it encompasses whole slice also for thick pens + // and miter joins. + int penWidth = (m_data.m_slicePen.width() * 2) / 3; + m_boundingRect = m_boundingRect.adjusted(-penWidth, -penWidth, penWidth, penWidth); +} + +QPointF PieSliceItem::sliceCenter(QPointF point, qreal radius, QPieSlice *slice) +{ + if (slice->isExploded()) { + qreal centerAngle = slice->startAngle() + (slice->angleSpan() / 2); + qreal len = radius * slice->explodeDistanceFactor(); + point += offset(centerAngle, len); + } + return point; +} + +QPainterPath PieSliceItem::slicePath(QPointF center, qreal radius, qreal startAngle, qreal angleSpan, qreal *centerAngle, QPointF *armStart) +{ + // calculate center angle + *centerAngle = startAngle + (angleSpan / 2); + + // calculate slice rectangle + QRectF rect(center.x() - radius, center.y() - radius, radius * 2, radius * 2); + + // slice path + QPainterPath path; + if (m_data.m_holeRadius > 0) { + QRectF insideRect(center.x() - m_data.m_holeRadius, center.y() - m_data.m_holeRadius, m_data.m_holeRadius * 2, m_data.m_holeRadius * 2); + path.arcMoveTo(rect, -startAngle + 90); + path.arcTo(rect, -startAngle + 90, -angleSpan); + path.arcTo(insideRect, -startAngle + 90 - angleSpan, angleSpan); + path.closeSubpath(); + } else { + path.moveTo(rect.center()); + path.arcTo(rect, -startAngle + 90, -angleSpan); + path.closeSubpath(); + } + + // calculate label arm start point + *armStart = center; + *armStart += offset(*centerAngle, radius + PIESLICE_LABEL_GAP); + + return path; +} + +QPainterPath PieSliceItem::labelArmPath(QPointF start, qreal angle, qreal length, qreal textWidth, QPointF *textStart) +{ + // Normalize the angle to 0-360 range + // NOTE: We are using int here on purpose. Depenging on platform and hardware + // qreal can be a double, float or something the user gives to the Qt configure + // (QT_COORD_TYPE). Compilers do not seem to support modulo for double or float + // but there are fmod() and fmodf() functions for that. So instead of some #ifdef + // that might break we just use int. Precision for this is just fine for our needs. + int normalized = angle * 10.0; + normalized = normalized % 3600; + if (normalized < 0) + normalized += 3600; + angle = (qreal) normalized / 10.0; + + // prevent label arm pointing straight down because it will look bad + if (angle < 180 && angle > 170) + angle = 170; + if (angle > 180 && angle < 190) + angle = 190; + + // line from slice to label + QPointF parm1 = start + offset(angle, length); + + // line to underline the label + QPointF parm2 = parm1; + if (angle < 180) { // arm swings the other way on the left side + parm2 += QPointF(textWidth, 0); + *textStart = parm1; + } else { + parm2 += QPointF(-textWidth, 0); + *textStart = parm2; + } + + QPainterPath path; + path.moveTo(start); + path.lineTo(parm1); + path.lineTo(parm2); + + return path; +} + +#include "moc_piesliceitem_p.cpp" + +QT_CHARTS_END_NAMESPACE + |