##// END OF EJS Templates
Check that areaseries is actually in chart before trying to update....
Check that areaseries is actually in chart before trying to update. When a line series is used both as a series on chart and as an area series boundary, but not at the same time, animation can sometimes get confused as to what series to update if the area series is removed from the chart and the line series is added to the chart while a series animation is running. Task-number: QTRD-3445 Change-Id: Ia3d72d3ceba784b6e162b2c9b678acdc3e3ffcac Reviewed-by: Titta Heikkala <titta.heikkala@theqtcompany.com>

File last commit:

r2776:bc1f6aa59d42
r2800:891ed0097501
Show More
piesliceitem.cpp
334 lines | 12.1 KiB | text/x-c | CppLexer
/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd
** All rights reserved.
** For any questions to The Qt Company, please use contact form at http://qt.io
**
** This file is part of the Qt Charts module.
**
** Licensees holding valid commercial license for Qt may use this file in
** accordance with the Qt License Agreement provided with the Software
** or, alternatively, in accordance with the terms contained in a written
** agreement between you and The Qt Company.
**
** If you have questions regarding the use of this file, please use
** contact form at http://qt.io
**
****************************************************************************/
#include <private/piesliceitem_p.h>
#include <private/piechartitem_p.h>
#include <QtCharts/QPieSeries>
#include <QtCharts/QPieSlice>
#include <private/chartpresenter_p.h>
#include <QtGui/QPainter>
#include <QtCore/QtMath>
#include <QtWidgets/QGraphicsSceneEvent>
#include <QtCore/QTime>
#include <QtGui/QTextDocument>
#include <QtCore/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),
m_mousePressed(false)
{
setAcceptHoverEvents(true);
setAcceptedMouseButtons(Qt::MouseButtonMask);
setZValue(ChartPresenter::PieSeriesZValue);
setFlag(QGraphicsItem::ItemIsSelectable);
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 pressed(event->buttons());
m_mousePressed = true;
}
void PieSliceItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
emit released(event->buttons());
if (m_mousePressed)
emit clicked(event->buttons());
}
void PieSliceItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
{
// For Pie slice a press signal needs to be explicitly fired for mouseDoubleClickEvent
emit pressed(event->buttons());
emit doubleClicked(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