summaryrefslogtreecommitdiffstats
path: root/src/charts/piechart/piesliceitem.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/charts/piechart/piesliceitem.cpp')
-rw-r--r--src/charts/piechart/piesliceitem.cpp319
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, &centerAngle, &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
+