diff options
Diffstat (limited to 'src/charts/piechart')
-rw-r--r-- | src/charts/piechart/piechart.pri | 26 | ||||
-rw-r--r-- | src/charts/piechart/piechartitem.cpp | 228 | ||||
-rw-r--r-- | src/charts/piechart/piechartitem_p.h | 86 | ||||
-rw-r--r-- | src/charts/piechart/pieslicedata_p.h | 141 | ||||
-rw-r--r-- | src/charts/piechart/piesliceitem.cpp | 319 | ||||
-rw-r--r-- | src/charts/piechart/piesliceitem_p.h | 91 | ||||
-rw-r--r-- | src/charts/piechart/qhpiemodelmapper.cpp | 267 | ||||
-rw-r--r-- | src/charts/piechart/qhpiemodelmapper.h | 70 | ||||
-rw-r--r-- | src/charts/piechart/qpiemodelmapper.cpp | 568 | ||||
-rw-r--r-- | src/charts/piechart/qpiemodelmapper.h | 69 | ||||
-rw-r--r-- | src/charts/piechart/qpiemodelmapper_p.h | 99 | ||||
-rw-r--r-- | src/charts/piechart/qpieseries.cpp | 947 | ||||
-rw-r--r-- | src/charts/piechart/qpieseries.h | 103 | ||||
-rw-r--r-- | src/charts/piechart/qpieseries_p.h | 93 | ||||
-rw-r--r-- | src/charts/piechart/qpieslice.cpp | 794 | ||||
-rw-r--r-- | src/charts/piechart/qpieslice.h | 147 | ||||
-rw-r--r-- | src/charts/piechart/qpieslice_p.h | 80 | ||||
-rw-r--r-- | src/charts/piechart/qvpiemodelmapper.cpp | 270 | ||||
-rw-r--r-- | src/charts/piechart/qvpiemodelmapper.h | 70 |
19 files changed, 4468 insertions, 0 deletions
diff --git a/src/charts/piechart/piechart.pri b/src/charts/piechart/piechart.pri new file mode 100644 index 00000000..2c45c4e6 --- /dev/null +++ b/src/charts/piechart/piechart.pri @@ -0,0 +1,26 @@ +INCLUDEPATH += $$PWD +DEPENDPATH += $$PWD + +SOURCES += \ + $$PWD/qpieseries.cpp \ + $$PWD/piesliceitem.cpp \ + $$PWD/piechartitem.cpp \ + $$PWD/qpieslice.cpp \ + $$PWD/qpiemodelmapper.cpp \ + $$PWD/qvpiemodelmapper.cpp \ + $$PWD/qhpiemodelmapper.cpp + +PRIVATE_HEADERS += \ + $$PWD/pieslicedata_p.h \ + $$PWD/piechartitem_p.h \ + $$PWD/piesliceitem_p.h \ + $$PWD/qpieslice_p.h \ + $$PWD/qpieseries_p.h \ + $$PWD/qpiemodelmapper_p.h + +PUBLIC_HEADERS += \ + $$PWD/qpieseries.h \ + $$PWD/qpieslice.h \ + $$PWD/qpiemodelmapper.h \ + $$PWD/qvpiemodelmapper.h \ + $$PWD/qhpiemodelmapper.h diff --git a/src/charts/piechart/piechartitem.cpp b/src/charts/piechart/piechartitem.cpp new file mode 100644 index 00000000..dbf7edf8 --- /dev/null +++ b/src/charts/piechart/piechartitem.cpp @@ -0,0 +1,228 @@ +/**************************************************************************** +** +** 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 "piechartitem_p.h" +#include "piesliceitem_p.h" +#include "qpieslice.h" +#include "qpieslice_p.h" +#include "qpieseries.h" +#include "qpieseries_p.h" +#include "chartpresenter_p.h" +#include "chartdataset_p.h" +#include "pieanimation_p.h" +#include <QPainter> +#include <QTimer> + +QT_CHARTS_BEGIN_NAMESPACE + +PieChartItem::PieChartItem(QPieSeries *series, QGraphicsItem* item) + : ChartItem(series->d_func(),item), + m_series(series), + m_animation(0) +{ + Q_ASSERT(series); + + QPieSeriesPrivate *p = QPieSeriesPrivate::fromSeries(series); + connect(series, SIGNAL(visibleChanged()), this, SLOT(handleSeriesVisibleChanged())); + connect(series, SIGNAL(opacityChanged()), this, SLOT(handleOpacityChanged())); + connect(series, SIGNAL(added(QList<QPieSlice*>)), this, SLOT(handleSlicesAdded(QList<QPieSlice*>))); + connect(series, SIGNAL(removed(QList<QPieSlice*>)), this, SLOT(handleSlicesRemoved(QList<QPieSlice*>))); + connect(p, SIGNAL(horizontalPositionChanged()), this, SLOT(updateLayout())); + connect(p, SIGNAL(verticalPositionChanged()), this, SLOT(updateLayout())); + connect(p, SIGNAL(pieSizeChanged()), this, SLOT(updateLayout())); + connect(p, SIGNAL(calculatedDataChanged()), this, SLOT(updateLayout())); + + // Note: the following does not affect as long as the item does not have anything to paint + setZValue(ChartPresenter::PieSeriesZValue); + + // Note: will not create slice items until we have a proper rectangle to draw on. +} + +PieChartItem::~PieChartItem() +{ + // slices deleted automatically through QGraphicsItem + if (m_series) { + m_series->disconnect(this); + QPieSeriesPrivate::fromSeries(m_series)->disconnect(this); + } + foreach (QPieSlice *slice, m_sliceItems.keys()) { + slice->disconnect(this); + QPieSlicePrivate::fromSlice(slice)->disconnect(this); + } +} + +void PieChartItem::setAnimation(PieAnimation *animation) +{ + m_animation = animation; +} + +ChartAnimation *PieChartItem::animation() const +{ + return m_animation; +} + +void PieChartItem::handleDomainUpdated() +{ + QRectF rect(QPointF(0,0),domain()->size()); + if(m_rect!=rect){ + prepareGeometryChange(); + m_rect = rect; + updateLayout(); + + if (m_sliceItems.isEmpty()) + handleSlicesAdded(m_series->slices()); + } +} + +void PieChartItem::updateLayout() +{ + // find pie center coordinates + m_pieCenter.setX(m_rect.left() + (m_rect.width() * m_series->horizontalPosition())); + m_pieCenter.setY(m_rect.top() + (m_rect.height() * m_series->verticalPosition())); + + // find maximum radius for pie + m_pieRadius = m_rect.height() / 2; + if (m_rect.width() < m_rect.height()) + m_pieRadius = m_rect.width() / 2; + + m_holeSize = m_pieRadius; + // apply size factor + m_pieRadius *= m_series->pieSize(); + m_holeSize *= m_series->holeSize(); + + // set layouts for existing slice items + foreach (QPieSlice *slice, m_series->slices()) { + PieSliceItem *sliceItem = m_sliceItems.value(slice); + if (sliceItem) { + PieSliceData sliceData = updateSliceGeometry(slice); + if (m_animation) + presenter()->startAnimation(m_animation->updateValue(sliceItem, sliceData)); + else + sliceItem->setLayout(sliceData); + } + } + + update(); +} + +void PieChartItem::handleSlicesAdded(QList<QPieSlice *> slices) +{ + // delay creating slice items until there is a proper rectangle + if (!m_rect.isValid() && m_sliceItems.isEmpty()) + return; + + themeManager()->updateSeries(m_series); + + bool startupAnimation = m_sliceItems.isEmpty(); + + foreach(QPieSlice * slice, slices) { + PieSliceItem *sliceItem = new PieSliceItem(this); + m_sliceItems.insert(slice, sliceItem); + + // Note: no need to connect to slice valueChanged() etc. + // This is handled through calculatedDataChanged signal. + connect(slice, SIGNAL(labelChanged()), this, SLOT(handleSliceChanged())); + connect(slice, SIGNAL(labelVisibleChanged()), this, SLOT(handleSliceChanged())); + connect(slice, SIGNAL(penChanged()), this, SLOT(handleSliceChanged())); + connect(slice, SIGNAL(brushChanged()), this, SLOT(handleSliceChanged())); + connect(slice, SIGNAL(labelBrushChanged()), this, SLOT(handleSliceChanged())); + connect(slice, SIGNAL(labelFontChanged()), this, SLOT(handleSliceChanged())); + + QPieSlicePrivate *p = QPieSlicePrivate::fromSlice(slice); + connect(p, SIGNAL(labelPositionChanged()), this, SLOT(handleSliceChanged())); + connect(p, SIGNAL(explodedChanged()), this, SLOT(handleSliceChanged())); + connect(p, SIGNAL(labelArmLengthFactorChanged()), this, SLOT(handleSliceChanged())); + connect(p, SIGNAL(explodeDistanceFactorChanged()), this, SLOT(handleSliceChanged())); + + connect(sliceItem, SIGNAL(clicked(Qt::MouseButtons)), slice, SIGNAL(clicked())); + connect(sliceItem, SIGNAL(hovered(bool)), slice, SIGNAL(hovered(bool))); + + PieSliceData sliceData = updateSliceGeometry(slice); + if (m_animation) + presenter()->startAnimation(m_animation->addSlice(sliceItem, sliceData, startupAnimation)); + else + sliceItem->setLayout(sliceData); + } +} + +void PieChartItem::handleSlicesRemoved(QList<QPieSlice *> slices) +{ + themeManager()->updateSeries(m_series); + + foreach (QPieSlice *slice, slices) { + + PieSliceItem *sliceItem = m_sliceItems.value(slice); + + // this can happen if you call append() & remove() in a row so that PieSliceItem is not even created + if (!sliceItem) + continue; + + m_sliceItems.remove(slice); + slice->disconnect(this); + QPieSlicePrivate::fromSlice(slice)->disconnect(this); + + if (m_animation) + presenter()->startAnimation(m_animation->removeSlice(sliceItem)); // animator deletes the PieSliceItem + else + delete sliceItem; + } +} + +void PieChartItem::handleSliceChanged() +{ + QPieSlice *slice = qobject_cast<QPieSlice *>(sender()); + if (!slice) { + QPieSlicePrivate *slicep = qobject_cast<QPieSlicePrivate *>(sender()); + slice = slicep->q_ptr; + } + Q_ASSERT(m_sliceItems.contains(slice)); + + PieSliceItem *sliceItem = m_sliceItems.value(slice); + PieSliceData sliceData = updateSliceGeometry(slice); + if (m_animation) + presenter()->startAnimation(m_animation->updateValue(sliceItem, sliceData)); + else + sliceItem->setLayout(sliceData); + + update(); +} + +void PieChartItem::handleSeriesVisibleChanged() +{ + setVisible(m_series->isVisible()); +} + +void PieChartItem::handleOpacityChanged() +{ + setOpacity(m_series->opacity()); +} + +PieSliceData PieChartItem::updateSliceGeometry(QPieSlice *slice) +{ + PieSliceData &sliceData = QPieSlicePrivate::fromSlice(slice)->m_data; + sliceData.m_center = PieSliceItem::sliceCenter(m_pieCenter, m_pieRadius, slice); + sliceData.m_radius = m_pieRadius; + sliceData.m_holeRadius = m_holeSize; + return sliceData; +} + +#include "moc_piechartitem_p.cpp" + +QT_CHARTS_END_NAMESPACE diff --git a/src/charts/piechart/piechartitem_p.h b/src/charts/piechart/piechartitem_p.h new file mode 100644 index 00000000..a6511b42 --- /dev/null +++ b/src/charts/piechart/piechartitem_p.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +// W A R N I N G +// ------------- +// +// This file is not part of the Qt Enterprise Chart API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef PIECHARTITEM_H +#define PIECHARTITEM_H + +#include "qpieseries.h" +#include "chartitem_p.h" +#include "piesliceitem_p.h" +#include <QPointer> + +class QGraphicsItem; +QT_CHARTS_BEGIN_NAMESPACE +class QPieSlice; +class ChartPresenter; +class PieAnimation; + +class PieChartItem : public ChartItem +{ + Q_OBJECT + +public: + explicit PieChartItem(QPieSeries *series, QGraphicsItem* item = 0); + ~PieChartItem(); + + // from QGraphicsItem + QRectF boundingRect() const { return m_rect; } + void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *) {} + +public Q_SLOTS: + // from Chart + virtual void handleDomainUpdated(); + + void updateLayout(); + void handleSlicesAdded(QList<QPieSlice *> slices); + void handleSlicesRemoved(QList<QPieSlice *> slices); + void handleSliceChanged(); + void handleSeriesVisibleChanged(); + void handleOpacityChanged(); + + void setAnimation(PieAnimation *animation); + ChartAnimation *animation() const; + +private: + PieSliceData updateSliceGeometry(QPieSlice *slice); + +private: + QHash<QPieSlice *, PieSliceItem *> m_sliceItems; + QPointer<QPieSeries> m_series; + QRectF m_rect; + QPointF m_pieCenter; + qreal m_pieRadius; + qreal m_holeSize; + PieAnimation *m_animation; + +}; + +QT_CHARTS_END_NAMESPACE + +#endif // PIECHARTITEM_H diff --git a/src/charts/piechart/pieslicedata_p.h b/src/charts/piechart/pieslicedata_p.h new file mode 100644 index 00000000..37985121 --- /dev/null +++ b/src/charts/piechart/pieslicedata_p.h @@ -0,0 +1,141 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +// W A R N I N G +// ------------- +// +// This file is not part of the Qt Enterprise Chart API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef PIESLICEDATA_P_H +#define PIESLICEDATA_P_H + +#include <qchartglobal.h> +#include <qpieslice.h> +#include <QPen> +#include <QBrush> + +QT_CHARTS_BEGIN_NAMESPACE + +template <class T> +class Themed : public T +{ +public: + Themed(): m_isThemed(true) {} + + inline T &operator=(const T &other) { return T::operator =(other); } + + inline bool operator!=(const T &other) const { return T::operator !=(other); } + inline bool operator!=(const Themed &other) const + { + if (T::operator !=(other)) + return true; + + if (m_isThemed != other.m_isThemed) + return true; + + return false; + } + + inline void setThemed(bool state) { m_isThemed = state; } + inline bool isThemed() const { return m_isThemed; } + +private: + bool m_isThemed; +}; + +class PieSliceData +{ +public: + PieSliceData() : + m_value(0), + m_isExploded(false), + m_explodeDistanceFactor(0.15), + m_isLabelVisible(false), + m_labelPosition(QPieSlice::LabelOutside), + m_labelArmLengthFactor(0.15), + m_percentage(0), + m_radius(0), + m_startAngle(0), + m_angleSpan(0), + m_holeRadius(0) + { + } + + bool operator!=(const PieSliceData &other) const { + if (!qFuzzyIsNull(m_value - other.m_value)) + return true; + + if (m_slicePen != other.m_slicePen || + m_sliceBrush != other.m_sliceBrush) + return true; + + if (m_isExploded != other.m_isExploded || + !qFuzzyIsNull(m_explodeDistanceFactor - other.m_explodeDistanceFactor)) + return true; + + if (m_isLabelVisible != other.m_isLabelVisible || + m_labelText != other.m_labelText || + m_labelFont != other.m_labelFont || + m_labelPosition != other.m_labelPosition || + !qFuzzyIsNull(m_labelArmLengthFactor - other.m_labelArmLengthFactor) || + m_labelBrush != other.m_labelBrush) + return true; + + if (!qFuzzyIsNull(m_percentage - other.m_percentage) || + m_center != other.m_center || + !qFuzzyIsNull(m_radius - other.m_radius) || + !qFuzzyIsNull(m_startAngle - other.m_startAngle) || + !qFuzzyIsNull(m_angleSpan - other.m_angleSpan)) + return true; + + return false; + } + + qreal m_value; + + Themed<QPen> m_slicePen; + Themed<QBrush> m_sliceBrush; + + bool m_isExploded; + qreal m_explodeDistanceFactor; + + bool m_isLabelVisible; + QString m_labelText; + Themed<QFont> m_labelFont; + QPieSlice::LabelPosition m_labelPosition; + qreal m_labelArmLengthFactor; + Themed<QBrush> m_labelBrush; + + qreal m_percentage; + QPointF m_center; + qreal m_radius; + qreal m_startAngle; + qreal m_angleSpan; + + qreal m_holeRadius; +}; + +QT_CHARTS_END_NAMESPACE + +#endif // PIESLICEDATA_P_H 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 + diff --git a/src/charts/piechart/piesliceitem_p.h b/src/charts/piechart/piesliceitem_p.h new file mode 100644 index 00000000..d6b7768c --- /dev/null +++ b/src/charts/piechart/piesliceitem_p.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +// W A R N I N G +// ------------- +// +// This file is not part of the Qt Enterprise Chart API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef PIESLICEITEM_H +#define PIESLICEITEM_H + +#include "qchartglobal.h" +#include "charttheme_p.h" +#include "qpieseries.h" +#include "pieslicedata_p.h" +#include <QGraphicsItem> +#include <QRectF> +#include <QColor> +#include <QPen> + +#define PIESLICE_LABEL_GAP 5 + +QT_CHARTS_BEGIN_NAMESPACE +class PieChartItem; +class PieSliceLabel; +class QPieSlice; + +class PieSliceItem : public QGraphicsObject +{ + Q_OBJECT + +public: + PieSliceItem(QGraphicsItem *parent = 0); + ~PieSliceItem(); + + // from QGraphicsItem + QRectF boundingRect() const; + QPainterPath shape() const; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + void hoverEnterEvent(QGraphicsSceneHoverEvent *event); + void hoverLeaveEvent(QGraphicsSceneHoverEvent *event); + void mousePressEvent(QGraphicsSceneMouseEvent *event); + + void setLayout(const PieSliceData &sliceData); + static QPointF sliceCenter(QPointF point, qreal radius, QPieSlice *slice); + +Q_SIGNALS: + void clicked(Qt::MouseButtons buttons); + void hovered(bool state); + +private: + void updateGeometry(); + QPainterPath slicePath(QPointF center, qreal radius, qreal startAngle, qreal angleSpan, qreal *centerAngle, QPointF *armStart); + QPainterPath labelArmPath(QPointF start, qreal angle, qreal length, qreal textWidth, QPointF *textStart); + +private: + PieSliceData m_data; + QRectF m_boundingRect; + QPainterPath m_slicePath; + QPainterPath m_labelArmPath; + QRectF m_labelTextRect; + bool m_hovered; + QGraphicsTextItem *m_labelItem; + + friend class PieSliceAnimation; +}; + +QT_CHARTS_END_NAMESPACE + +#endif // PIESLICEITEM_H diff --git a/src/charts/piechart/qhpiemodelmapper.cpp b/src/charts/piechart/qhpiemodelmapper.cpp new file mode 100644 index 00000000..b1c7d84f --- /dev/null +++ b/src/charts/piechart/qhpiemodelmapper.cpp @@ -0,0 +1,267 @@ +/**************************************************************************** +** +** 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 "qhpiemodelmapper.h" + +QT_CHARTS_BEGIN_NAMESPACE + +/*! + \class QHPieModelMapper + \inmodule Qt Charts + \brief Horizontal model mapper for pie series. + \mainclass + + Model mappers allow you to use QAbstractItemModel derived models as a data source for a chart series. + Horizontal model mapper is used to create a connection between QPieSeries and QAbstractItemModel derived model object that keeps the consecutive pie slices data in rows. + It is possible to use both QAbstractItemModel and QPieSeries model API. QHPieModelMapper makes sure that Pie and the model are kept in sync. + \note Used model has to support adding/removing rows/columns and modifying the data of the cells. +*/ +/*! + \qmltype HPieModelMapper + \instantiates QHPieModelMapper + \inqmlmodule QtCharts + + \brief Horizontal model mapper for pie series. + + HPieModelMapper allows you to use your own QAbstractItemModel derived model with data in rows as + a data source for a pie series. It is possible to use both QAbstractItemModel and PieSeries data + API to manipulate data. HPieModelMapper keeps the Pie and the model in sync. + + The following QML example would create a pie series with four slices (assuming the model has + at least five columns). Each slice would contain a label from row 1 and a value from row 2. + \code + HPieModelMapper { + series: pieSeries + model: customModel + labelsRow: 1 + valuesRow: 2 + firstColumn: 1 + columnCount: 4 + } + \endcode +*/ + +/*! + \property QHPieModelMapper::series + \brief Defines the QPieSeries object that is used by the mapper. + + All the data in the series is discarded when it is set to the mapper. + When new series is specified the old series is disconnected (it preserves its data) +*/ +/*! + \qmlproperty PieSeries HPieModelMapper::series + Defines the PieSeries object that is used by the mapper. If you define the mapper element as a child for a + PieSeries, leave this property undefined. All the data in the series is discarded when it is set to the mapper. + When new series is specified the old series is disconnected (it preserves its data). +*/ + +/*! + \property QHPieModelMapper::model + \brief Defines the model that is used by the mapper. +*/ +/*! + \qmlproperty SomeModel HPieModelMapper::model + The QAbstractItemModel based model that is used by the mapper. You need to implement the model + and expose it to QML. Note: the model has to support adding/removing rows/columns and modifying + the data of the cells. +*/ + +/*! + \property QHPieModelMapper::valuesRow + \brief Defines which row of the model is kept in sync with the values of the pie's slices. + + Default value is: -1 (invalid mapping) +*/ +/*! + \qmlproperty int HPieModelMapper::valuesRow + Defines which row of the model is kept in sync with the values of the pie's slices. Default value is: -1 (invalid + mapping). +*/ + +/*! + \property QHPieModelMapper::labelsRow + \brief Defines which row of the model is kept in sync with the labels of the pie's slices. + + Default value is: -1 (invalid mapping) +*/ +/*! + \qmlproperty int HPieModelMapper::labelsRow + Defines which row of the model is kept in sync with the labels of the pie's slices + Default value is: -1 (invalid mapping) +*/ + +/*! + \property QHPieModelMapper::firstColumn + \brief Defines which column of the model contains the first slice value. + + Minimal and default value is: 0 +*/ +/*! + \qmlproperty int HPieModelMapper::firstColumn + Defines which column of the model contains the first slice value. + The default value is 0. +*/ + +/*! + \property QHPieModelMapper::columnCount + \brief Defines the number of columns of the model that are mapped as the data for QPieSeries. + + Minimal and default value is: -1 (count limited by the number of columns in the model) +*/ +/*! + \qmlproperty int HPieModelMapper::columnCount + Defines the number of columns of the model that are mapped as the data for QPieSeries. The default value is + -1 (count limited by the number of columns in the model) +*/ + +/*! + \fn void QHPieModelMapper::seriesReplaced() + Emitted when the series to which mapper is connected to has changed. +*/ + +/*! + \fn void QHPieModelMapper::modelReplaced() + Emitted when the model to which mapper is connected to has changed. +*/ + +/*! + \fn void QHPieModelMapper::valuesRowChanged() + Emitted when the valuesRow has changed. +*/ + +/*! + \fn void QHPieModelMapper::labelsRowChanged() + Emitted when the labelsRow has changed. +*/ + +/*! + \fn void QHPieModelMapper::firstColumnChanged() + Emitted when the firstColumn has changed. +*/ + +/*! + \fn void QHPieModelMapper::columnCountChanged() + Emitted when the columnCount has changed. +*/ + +/*! + Constructs a mapper object which is a child of \a parent. +*/ +QHPieModelMapper::QHPieModelMapper(QObject *parent) : + QPieModelMapper(parent) +{ + setOrientation(Qt::Horizontal); +} + +QAbstractItemModel *QHPieModelMapper::model() const +{ + return QPieModelMapper::model(); +} + +void QHPieModelMapper::setModel(QAbstractItemModel *model) +{ + if (model != QPieModelMapper::model()) { + QPieModelMapper::setModel(model); + emit modelReplaced(); + } +} + +QPieSeries *QHPieModelMapper::series() const +{ + return QPieModelMapper::series(); +} + +void QHPieModelMapper::setSeries(QPieSeries *series) +{ + if (series != QPieModelMapper::series()) { + QPieModelMapper::setSeries(series); + emit seriesReplaced(); + } +} + +/*! + Returns which row of the model is kept in sync with the values of the pie's slices +*/ +int QHPieModelMapper::valuesRow() const +{ + return valuesSection(); +} + +/*! + Sets the model row that is kept in sync with the pie slices values. + Parameter \a valuesRow specifies the row of the model. +*/ +void QHPieModelMapper::setValuesRow(int valuesRow) +{ + if (valuesRow != valuesSection()) { + setValuesSection(valuesRow); + emit valuesRowChanged(); + } +} + +/*! + Returns which row of the model is kept in sync with the labels of the pie's slices +*/ +int QHPieModelMapper::labelsRow() const +{ + return labelsSection(); +} + +/*! + Sets the model row that is kept in sync with the pie's slices labels. + Parameter \a labelsRow specifies the row of the model. +*/ +void QHPieModelMapper::setLabelsRow(int labelsRow) +{ + if (labelsRow != labelsSection()) { + setLabelsSection(labelsRow); + emit labelsRowChanged(); + } +} + +int QHPieModelMapper::firstColumn() const +{ + return first(); +} + +void QHPieModelMapper::setFirstColumn(int firstColumn) +{ + if (firstColumn != first()) { + setFirst(firstColumn); + emit firstColumnChanged(); + } +} + +int QHPieModelMapper::columnCount() const +{ + return count(); +} + +void QHPieModelMapper::setColumnCount(int columnCount) +{ + if (columnCount != count()) { + setCount(columnCount); + emit columnCountChanged(); + } +} + +#include "moc_qhpiemodelmapper.cpp" + +QT_CHARTS_END_NAMESPACE diff --git a/src/charts/piechart/qhpiemodelmapper.h b/src/charts/piechart/qhpiemodelmapper.h new file mode 100644 index 00000000..50b5c6f2 --- /dev/null +++ b/src/charts/piechart/qhpiemodelmapper.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QHPIEMODELMAPPER_H +#define QHPIEMODELMAPPER_H + +#include <QtCharts/qpiemodelmapper.h> + +QT_CHARTS_BEGIN_NAMESPACE +/* Comment line for syncqt to generate the fwd-include correctly, due to QTBUG-22432 */ +class QT_CHARTS_EXPORT QHPieModelMapper : public QPieModelMapper +{ + Q_OBJECT + Q_PROPERTY(QPieSeries *series READ series WRITE setSeries NOTIFY seriesReplaced) + Q_PROPERTY(QAbstractItemModel *model READ model WRITE setModel NOTIFY modelReplaced) + Q_PROPERTY(int valuesRow READ valuesRow WRITE setValuesRow NOTIFY valuesRowChanged) + Q_PROPERTY(int labelsRow READ labelsRow WRITE setLabelsRow NOTIFY labelsRowChanged) + Q_PROPERTY(int firstColumn READ firstColumn WRITE setFirstColumn NOTIFY firstColumnChanged) + Q_PROPERTY(int columnCount READ columnCount WRITE setColumnCount NOTIFY columnCountChanged) + +public: + explicit QHPieModelMapper(QObject *parent = 0); + + QAbstractItemModel *model() const; + void setModel(QAbstractItemModel *model); + + QPieSeries *series() const; + void setSeries(QPieSeries *series); + + int valuesRow() const; + void setValuesRow(int valuesRow); + + int labelsRow() const; + void setLabelsRow(int labelsRow); + + int firstColumn() const; + void setFirstColumn(int firstColumn); + + int columnCount() const; + void setColumnCount(int columnCount); + +Q_SIGNALS: + void seriesReplaced(); + void modelReplaced(); + void valuesRowChanged(); + void labelsRowChanged(); + void firstColumnChanged(); + void columnCountChanged(); +}; + +QT_CHARTS_END_NAMESPACE + +#endif // QHPIEMODELMAPPER_H diff --git a/src/charts/piechart/qpiemodelmapper.cpp b/src/charts/piechart/qpiemodelmapper.cpp new file mode 100644 index 00000000..186be815 --- /dev/null +++ b/src/charts/piechart/qpiemodelmapper.cpp @@ -0,0 +1,568 @@ +/**************************************************************************** +** +** 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 "qpiemodelmapper.h" +#include "qpiemodelmapper_p.h" +#include "qpieseries.h" +#include "qpieslice.h" +#include <QAbstractItemModel> + +QT_CHARTS_BEGIN_NAMESPACE + +QPieModelMapper::QPieModelMapper(QObject *parent) + : QObject(parent), + d_ptr(new QPieModelMapperPrivate(this)) +{ +} + +QAbstractItemModel *QPieModelMapper::model() const +{ + Q_D(const QPieModelMapper); + return d->m_model; +} + +void QPieModelMapper::setModel(QAbstractItemModel *model) +{ + if (model == 0) + return; + + Q_D(QPieModelMapper); + if (d->m_model) { + disconnect(d->m_model, 0, d, 0); + } + + d->m_model = model; + d->initializePieFromModel(); + // connect signals from the model + connect(d->m_model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), d, SLOT(modelUpdated(QModelIndex,QModelIndex))); + connect(d->m_model, SIGNAL(rowsInserted(QModelIndex,int,int)), d, SLOT(modelRowsAdded(QModelIndex,int,int))); + connect(d->m_model, SIGNAL(rowsRemoved(QModelIndex,int,int)), d, SLOT(modelRowsRemoved(QModelIndex,int,int))); + connect(d->m_model, SIGNAL(columnsInserted(QModelIndex,int,int)), d, SLOT(modelColumnsAdded(QModelIndex,int,int))); + connect(d->m_model, SIGNAL(columnsRemoved(QModelIndex,int,int)), d, SLOT(modelColumnsRemoved(QModelIndex,int,int))); + connect(d->m_model, SIGNAL(destroyed()), d, SLOT(handleModelDestroyed())); +} + +QPieSeries *QPieModelMapper::series() const +{ + Q_D(const QPieModelMapper); + return d->m_series; +} + +void QPieModelMapper::setSeries(QPieSeries *series) +{ + Q_D(QPieModelMapper); + if (d->m_series) { + disconnect(d->m_series, 0, d, 0); + } + + if (series == 0) + return; + + d->m_series = series; + d->initializePieFromModel(); + // connect the signals from the series + connect(d->m_series, SIGNAL(added(QList<QPieSlice*>)), d, SLOT(slicesAdded(QList<QPieSlice*>))); + connect(d->m_series, SIGNAL(removed(QList<QPieSlice*>)), d, SLOT(slicesRemoved(QList<QPieSlice*>))); + connect(d->m_series, SIGNAL(destroyed()), d, SLOT(handleSeriesDestroyed())); +} + +/*! + Defines which row/column of the model contains the first slice value. + Minimal and default value is: 0 +*/ +int QPieModelMapper::first() const +{ + Q_D(const QPieModelMapper); + return d->m_first; +} + +/*! + Sets which row/column of the model contains the \a first slice value. + Minimal and default value is: 0 +*/ +void QPieModelMapper::setFirst(int first) +{ + Q_D(QPieModelMapper); + d->m_first = qMax(first, 0); + d->initializePieFromModel(); +} + +/*! + Defines the number of rows/columns of the model that are mapped as the data for QPieSeries + Minimal and default value is: -1 (count limited by the number of rows/columns in the model) +*/ +int QPieModelMapper::count() const +{ + Q_D(const QPieModelMapper); + return d->m_count; +} + +/*! + Defines the \a count of rows/columns of the model that are mapped as the data for QPieSeries + Minimal and default value is: -1 (count limited by the number of rows/columns in the model) +*/ +void QPieModelMapper::setCount(int count) +{ + Q_D(QPieModelMapper); + d->m_count = qMax(count, -1); + d->initializePieFromModel(); +} + +/*! + Returns the orientation that is used when QPieModelMapper accesses the model. + This mean whether the consecutive values/labels of the pie are read from row (Qt::Horizontal) + or from columns (Qt::Vertical) +*/ +Qt::Orientation QPieModelMapper::orientation() const +{ + Q_D(const QPieModelMapper); + return d->m_orientation; +} + +/*! + Returns the \a orientation that is used when QPieModelMapper accesses the model. + This mean whether the consecutive values/labels of the pie are read from row (Qt::Horizontal) + or from columns (Qt::Vertical) +*/ +void QPieModelMapper::setOrientation(Qt::Orientation orientation) +{ + Q_D(QPieModelMapper); + d->m_orientation = orientation; + d->initializePieFromModel(); +} + +/*! + Returns which section of the model is kept in sync with the values of the pie's slices +*/ +int QPieModelMapper::valuesSection() const +{ + Q_D(const QPieModelMapper); + return d->m_valuesSection; +} + +/*! + Sets the model section that is kept in sync with the pie slices values. + Parameter \a valuesSection specifies the section of the model. +*/ +void QPieModelMapper::setValuesSection(int valuesSection) +{ + Q_D(QPieModelMapper); + d->m_valuesSection = qMax(-1, valuesSection); + d->initializePieFromModel(); +} + +/*! + Returns which section of the model is kept in sync with the labels of the pie's slices +*/ +int QPieModelMapper::labelsSection() const +{ + Q_D(const QPieModelMapper); + return d->m_labelsSection; +} + +/*! + Sets the model section that is kept in sync with the pie slices labels. + Parameter \a labelsSection specifies the section of the model. +*/ +void QPieModelMapper::setLabelsSection(int labelsSection) +{ + Q_D(QPieModelMapper); + d->m_labelsSection = qMax(-1, labelsSection); + d->initializePieFromModel(); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +QPieModelMapperPrivate::QPieModelMapperPrivate(QPieModelMapper *q) : + QObject(q), + m_series(0), + m_model(0), + m_first(0), + m_count(-1), + m_orientation(Qt::Vertical), + m_valuesSection(-1), + m_labelsSection(-1), + m_seriesSignalsBlock(false), + m_modelSignalsBlock(false), + q_ptr(q) +{ +} + +void QPieModelMapperPrivate::blockModelSignals(bool block) +{ + m_modelSignalsBlock = block; +} + +void QPieModelMapperPrivate::blockSeriesSignals(bool block) +{ + m_seriesSignalsBlock = block; +} + + +QPieSlice *QPieModelMapperPrivate::pieSlice(QModelIndex index) const +{ + if (!index.isValid()) + return 0; // index is invalid + + if (m_orientation == Qt::Vertical && (index.column() == m_valuesSection || index.column() == m_labelsSection)) { + if (index.row() >= m_first && (m_count == - 1 || index.row() < m_first + m_count)) { + if (m_model->index(index.row(), m_valuesSection).isValid() && m_model->index(index.row(), m_labelsSection).isValid()) + return m_series->slices().at(index.row() - m_first); + else + return 0; + } + } else if (m_orientation == Qt::Horizontal && (index.row() == m_valuesSection || index.row() == m_labelsSection)) { + if (index.column() >= m_first && (m_count == - 1 || index.column() < m_first + m_count)) { + if (m_model->index(m_valuesSection, index.column()).isValid() && m_model->index(m_labelsSection, index.column()).isValid()) + return m_series->slices().at(index.column() - m_first); + else + return 0; + } + } + return 0; // This part of model has not been mapped to any slice +} + +QModelIndex QPieModelMapperPrivate::valueModelIndex(int slicePos) +{ + if (m_count != -1 && slicePos >= m_count) + return QModelIndex(); // invalid + + if (m_orientation == Qt::Vertical) + return m_model->index(slicePos + m_first, m_valuesSection); + else + return m_model->index(m_valuesSection, slicePos + m_first); +} + +QModelIndex QPieModelMapperPrivate::labelModelIndex(int slicePos) +{ + if (m_count != -1 && slicePos >= m_count) + return QModelIndex(); // invalid + + if (m_orientation == Qt::Vertical) + return m_model->index(slicePos + m_first, m_labelsSection); + else + return m_model->index(m_labelsSection, slicePos + m_first); +} + +bool QPieModelMapperPrivate::isLabelIndex(QModelIndex index) const +{ + if (m_orientation == Qt::Vertical && index.column() == m_labelsSection) + return true; + else if (m_orientation == Qt::Horizontal && index.row() == m_labelsSection) + return true; + + return false; +} + +bool QPieModelMapperPrivate::isValueIndex(QModelIndex index) const +{ + if (m_orientation == Qt::Vertical && index.column() == m_valuesSection) + return true; + else if (m_orientation == Qt::Horizontal && index.row() == m_valuesSection) + return true; + + return false; +} + +void QPieModelMapperPrivate::slicesAdded(QList<QPieSlice *> slices) +{ + if (m_seriesSignalsBlock) + return; + + if (slices.count() == 0) + return; + + int firstIndex = m_series->slices().indexOf(slices.at(0)); + if (firstIndex == -1) + return; + + if (m_count != -1) + m_count += slices.count(); + + for (int i = firstIndex; i < firstIndex + slices.count(); i++) { + m_slices.insert(i, slices.at(i - firstIndex)); + connect(slices.at(i - firstIndex), SIGNAL(labelChanged()), this, SLOT(sliceLabelChanged())); + connect(slices.at(i - firstIndex), SIGNAL(valueChanged()), this, SLOT(sliceValueChanged())); + } + + blockModelSignals(); + if (m_orientation == Qt::Vertical) + m_model->insertRows(firstIndex + m_first, slices.count()); + else + m_model->insertColumns(firstIndex + m_first, slices.count()); + + for (int i = firstIndex; i < firstIndex + slices.count(); i++) { + m_model->setData(valueModelIndex(i), slices.at(i - firstIndex)->value()); + m_model->setData(labelModelIndex(i), slices.at(i - firstIndex)->label()); + } + blockModelSignals(false); +} + +void QPieModelMapperPrivate::slicesRemoved(QList<QPieSlice *> slices) +{ + if (m_seriesSignalsBlock) + return; + + if (slices.count() == 0) + return; + + int firstIndex = m_slices.indexOf(slices.at(0)); + if (firstIndex == -1) + return; + + if (m_count != -1) + m_count -= slices.count(); + + for (int i = firstIndex + slices.count() - 1; i >= firstIndex; i--) + m_slices.removeAt(i); + + blockModelSignals(); + if (m_orientation == Qt::Vertical) + m_model->removeRows(firstIndex + m_first, slices.count()); + else + m_model->removeColumns(firstIndex + m_first, slices.count()); + blockModelSignals(false); +} + +void QPieModelMapperPrivate::sliceLabelChanged() +{ + if (m_seriesSignalsBlock) + return; + + blockModelSignals(); + QPieSlice *slice = qobject_cast<QPieSlice *>(QObject::sender()); + m_model->setData(labelModelIndex(m_series->slices().indexOf(slice)), slice->label()); + blockModelSignals(false); +} + +void QPieModelMapperPrivate::sliceValueChanged() +{ + if (m_seriesSignalsBlock) + return; + + blockModelSignals(); + QPieSlice *slice = qobject_cast<QPieSlice *>(QObject::sender()); + m_model->setData(valueModelIndex(m_series->slices().indexOf(slice)), slice->value()); + blockModelSignals(false); +} + +void QPieModelMapperPrivate::handleSeriesDestroyed() +{ + m_series = 0; +} + +void QPieModelMapperPrivate::modelUpdated(QModelIndex topLeft, QModelIndex bottomRight) +{ + if (m_model == 0 || m_series == 0) + return; + + if (m_modelSignalsBlock) + return; + + blockSeriesSignals(); + QModelIndex index; + QPieSlice *slice; + for (int row = topLeft.row(); row <= bottomRight.row(); row++) { + for (int column = topLeft.column(); column <= bottomRight.column(); column++) { + index = topLeft.sibling(row, column); + slice = pieSlice(index); + if (slice) { + if (isValueIndex(index)) + slice->setValue(m_model->data(index, Qt::DisplayRole).toReal()); + if (isLabelIndex(index)) + slice->setLabel(m_model->data(index, Qt::DisplayRole).toString()); + } + } + } + blockSeriesSignals(false); +} + + +void QPieModelMapperPrivate::modelRowsAdded(QModelIndex parent, int start, int end) +{ + Q_UNUSED(parent); + if (m_modelSignalsBlock) + return; + + blockSeriesSignals(); + if (m_orientation == Qt::Vertical) + insertData(start, end); + else if (start <= m_valuesSection || start <= m_labelsSection) // if the changes affect the map - reinitialize the pie + initializePieFromModel(); + blockSeriesSignals(false); +} + +void QPieModelMapperPrivate::modelRowsRemoved(QModelIndex parent, int start, int end) +{ + Q_UNUSED(parent); + if (m_modelSignalsBlock) + return; + + blockSeriesSignals(); + if (m_orientation == Qt::Vertical) + removeData(start, end); + else if (start <= m_valuesSection || start <= m_labelsSection) // if the changes affect the map - reinitialize the pie + initializePieFromModel(); + blockSeriesSignals(false); +} + +void QPieModelMapperPrivate::modelColumnsAdded(QModelIndex parent, int start, int end) +{ + Q_UNUSED(parent); + if (m_modelSignalsBlock) + return; + + blockSeriesSignals(); + if (m_orientation == Qt::Horizontal) + insertData(start, end); + else if (start <= m_valuesSection || start <= m_labelsSection) // if the changes affect the map - reinitialize the pie + initializePieFromModel(); + blockSeriesSignals(false); +} + +void QPieModelMapperPrivate::modelColumnsRemoved(QModelIndex parent, int start, int end) +{ + Q_UNUSED(parent); + if (m_modelSignalsBlock) + return; + + blockSeriesSignals(); + if (m_orientation == Qt::Horizontal) + removeData(start, end); + else if (start <= m_valuesSection || start <= m_labelsSection) // if the changes affect the map - reinitialize the pie + initializePieFromModel(); + blockSeriesSignals(false); +} + +void QPieModelMapperPrivate::handleModelDestroyed() +{ + m_model = 0; +} + +void QPieModelMapperPrivate::insertData(int start, int end) +{ + if (m_model == 0 || m_series == 0) + return; + + if (m_count != -1 && start >= m_first + m_count) { + return; + } else { + int addedCount = end - start + 1; + if (m_count != -1 && addedCount > m_count) + addedCount = m_count; + int first = qMax(start, m_first); + int last = qMin(first + addedCount - 1, m_orientation == Qt::Vertical ? m_model->rowCount() - 1 : m_model->columnCount() - 1); + for (int i = first; i <= last; i++) { + QModelIndex valueIndex = valueModelIndex(i - m_first); + QModelIndex labelIndex = labelModelIndex(i - m_first); + if (valueIndex.isValid() && labelIndex.isValid()) { + QPieSlice *slice = new QPieSlice; + slice->setValue(m_model->data(valueIndex, Qt::DisplayRole).toDouble()); + slice->setLabel(m_model->data(labelIndex, Qt::DisplayRole).toString()); + connect(slice, SIGNAL(labelChanged()), this, SLOT(sliceLabelChanged())); + connect(slice, SIGNAL(valueChanged()), this, SLOT(sliceValueChanged())); + m_series->insert(i - m_first, slice); + m_slices.insert(i - m_first, slice); + } + } + + // remove excess of slices (abouve m_count) + if (m_count != -1 && m_series->slices().size() > m_count) + for (int i = m_series->slices().size() - 1; i >= m_count; i--) { + m_series->remove(m_series->slices().at(i)); + m_slices.removeAt(i); + } + } +} + +void QPieModelMapperPrivate::removeData(int start, int end) +{ + if (m_model == 0 || m_series == 0) + return; + + int removedCount = end - start + 1; + if (m_count != -1 && start >= m_first + m_count) { + return; + } else { + int toRemove = qMin(m_series->slices().size(), removedCount); // first find how many items can actually be removed + int first = qMax(start, m_first); // get the index of the first item that will be removed. + int last = qMin(first + toRemove - 1, m_series->slices().size() + m_first - 1); // get the index of the last item that will be removed. + for (int i = last; i >= first; i--) { + m_series->remove(m_series->slices().at(i - m_first)); + m_slices.removeAt(i - m_first); + } + + if (m_count != -1) { + int itemsAvailable; // check how many are available to be added + if (m_orientation == Qt::Vertical) + itemsAvailable = m_model->rowCount() - m_first - m_series->slices().size(); + else + itemsAvailable = m_model->columnCount() - m_first - m_series->slices().size(); + int toBeAdded = qMin(itemsAvailable, m_count - m_series->slices().size()); // add not more items than there is space left to be filled. + int currentSize = m_series->slices().size(); + if (toBeAdded > 0) + for (int i = m_series->slices().size(); i < currentSize + toBeAdded; i++) { + QModelIndex valueIndex = valueModelIndex(i - m_first); + QModelIndex labelIndex = labelModelIndex(i - m_first); + if (valueIndex.isValid() && labelIndex.isValid()) { + QPieSlice *slice = new QPieSlice; + slice->setValue(m_model->data(valueIndex, Qt::DisplayRole).toDouble()); + slice->setLabel(m_model->data(labelIndex, Qt::DisplayRole).toString()); + m_series->insert(i, slice); + m_slices.insert(i, slice); + } + } + } + } +} + +void QPieModelMapperPrivate::initializePieFromModel() +{ + if (m_model == 0 || m_series == 0) + return; + + blockSeriesSignals(); + // clear current content + m_series->clear(); + m_slices.clear(); + + // create the initial slices set + int slicePos = 0; + QModelIndex valueIndex = valueModelIndex(slicePos); + QModelIndex labelIndex = labelModelIndex(slicePos); + while (valueIndex.isValid() && labelIndex.isValid()) { + QPieSlice *slice = new QPieSlice; + slice->setLabel(m_model->data(labelIndex, Qt::DisplayRole).toString()); + slice->setValue(m_model->data(valueIndex, Qt::DisplayRole).toDouble()); + connect(slice, SIGNAL(labelChanged()), this, SLOT(sliceLabelChanged())); + connect(slice, SIGNAL(valueChanged()), this, SLOT(sliceValueChanged())); + m_series->append(slice); + m_slices.append(slice); + slicePos++; + valueIndex = valueModelIndex(slicePos); + labelIndex = labelModelIndex(slicePos); + } + blockSeriesSignals(false); +} + +#include "moc_qpiemodelmapper_p.cpp" +#include "moc_qpiemodelmapper.cpp" + +QT_CHARTS_END_NAMESPACE diff --git a/src/charts/piechart/qpiemodelmapper.h b/src/charts/piechart/qpiemodelmapper.h new file mode 100644 index 00000000..a4ab6a2c --- /dev/null +++ b/src/charts/piechart/qpiemodelmapper.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QPIEMODELMAPPER_H +#define QPIEMODELMAPPER_H + +#include <QtCharts/qchartglobal.h> +#include <QObject> + +class QAbstractItemModel; + +QT_CHARTS_BEGIN_NAMESPACE + +class QPieModelMapperPrivate; +class QPieSeries; + +class QT_CHARTS_EXPORT QPieModelMapper : public QObject +{ + Q_OBJECT + +protected: + explicit QPieModelMapper(QObject *parent = 0); + + QAbstractItemModel *model() const; + void setModel(QAbstractItemModel *model); + + QPieSeries *series() const; + void setSeries(QPieSeries *series); + + int first() const; + void setFirst(int first); + + int count() const; + void setCount(int count); + + int valuesSection() const; + void setValuesSection(int valuesSection); + + int labelsSection() const; + void setLabelsSection(int labelsSection); + + Qt::Orientation orientation() const; + void setOrientation(Qt::Orientation orientation); + +protected: + QPieModelMapperPrivate * const d_ptr; + Q_DECLARE_PRIVATE(QPieModelMapper) +}; + +QT_CHARTS_END_NAMESPACE + +#endif // QPIEMODELMAPPER_H diff --git a/src/charts/piechart/qpiemodelmapper_p.h b/src/charts/piechart/qpiemodelmapper_p.h new file mode 100644 index 00000000..a287497f --- /dev/null +++ b/src/charts/piechart/qpiemodelmapper_p.h @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +// W A R N I N G +// ------------- +// +// This file is not part of the Qt Enterprise Chart API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef QPIEMODELMAPPER_P_H +#define QPIEMODELMAPPER_P_H + +#include <QObject> +#include <qpiemodelmapper.h> + +class QModelIndex; + +QT_CHARTS_BEGIN_NAMESPACE + +class QPieSlice; + +class QPieModelMapperPrivate : public QObject +{ + Q_OBJECT + +public: + explicit QPieModelMapperPrivate(QPieModelMapper *q); + +public Q_SLOTS: + // for the model + void modelUpdated(QModelIndex topLeft, QModelIndex bottomRight); + void modelRowsAdded(QModelIndex parent, int start, int end); + void modelRowsRemoved(QModelIndex parent, int start, int end); + void modelColumnsAdded(QModelIndex parent, int start, int end); + void modelColumnsRemoved(QModelIndex parent, int start, int end); + void handleModelDestroyed(); + + // for the series + void slicesAdded(QList<QPieSlice *> slices); + void slicesRemoved(QList<QPieSlice *> slices); + void sliceLabelChanged(); + void sliceValueChanged(); + void handleSeriesDestroyed(); + + void initializePieFromModel(); + +private: + QPieSlice *pieSlice(QModelIndex index) const; + bool isLabelIndex(QModelIndex index) const; + bool isValueIndex(QModelIndex index) const; + QModelIndex valueModelIndex(int slicePos); + QModelIndex labelModelIndex(int slicePos); + void insertData(int start, int end); + void removeData(int start, int end); + + void blockModelSignals(bool block = true); + void blockSeriesSignals(bool block = true); + +private: + QPieSeries *m_series; + QList<QPieSlice *> m_slices; + QAbstractItemModel *m_model; + int m_first; + int m_count; + Qt::Orientation m_orientation; + int m_valuesSection; + int m_labelsSection; + bool m_seriesSignalsBlock; + bool m_modelSignalsBlock; + +private: + + QPieModelMapper *q_ptr; + Q_DECLARE_PUBLIC(QPieModelMapper) +}; + +QT_CHARTS_END_NAMESPACE + +#endif // QPIEMODELMAPPER_P_H diff --git a/src/charts/piechart/qpieseries.cpp b/src/charts/piechart/qpieseries.cpp new file mode 100644 index 00000000..733429d1 --- /dev/null +++ b/src/charts/piechart/qpieseries.cpp @@ -0,0 +1,947 @@ +/**************************************************************************** +** +** 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 "qpieseries.h" +#include "qpieseries_p.h" +#include "qpieslice.h" +#include "qpieslice_p.h" +#include "pieslicedata_p.h" +#include "chartdataset_p.h" +#include "charttheme_p.h" +#include "qabstractaxis.h" +#include "pieanimation_p.h" +#include "charthelpers_p.h" + +#include "qpielegendmarker.h" + +QT_CHARTS_BEGIN_NAMESPACE + +/*! + \class QPieSeries + \inmodule Qt Charts + \brief Pie series API for Qt Charts. + + The pie series defines a pie chart which consists of pie slices which are defined as QPieSlice objects. + The slices can have any values as the QPieSeries will calculate its relative value to the sum of all slices. + The actual slice size is determined by that relative value. + + Pie size and position on the chart is controlled by using relative values which range from 0.0 to 1.0. + These relate to the actual chart rectangle. + + By default the pie is defined as a full pie but it can also be a partial pie. + This can be done by setting a starting angle and angle span to the series. + Full pie is 360 degrees where 0 is at 12 a'clock. + + See the \l {PieChart Example} {pie chart example} or \l {DonutChart Example} {donut chart example} to learn how to use QPieSeries. + \table 100% + \row + \li \image examples_piechart.png + \li \image examples_donutchart.png + \endtable +*/ +/*! + \qmltype PieSeries + \instantiates QPieSeries + \inqmlmodule QtCharts + + \inherits AbstractSeries + + \brief The PieSeries type is used for making pie charts. + + The following QML shows how to create a simple pie chart. + + \snippet qmlchart/qml/qmlchart/View1.qml 1 + + \beginfloatleft + \image examples_qmlchart1.png + \endfloat + \clearfloat +*/ + +/*! + \property QPieSeries::horizontalPosition + \brief Defines the horizontal position of the pie. + + The value is a relative value to the chart rectangle where: + + \list + \li 0.0 is the absolute left. + \li 1.0 is the absolute right. + \endlist + Default value is 0.5 (center). + \sa verticalPosition +*/ + +/*! + \qmlproperty real PieSeries::horizontalPosition + + Defines the horizontal position of the pie. + + The value is a relative value to the chart rectangle where: + + \list + \li 0.0 is the absolute left. + \li 1.0 is the absolute right. + \endlist + Default value is 0.5 (center). + \sa verticalPosition +*/ + +/*! + \property QPieSeries::verticalPosition + \brief Defines the vertical position of the pie. + + The value is a relative value to the chart rectangle where: + + \list + \li 0.0 is the absolute top. + \li 1.0 is the absolute bottom. + \endlist + Default value is 0.5 (center). + \sa horizontalPosition +*/ + +/*! + \qmlproperty real PieSeries::verticalPosition + + Defines the vertical position of the pie. + + The value is a relative value to the chart rectangle where: + + \list + \li 0.0 is the absolute top. + \li 1.0 is the absolute bottom. + \endlist + Default value is 0.5 (center). + \sa horizontalPosition +*/ + +/*! + \property QPieSeries::size + \brief Defines the pie size. + + The value is a relative value to the chart rectangle where: + + \list + \li 0.0 is the minimum size (pie not drawn). + \li 1.0 is the maximum size that can fit the chart. + \endlist + + When setting this property the holeSize property is adjusted if necessary, to ensure that the hole size is not greater than the outer size. + + Default value is 0.7. +*/ + +/*! + \qmlproperty real PieSeries::size + + Defines the pie size. + + The value is a relative value to the chart rectangle where: + + \list + \li 0.0 is the minimum size (pie not drawn). + \li 1.0 is the maximum size that can fit the chart. + \endlist + + Default value is 0.7. +*/ + +/*! + \property QPieSeries::holeSize + \brief Defines the donut hole size. + + The value is a relative value to the chart rectangle where: + + \list + \li 0.0 is the minimum size (full pie drawn, without any hole inside). + \li 1.0 is the maximum size that can fit the chart. (donut has no width) + \endlist + + The value is never greater then size property. + Default value is 0.0. +*/ + +/*! + \qmlproperty real PieSeries::holeSize + + Defines the donut hole size. + + The value is a relative value to the chart rectangle where: + + \list + \li 0.0 is the minimum size (full pie drawn, without any hole inside). + \li 1.0 is the maximum size that can fit the chart. (donut has no width) + \endlist + + When setting this property the size property is adjusted if necessary, to ensure that the inner size is not greater than the outer size. + + Default value is 0.0. +*/ + +/*! + \property QPieSeries::startAngle + \brief Defines the starting angle of the pie. + + Full pie is 360 degrees where 0 degrees is at 12 a'clock. + + Default is value is 0. +*/ + +/*! + \qmlproperty real PieSeries::startAngle + + Defines the starting angle of the pie. + + Full pie is 360 degrees where 0 degrees is at 12 a'clock. + + Default is value is 0. +*/ + +/*! + \property QPieSeries::endAngle + \brief Defines the ending angle of the pie. + + Full pie is 360 degrees where 0 degrees is at 12 a'clock. + + Default is value is 360. +*/ + +/*! + \qmlproperty real PieSeries::endAngle + + Defines the ending angle of the pie. + + Full pie is 360 degrees where 0 degrees is at 12 a'clock. + + Default is value is 360. +*/ + +/*! + \property QPieSeries::count + + Number of slices in the series. +*/ + +/*! + \qmlproperty int PieSeries::count + + Number of slices in the series. +*/ + +/*! + \fn void QPieSeries::countChanged() + Emitted when the slice count has changed. + \sa count +*/ +/*! + \qmlsignal PieSeries::onCountChanged() + Emitted when the slice count has changed. +*/ + +/*! + \property QPieSeries::sum + + Sum of all slices. + + The series keeps track of the sum of all slices it holds. +*/ + +/*! + \qmlproperty real PieSeries::sum + + Sum of all slices. + + The series keeps track of the sum of all slices it holds. +*/ + +/*! + \fn void QPieSeries::sumChanged() + Emitted when the sum of all slices has changed. + \sa sum +*/ +/*! + \qmlsignal PieSeries::onSumChanged() + Emitted when the sum of all slices has changed. This may happen for example if you add or remove slices, or if you + change value of a slice. +*/ + +/*! + \fn void QPieSeries::added(QList<QPieSlice*> slices) + + This signal is emitted when \a slices have been added to the series. + + \sa append(), insert() +*/ +/*! + \qmlsignal PieSeries::onAdded(PieSlice slice) + Emitted when \a slice has been added to the series. +*/ + +/*! + \fn void QPieSeries::removed(QList<QPieSlice*> slices) + This signal is emitted when \a slices have been removed from the series. + \sa remove() +*/ +/*! + \qmlsignal PieSeries::onRemoved(PieSlice slice) + Emitted when \a slice has been removed from the series. +*/ + +/*! + \fn void QPieSeries::clicked(QPieSlice* slice) + This signal is emitted when a \a slice has been clicked. + \sa QPieSlice::clicked() +*/ +/*! + \qmlsignal PieSeries::onClicked(PieSlice slice) + This signal is emitted when a \a slice has been clicked. +*/ + +/*! + \fn void QPieSeries::hovered(QPieSlice* slice, bool state) + This signal is emitted when user has hovered over or away from the \a slice. + \a state is true when user has hovered over the slice and false when hover has moved away from the slice. + \sa QPieSlice::hovered() +*/ +/*! + \qmlsignal PieSeries::onHovered(PieSlice slice, bool state) + This signal is emitted when user has hovered over or away from the \a slice. \a state is true when user has hovered + over the slice and false when hover has moved away from the slice. +*/ + +/*! + \qmlmethod PieSlice PieSeries::at(int index) + Returns slice at \a index. Returns null if the index is not valid. +*/ + +/*! + \qmlmethod PieSlice PieSeries::find(string label) + Returns the first slice with \a label. Returns null if the index is not valid. +*/ + +/*! + \qmlmethod PieSlice PieSeries::append(string label, real value) + Adds a new slice with \a label and \a value to the pie. +*/ + +/*! + \qmlmethod bool PieSeries::remove(PieSlice slice) + Removes the \a slice from the pie. Returns true if the removal was successful, false otherwise. +*/ + +/*! + \qmlmethod PieSeries::clear() + Removes all slices from the pie. +*/ + +/*! + Constructs a series object which is a child of \a parent. +*/ +QPieSeries::QPieSeries(QObject *parent) + : QAbstractSeries(*new QPieSeriesPrivate(this), parent) +{ + Q_D(QPieSeries); + QObject::connect(this, SIGNAL(countChanged()), d, SIGNAL(countChanged())); +} + +/*! + Destroys the series and its slices. +*/ +QPieSeries::~QPieSeries() +{ + // NOTE: d_prt destroyed by QObject + clear(); +} + +/*! + Returns QAbstractSeries::SeriesTypePie. +*/ +QAbstractSeries::SeriesType QPieSeries::type() const +{ + return QAbstractSeries::SeriesTypePie; +} + +/*! + Appends a single \a slice to the series. + Slice ownership is passed to the series. + + Returns true if append was succesfull. +*/ +bool QPieSeries::append(QPieSlice *slice) +{ + return append(QList<QPieSlice *>() << slice); +} + +/*! + Appends an array of \a slices to the series. + Slice ownership is passed to the series. + + Returns true if append was successful. +*/ +bool QPieSeries::append(QList<QPieSlice *> slices) +{ + Q_D(QPieSeries); + + if (slices.count() == 0) + return false; + + foreach (QPieSlice *s, slices) { + if (!s || d->m_slices.contains(s)) + return false; + if (s->series()) // already added to some series + return false; + if (!isValidValue(s->value())) + return false; + } + + foreach (QPieSlice *s, slices) { + s->setParent(this); + QPieSlicePrivate::fromSlice(s)->m_series = this; + d->m_slices << s; + } + + d->updateDerivativeData(); + + foreach(QPieSlice * s, slices) { + connect(s, SIGNAL(valueChanged()), d, SLOT(sliceValueChanged())); + connect(s, SIGNAL(clicked()), d, SLOT(sliceClicked())); + connect(s, SIGNAL(hovered(bool)), d, SLOT(sliceHovered(bool))); + } + + emit added(slices); + emit countChanged(); + + return true; +} + +/*! + Appends a single \a slice to the series and returns a reference to the series. + Slice ownership is passed to the series. +*/ +QPieSeries &QPieSeries::operator << (QPieSlice *slice) +{ + append(slice); + return *this; +} + + +/*! + Appends a single slice to the series with give \a value and \a label. + Slice ownership is passed to the series. + Returns NULL if value is NaN, Inf or -Inf and no slice is added to the series. +*/ +QPieSlice *QPieSeries::append(QString label, qreal value) +{ + if (isValidValue(value)) { + QPieSlice *slice = new QPieSlice(label, value); + append(slice); + return slice; + } else { + return 0; + } +} + +/*! + Inserts a single \a slice to the series before the slice at \a index position. + Slice ownership is passed to the series. + + Returns true if insert was successful. +*/ +bool QPieSeries::insert(int index, QPieSlice *slice) +{ + Q_D(QPieSeries); + + if (index < 0 || index > d->m_slices.count()) + return false; + + if (!slice || d->m_slices.contains(slice)) + return false; + + if (slice->series()) // already added to some series + return false; + + if (!isValidValue(slice->value())) + return false; + + slice->setParent(this); + QPieSlicePrivate::fromSlice(slice)->m_series = this; + d->m_slices.insert(index, slice); + + d->updateDerivativeData(); + + connect(slice, SIGNAL(valueChanged()), d, SLOT(sliceValueChanged())); + connect(slice, SIGNAL(clicked()), d, SLOT(sliceClicked())); + connect(slice, SIGNAL(hovered(bool)), d, SLOT(sliceHovered(bool))); + + emit added(QList<QPieSlice *>() << slice); + emit countChanged(); + + return true; +} + +/*! + Removes a single \a slice from the series and deletes the slice. + + Do not reference the pointer after this call. + + Returns true if remove was successful. +*/ +bool QPieSeries::remove(QPieSlice *slice) +{ + Q_D(QPieSeries); + + if (!d->m_slices.removeOne(slice)) + return false; + + d->updateDerivativeData(); + + emit removed(QList<QPieSlice *>() << slice); + emit countChanged(); + + delete slice; + slice = 0; + + return true; +} + +/*! + Takes a single \a slice from the series. Does not destroy the slice object. + + \note The series remains as the slice's parent object. You must set the + parent object to take full ownership. + + Returns true if take was successful. +*/ +bool QPieSeries::take(QPieSlice *slice) +{ + Q_D(QPieSeries); + + if (!d->m_slices.removeOne(slice)) + return false; + + QPieSlicePrivate::fromSlice(slice)->m_series = 0; + slice->disconnect(d); + + d->updateDerivativeData(); + + emit removed(QList<QPieSlice *>() << slice); + emit countChanged(); + + return true; +} + +/*! + Clears all slices from the series. +*/ +void QPieSeries::clear() +{ + Q_D(QPieSeries); + if (d->m_slices.count() == 0) + return; + + QList<QPieSlice *> slices = d->m_slices; + foreach (QPieSlice *s, d->m_slices) + d->m_slices.removeOne(s); + + d->updateDerivativeData(); + + emit removed(slices); + emit countChanged(); + + foreach (QPieSlice *s, slices) + delete s; +} + +/*! + Returns a list of slices that belong to this series. +*/ +QList<QPieSlice *> QPieSeries::slices() const +{ + Q_D(const QPieSeries); + return d->m_slices; +} + +/*! + returns the number of the slices in this series. +*/ +int QPieSeries::count() const +{ + Q_D(const QPieSeries); + return d->m_slices.count(); +} + +/*! + Returns true is the series is empty. +*/ +bool QPieSeries::isEmpty() const +{ + Q_D(const QPieSeries); + return d->m_slices.isEmpty(); +} + +/*! + Returns the sum of all slice values in this series. + + \sa QPieSlice::value(), QPieSlice::setValue(), QPieSlice::percentage() +*/ +qreal QPieSeries::sum() const +{ + Q_D(const QPieSeries); + return d->m_sum; +} + +void QPieSeries::setHoleSize(qreal holeSize) +{ + Q_D(QPieSeries); + holeSize = qBound((qreal)0.0, holeSize, (qreal)1.0); + d->setSizes(holeSize, qMax(d->m_pieRelativeSize, holeSize)); +} + +qreal QPieSeries::holeSize() const +{ + Q_D(const QPieSeries); + return d->m_holeRelativeSize; +} + +void QPieSeries::setHorizontalPosition(qreal relativePosition) +{ + Q_D(QPieSeries); + + if (relativePosition < 0.0) + relativePosition = 0.0; + if (relativePosition > 1.0) + relativePosition = 1.0; + + if (!qFuzzyCompare(d->m_pieRelativeHorPos, relativePosition)) { + d->m_pieRelativeHorPos = relativePosition; + emit d->horizontalPositionChanged(); + } +} + +qreal QPieSeries::horizontalPosition() const +{ + Q_D(const QPieSeries); + return d->m_pieRelativeHorPos; +} + +void QPieSeries::setVerticalPosition(qreal relativePosition) +{ + Q_D(QPieSeries); + + if (relativePosition < 0.0) + relativePosition = 0.0; + if (relativePosition > 1.0) + relativePosition = 1.0; + + if (!qFuzzyCompare(d->m_pieRelativeVerPos, relativePosition)) { + d->m_pieRelativeVerPos = relativePosition; + emit d->verticalPositionChanged(); + } +} + +qreal QPieSeries::verticalPosition() const +{ + Q_D(const QPieSeries); + return d->m_pieRelativeVerPos; +} + +void QPieSeries::setPieSize(qreal relativeSize) +{ + Q_D(QPieSeries); + relativeSize = qBound((qreal)0.0, relativeSize, (qreal)1.0); + d->setSizes(qMin(d->m_holeRelativeSize, relativeSize), relativeSize); + +} + +qreal QPieSeries::pieSize() const +{ + Q_D(const QPieSeries); + return d->m_pieRelativeSize; +} + + +void QPieSeries::setPieStartAngle(qreal angle) +{ + Q_D(QPieSeries); + if (qFuzzyCompare(d->m_pieStartAngle, angle)) + return; + d->m_pieStartAngle = angle; + d->updateDerivativeData(); + emit d->pieStartAngleChanged(); +} + +qreal QPieSeries::pieStartAngle() const +{ + Q_D(const QPieSeries); + return d->m_pieStartAngle; +} + +/*! + Sets the end angle of the pie. + + Full pie is 360 degrees where 0 degrees is at 12 a'clock. + + \a angle must be greater than start angle. + + \sa pieEndAngle(), pieStartAngle(), setPieStartAngle() +*/ +void QPieSeries::setPieEndAngle(qreal angle) +{ + Q_D(QPieSeries); + if (qFuzzyCompare(d->m_pieEndAngle, angle)) + return; + d->m_pieEndAngle = angle; + d->updateDerivativeData(); + emit d->pieEndAngleChanged(); +} + +/*! + Returns the end angle of the pie. + + Full pie is 360 degrees where 0 degrees is at 12 a'clock. + + \sa setPieEndAngle(), pieStartAngle(), setPieStartAngle() +*/ +qreal QPieSeries::pieEndAngle() const +{ + Q_D(const QPieSeries); + return d->m_pieEndAngle; +} + +/*! + Sets the all the slice labels \a visible or invisible. + + Note that this affects only the current slices in the series. + If user adds a new slice the default label visibility is false. + + \sa QPieSlice::isLabelVisible(), QPieSlice::setLabelVisible() +*/ +void QPieSeries::setLabelsVisible(bool visible) +{ + Q_D(QPieSeries); + foreach (QPieSlice *s, d->m_slices) + s->setLabelVisible(visible); +} + +/*! + Sets the all the slice labels \a position + + Note that this affects only the current slices in the series. + If user adds a new slice the default label position is LabelOutside + + \sa QPieSlice::labelPosition(), QPieSlice::setLabelPosition() +*/ +void QPieSeries::setLabelsPosition(QPieSlice::LabelPosition position) +{ + Q_D(QPieSeries); + foreach (QPieSlice *s, d->m_slices) + s->setLabelPosition(position); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + +QPieSeriesPrivate::QPieSeriesPrivate(QPieSeries *parent) : + QAbstractSeriesPrivate(parent), + m_pieRelativeHorPos(0.5), + m_pieRelativeVerPos(0.5), + m_pieRelativeSize(0.7), + m_pieStartAngle(0), + m_pieEndAngle(360), + m_sum(0), + m_holeRelativeSize(0.0) +{ +} + +QPieSeriesPrivate::~QPieSeriesPrivate() +{ +} + +void QPieSeriesPrivate::updateDerivativeData() +{ + // calculate sum of all slices + qreal sum = 0; + foreach (QPieSlice *s, m_slices) + sum += s->value(); + + if (!qFuzzyCompare(m_sum, sum)) { + m_sum = sum; + emit q_func()->sumChanged(); + } + + // nothing to show.. + if (qFuzzyCompare(m_sum, 0)) + return; + + // update slice attributes + qreal sliceAngle = m_pieStartAngle; + qreal pieSpan = m_pieEndAngle - m_pieStartAngle; + QVector<QPieSlice *> changed; + foreach (QPieSlice *s, m_slices) { + QPieSlicePrivate *d = QPieSlicePrivate::fromSlice(s); + d->setPercentage(s->value() / m_sum); + d->setStartAngle(sliceAngle); + d->setAngleSpan(pieSpan * s->percentage()); + sliceAngle += s->angleSpan(); + } + + + emit calculatedDataChanged(); +} + +void QPieSeriesPrivate::setSizes(qreal innerSize, qreal outerSize) +{ + bool changed = false; + + if (!qFuzzyCompare(m_holeRelativeSize, innerSize)) { + m_holeRelativeSize = innerSize; + changed = true; + } + + if (!qFuzzyCompare(m_pieRelativeSize, outerSize)) { + m_pieRelativeSize = outerSize; + changed = true; + } + + if (changed) + emit pieSizeChanged(); +} + +QPieSeriesPrivate *QPieSeriesPrivate::fromSeries(QPieSeries *series) +{ + return series->d_func(); +} + +void QPieSeriesPrivate::sliceValueChanged() +{ + Q_ASSERT(m_slices.contains(qobject_cast<QPieSlice *>(sender()))); + updateDerivativeData(); +} + +void QPieSeriesPrivate::sliceClicked() +{ + QPieSlice *slice = qobject_cast<QPieSlice *>(sender()); + Q_ASSERT(m_slices.contains(slice)); + Q_Q(QPieSeries); + emit q->clicked(slice); +} + +void QPieSeriesPrivate::sliceHovered(bool state) +{ + QPieSlice *slice = qobject_cast<QPieSlice *>(sender()); + if (!m_slices.isEmpty()) { + Q_ASSERT(m_slices.contains(slice)); + Q_Q(QPieSeries); + emit q->hovered(slice, state); + } +} + +void QPieSeriesPrivate::initializeDomain() +{ + // does not apply to pie +} + +void QPieSeriesPrivate::initializeGraphics(QGraphicsItem* parent) +{ + Q_Q(QPieSeries); + PieChartItem *pie = new PieChartItem(q,parent); + m_item.reset(pie); + QAbstractSeriesPrivate::initializeGraphics(parent); +} + +void QPieSeriesPrivate::initializeAnimations(QtCharts::QChart::AnimationOptions options) +{ + PieChartItem *item = static_cast<PieChartItem *>(m_item.data()); + Q_ASSERT(item); + if (item->animation()) + item->animation()->stopAndDestroyLater(); + + if (options.testFlag(QChart::SeriesAnimations)) + item->setAnimation(new PieAnimation(item)); + else + item->setAnimation(0); + QAbstractSeriesPrivate::initializeAnimations(options); +} + +QList<QLegendMarker*> QPieSeriesPrivate::createLegendMarkers(QLegend* legend) +{ + Q_Q(QPieSeries); + QList<QLegendMarker*> markers; + foreach(QPieSlice* slice, q->slices()) { + QPieLegendMarker* marker = new QPieLegendMarker(q,slice,legend); + markers << marker; + } + return markers; +} + +void QPieSeriesPrivate::initializeAxes() +{ + +} + +QAbstractAxis::AxisType QPieSeriesPrivate::defaultAxisType(Qt::Orientation orientation) const +{ + Q_UNUSED(orientation); + return QAbstractAxis::AxisTypeNoAxis; +} + +QAbstractAxis* QPieSeriesPrivate::createDefaultAxis(Qt::Orientation orientation) const +{ + Q_UNUSED(orientation); + return 0; +} + +void QPieSeriesPrivate::initializeTheme(int index, ChartTheme* theme, bool forced) +{ + //Q_Q(QPieSeries); + //const QList<QColor>& colors = theme->seriesColors(); + const QList<QGradient>& gradients = theme->seriesGradients(); + + for (int i(0); i < m_slices.count(); i++) { + + QColor penColor = ChartThemeManager::colorAt(gradients.at(index % gradients.size()), 0.0); + + // Get color for a slice from a gradient linearly, beginning from the start of the gradient + qreal pos = (qreal)(i + 1) / (qreal) m_slices.count(); + QColor brushColor = ChartThemeManager::colorAt(gradients.at(index % gradients.size()), pos); + + QPieSlice *s = m_slices.at(i); + QPieSlicePrivate *d = QPieSlicePrivate::fromSlice(s); + + if (forced || d->m_data.m_slicePen.isThemed()) + d->setPen(penColor, true); + + if (forced || d->m_data.m_sliceBrush.isThemed()) + d->setBrush(brushColor, true); + + if (forced || d->m_data.m_labelBrush.isThemed()) + d->setLabelBrush(theme->labelBrush().color(), true); + + if (forced || d->m_data.m_labelFont.isThemed()) + d->setLabelFont(theme->labelFont(), true); + } +} + + +#include "moc_qpieseries.cpp" +#include "moc_qpieseries_p.cpp" + +QT_CHARTS_END_NAMESPACE diff --git a/src/charts/piechart/qpieseries.h b/src/charts/piechart/qpieseries.h new file mode 100644 index 00000000..27c8042b --- /dev/null +++ b/src/charts/piechart/qpieseries.h @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QPIESERIES_H +#define QPIESERIES_H + +#include <QtCharts/qabstractseries.h> +#include <QtCharts/QPieSlice> + +QT_CHARTS_BEGIN_NAMESPACE +class QPieSeriesPrivate; + +class QT_CHARTS_EXPORT QPieSeries : public QAbstractSeries +{ + Q_OBJECT + Q_PROPERTY(qreal horizontalPosition READ horizontalPosition WRITE setHorizontalPosition) + Q_PROPERTY(qreal verticalPosition READ verticalPosition WRITE setVerticalPosition) + Q_PROPERTY(qreal size READ pieSize WRITE setPieSize) + Q_PROPERTY(qreal startAngle READ pieStartAngle WRITE setPieStartAngle) + Q_PROPERTY(qreal endAngle READ pieEndAngle WRITE setPieEndAngle) + Q_PROPERTY(int count READ count NOTIFY countChanged) + Q_PROPERTY(qreal sum READ sum NOTIFY sumChanged) + Q_PROPERTY(qreal holeSize READ holeSize WRITE setHoleSize) + +public: + explicit QPieSeries(QObject *parent = 0); + virtual ~QPieSeries(); + + QAbstractSeries::SeriesType type() const; + + bool append(QPieSlice *slice); + bool append(QList<QPieSlice *> slices); + QPieSeries &operator << (QPieSlice *slice); + QPieSlice *append(QString label, qreal value); + + bool insert(int index, QPieSlice *slice); + + bool remove(QPieSlice *slice); + bool take(QPieSlice *slice); + void clear(); + + QList<QPieSlice *> slices() const; + int count() const; + + bool isEmpty() const; + + qreal sum() const; + + void setHoleSize(qreal holeSize); + qreal holeSize() const; + + void setHorizontalPosition(qreal relativePosition); + qreal horizontalPosition() const; + + void setVerticalPosition(qreal relativePosition); + qreal verticalPosition() const; + + void setPieSize(qreal relativeSize); + qreal pieSize() const; + + void setPieStartAngle(qreal startAngle); + qreal pieStartAngle() const; + + void setPieEndAngle(qreal endAngle); + qreal pieEndAngle() const; + + void setLabelsVisible(bool visible = true); + void setLabelsPosition(QPieSlice::LabelPosition position); + +Q_SIGNALS: + void added(QList<QPieSlice *> slices); + void removed(QList<QPieSlice *> slices); + void clicked(QPieSlice *slice); + void hovered(QPieSlice *slice, bool state); + void countChanged(); + void sumChanged(); + +private: + Q_DECLARE_PRIVATE(QPieSeries) + Q_DISABLE_COPY(QPieSeries) + friend class PieChartItem; +}; + +QT_CHARTS_END_NAMESPACE + +#endif // QPIESERIES_H diff --git a/src/charts/piechart/qpieseries_p.h b/src/charts/piechart/qpieseries_p.h new file mode 100644 index 00000000..f2a52180 --- /dev/null +++ b/src/charts/piechart/qpieseries_p.h @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +// W A R N I N G +// ------------- +// +// This file is not part of the Qt Enterprise Chart API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef QPIESERIES_P_H +#define QPIESERIES_P_H + +#include "qpieseries.h" +#include "qabstractseries_p.h" + +QT_CHARTS_BEGIN_NAMESPACE +class QLegendPrivate; + +class QPieSeriesPrivate : public QAbstractSeriesPrivate +{ + Q_OBJECT + +public: + QPieSeriesPrivate(QPieSeries *parent); + ~QPieSeriesPrivate(); + + void initializeDomain(); + void initializeAxes(); + void initializeGraphics(QGraphicsItem* parent); + void initializeAnimations(QtCharts::QChart::AnimationOptions options); + void initializeTheme(int index, ChartTheme* theme, bool forced = false); + + QList<QLegendMarker *> createLegendMarkers(QLegend *legend); + + QAbstractAxis::AxisType defaultAxisType(Qt::Orientation orientation) const; + QAbstractAxis* createDefaultAxis(Qt::Orientation orientation) const; + + void updateDerivativeData(); + void setSizes(qreal innerSize, qreal outerSize); + + static QPieSeriesPrivate *fromSeries(QPieSeries *series); + +signals: + void calculatedDataChanged(); + void pieSizeChanged(); + void pieStartAngleChanged(); + void pieEndAngleChanged(); + void horizontalPositionChanged(); + void verticalPositionChanged(); + +public Q_SLOTS: + void sliceValueChanged(); + void sliceClicked(); + void sliceHovered(bool state); + +private: + QList<QPieSlice *> m_slices; + qreal m_pieRelativeHorPos; + qreal m_pieRelativeVerPos; + qreal m_pieRelativeSize; + qreal m_pieStartAngle; + qreal m_pieEndAngle; + qreal m_sum; + qreal m_holeRelativeSize; + +public: + friend class QLegendPrivate; + Q_DECLARE_PUBLIC(QPieSeries) +}; + +QT_CHARTS_END_NAMESPACE + +#endif // QPIESERIES_P_H diff --git a/src/charts/piechart/qpieslice.cpp b/src/charts/piechart/qpieslice.cpp new file mode 100644 index 00000000..48227e26 --- /dev/null +++ b/src/charts/piechart/qpieslice.cpp @@ -0,0 +1,794 @@ +/**************************************************************************** +** +** 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 "qpieslice.h" +#include "qpieslice_p.h" + +QT_CHARTS_BEGIN_NAMESPACE + +/*! + \class QPieSlice + \inmodule Qt Charts + \brief Defines a slice in pie series. + + This object defines the properties of a single slice in a QPieSeries. + + In addition to the obvious value and label properties the user can also control + the visual appearance of a slice. By modifying the visual appearance also means that + the user is overriding the default appearance set by the theme. + + Note that if the user has customized slices and theme is changed all customizations will be lost. + + To enable user interaction with the pie some basic signals are provided about clicking and hovering. +*/ + +/*! + \qmltype PieSlice + \instantiates QPieSlice + \inqmlmodule QtCharts + + \brief Defines a slice in pie series. + + PieSlice defines the properties of a single slice in a PieSeries. The element should be used + as a child for a PieSeries. For example: + \snippet qmlpiechart/qml/qmlpiechart/main.qml 2 + + An alternative (dynamic) method for adding slices to a PieSeries is using PieSeries.append + method. + \snippet qmlpiechart/qml/qmlpiechart/main.qml 4 + + In that case you may want to use PieSeries.at or PieSeries.find to access the properties of + an individual PieSlice instance. + \snippet qmlpiechart/qml/qmlpiechart/main.qml 5 + \sa PieSeries +*/ + +/*! + \enum QPieSlice::LabelPosition + + This enum describes the position of the slice label. + + \value LabelOutside Label is outside the slice with an arm. + \value LabelInsideHorizontal Label is centered inside the slice and laid out horizontally. + \value LabelInsideTangential Label is centered inside the slice and rotated to be parallel to the tangential of the slice's arc. + \value LabelInsideNormal Label is centered inside the slice rotated to be parallel to the normal of the slice's arc. + */ + +/*! + \property QPieSlice::label + Label of the slice. + \sa labelVisible, labelBrush, labelFont, labelArmLengthFactor +*/ +/*! + \qmlproperty string PieSlice::label + Label (text) of the slice. +*/ + +/*! + \fn void QPieSlice::labelChanged() + This signal emitted when the slice label has been changed. + \sa label +*/ +/*! + \qmlsignal PieSlice::onLabelChanged() + This signal emitted when the slice label has been changed. + \sa label +*/ + +/*! + \property QPieSlice::value + Value of the slice. + Note that if users sets a negative value it is converted to a positive value. + \sa percentage(), QPieSeries::sum() +*/ +/*! + \qmlproperty real PieSlice::value + Value of the slice. Note that if users sets a negative value it is converted to a positive value. +*/ + +/*! + \fn void QPieSlice::valueChanged() + This signal is emitted when the slice value changes. + \sa value +*/ +/*! + \qmlsignal PieSlice::onValueChanged() + This signal is emitted when the slice value changes. + \sa value +*/ + +/*! + \property QPieSlice::labelVisible + Defines the visibility of slice label. By default the label is not visible. + \sa label, labelBrush, labelFont, labelArmLengthFactor +*/ +/*! + \qmlproperty bool PieSlice::labelVisible + Defines the visibility of slice label. By default the label is not visible. +*/ + +/*! + \fn void QPieSlice::labelVisibleChanged() + This signal emitted when visibility of the slice label has changed. + \sa labelVisible +*/ +/*! + \qmlsignal PieSlice::onLabelVisibleChanged() + This signal emitted when visibility of the slice label has changed. + \sa labelVisible +*/ + +/*! + \property QPieSlice::exploded + If set to true the slice is "exploded" away from the pie. + \sa explodeDistanceFactor +*/ +/*! + \qmlproperty bool PieSlice::exploded + If set to true the slice is "exploded" away from the pie. + \sa explodeDistanceFactor +*/ + +/*! + \property QPieSlice::pen + Pen used to draw the slice border. +*/ + +/*! + \fn void QPieSlice::penChanged() + This signal is emitted when the pen of the slice has changed. + \sa pen +*/ + +/*! + \property QPieSlice::borderColor + Color used to draw the slice border. + This is a convenience property for modifying the slice pen. + \sa pen, borderWidth +*/ +/*! + \qmlproperty color PieSlice::borderColor + Color used to draw the slice border (pen color). + \sa borderWidth +*/ + +/*! + \fn void QPieSlice::borderColorChanged() + This signal is emitted when slice border color changes. + \sa pen, borderColor +*/ +/*! + \qmlsignal PieSlice::onBorderColorChanged() + This signal is emitted when slice border color changes. + \sa borderColor +*/ + +/*! + \property QPieSlice::borderWidth + Width of the slice border. + This is a convenience property for modifying the slice pen. + \sa pen, borderColor +*/ +/*! + \qmlproperty int PieSlice::borderWidth + Width of the slice border. + This is a convenience property for modifying the slice pen. + \sa borderColor +*/ + +/*! + \fn void QPieSlice::borderWidthChanged() + This signal is emitted when slice border width changes. + \sa pen, borderWidth +*/ +/*! + \qmlsignal PieSlice::onBorderWidthChanged() + This signal is emitted when slice border width changes. + \sa borderWidth +*/ + +/*! + \property QPieSlice::brush + Brush used to draw the slice. +*/ + +/*! + \fn void QPieSlice::brushChanged() + This signal is emitted when the brush of the slice has changed. + \sa brush +*/ + +/*! + \qmlproperty QString PieSlice::brushFilename + The name of the file used as a brush for the slice. +*/ + +/*! + \property QPieSlice::color + Fill (brush) color of the slice. + This is a convenience property for modifying the slice brush. + \sa brush +*/ +/*! + \qmlproperty color PieSlice::color + Fill (brush) color of the slice. +*/ + +/*! + \fn void QPieSlice::colorChanged() + This signal is emitted when slice color changes. + \sa brush +*/ +/*! + \qmlsignal PieSlice::onColorChanged() + This signal is emitted when slice color changes. +*/ + +/*! + \property QPieSlice::labelBrush + Brush used to draw label and label arm of the slice. + \sa label, labelVisible, labelFont, labelArmLengthFactor +*/ + +/*! + \fn void QPieSlice::labelBrushChanged() + This signal is emitted when the label brush of the slice has changed. + \sa labelBrush +*/ + +/*! + \property QPieSlice::labelColor + Color used to draw the slice label. + This is a convenience property for modifying the slice label brush. + \sa labelBrush +*/ +/*! + \qmlproperty color PieSlice::labelColor + Color used to draw the slice label. +*/ + +/*! + \fn void QPieSlice::labelColorChanged() + This signal is emitted when slice label color changes. + \sa labelColor +*/ +/*! + \qmlsignal PieSlice::onLabelColorChanged() + This signal is emitted when slice label color changes. + \sa labelColor +*/ + +/*! + \property QPieSlice::labelFont + Font used for drawing label text. + \sa label, labelVisible, labelArmLengthFactor +*/ + +/*! + \fn void QPieSlice::labelFontChanged() + This signal is emitted when the label font of the slice has changed. + \sa labelFont +*/ + +/*! + \qmlproperty Font PieSlice::labelFont + + Defines the font used for slice label. + + See the Qt documentation for more details of Font. + + \sa labelVisible, labelPosition +*/ + +/*! + \property QPieSlice::labelPosition + Position of the slice label. + \sa label, labelVisible +*/ +/*! + \qmlproperty LabelPosition PieSlice::labelPosition + Position of the slice label. One of PieSlice.LabelOutside, PieSlice.LabelInsideHorizontal, + PieSlice.LabelInsideTangential or PieSlice.LabelInsideNormal. By default the position is + PieSlice.LabelOutside. + \sa labelVisible +*/ + +/*! + \property QPieSlice::labelArmLengthFactor + Defines the length of the label arm. + The factor is relative to pie radius. For example: + 1.0 means the length is the same as the radius. + 0.5 means the length is half of the radius. + By default the arm length is 0.15 + \sa label, labelVisible, labelBrush, labelFont +*/ +/*! + \qmlproperty real PieSlice::labelArmLengthFactor + Defines the length of the label arm. + The factor is relative to pie radius. For example: + 1.0 means the length is the same as the radius. + 0.5 means the length is half of the radius. + By default the arm length is 0.15 + \sa labelVisible +*/ + +/*! + \property QPieSlice::explodeDistanceFactor + When the slice is exploded this factor defines how far the slice is exploded away from the pie. + The factor is relative to pie radius. For example: + 1.0 means the distance is the same as the radius. + 0.5 means the distance is half of the radius. + By default the distance is is 0.15 + \sa exploded +*/ +/*! + \qmlproperty real PieSlice::explodeDistanceFactor + When the slice is exploded this factor defines how far the slice is exploded away from the pie. + The factor is relative to pie radius. For example: + 1.0 means the distance is the same as the radius. + 0.5 means the distance is half of the radius. + By default the distance is is 0.15 + \sa exploded +*/ + +/*! + \property QPieSlice::percentage + Percentage of the slice compared to the sum of all slices in the series. + The actual value ranges from 0.0 to 1.0. + Updated automatically once the slice is added to the series. + \sa value, QPieSeries::sum +*/ +/*! + \qmlproperty real PieSlice::percentage + Percentage of the slice compared to the sum of all slices in the series. + The actual value ranges from 0.0 to 1.0. + Updated automatically once the slice is added to the series. +*/ + +/*! + \fn void QPieSlice::percentageChanged() + This signal is emitted when the percentage of the slice has changed. + \sa percentage +*/ +/*! + \qmlsignal void PieSlice::onPercentageChanged() + This signal is emitted when the percentage of the slice has changed. + \sa percentage +*/ + +/*! + \property QPieSlice::startAngle + Defines the starting angle of this slice in the series it belongs to. + Full pie is 360 degrees where 0 degrees is at 12 a'clock. + Updated automatically once the slice is added to the series. +*/ +/*! + \qmlproperty real PieSlice::startAngle + Defines the starting angle of this slice in the series it belongs to. + Full pie is 360 degrees where 0 degrees is at 12 a'clock. + Updated automatically once the slice is added to the series. +*/ + +/*! + \fn void QPieSlice::startAngleChanged() + This signal is emitted when the starting angle f the slice has changed. + \sa startAngle +*/ +/*! + \qmlsignal PieSlice::onStartAngleChanged() + This signal is emitted when the starting angle f the slice has changed. + \sa startAngle +*/ + +/*! + \property QPieSlice::angleSpan + Span of the slice in degrees. + Full pie is 360 degrees where 0 degrees is at 12 a'clock. + Updated automatically once the slice is added to the series. +*/ +/*! + \qmlproperty real PieSlice::angleSpan + Span of the slice in degrees. + Full pie is 360 degrees where 0 degrees is at 12 a'clock. + Updated automatically once the slice is added to the series. +*/ + +/*! + \fn void QPieSlice::angleSpanChanged() + This signal is emitted when the angle span of the slice has changed. + \sa angleSpan +*/ +/*! + \qmlsignal PieSlice::onAngleSpanChanged() + This signal is emitted when the angle span of the slice has changed. + \sa angleSpan +*/ + +/*! + \fn void QPieSlice::clicked() + This signal is emitted when user has clicked the slice. + \sa QPieSeries::clicked() +*/ +/*! + \qmlsignal PieSlice::onClicked() + This signal is emitted when user has clicked the slice. +*/ + +/*! + \fn void QPieSlice::hovered(bool state) + This signal is emitted when user has hovered over or away from the slice. + \a state is true when user has hovered over the slice and false when hover has moved away from the slice. + \sa QPieSeries::hovered() +*/ +/*! + \qmlsignal PieSlice::onHovered(bool state) + This signal is emitted when user has hovered over or away from the slice. + \a state is true when user has hovered over the slice and false when hover has moved away from the slice. +*/ + +/*! + Constructs an empty slice with a \a parent. + \sa QPieSeries::append(), QPieSeries::insert() +*/ +QPieSlice::QPieSlice(QObject *parent) + : QObject(parent), + d_ptr(new QPieSlicePrivate(this)) +{ + +} + +/*! + Constructs an empty slice with given \a value, \a label and a \a parent. + \sa QPieSeries::append(), QPieSeries::insert() +*/ +QPieSlice::QPieSlice(QString label, qreal value, QObject *parent) + : QObject(parent), + d_ptr(new QPieSlicePrivate(this)) +{ + setValue(value); + setLabel(label); +} + +/*! + Destroys the slice. + User should not delete the slice if it has been added to the series. +*/ +QPieSlice::~QPieSlice() +{ + +} + +void QPieSlice::setLabel(QString label) +{ + if (d_ptr->m_data.m_labelText != label) { + d_ptr->m_data.m_labelText = label; + emit labelChanged(); + } +} + +QString QPieSlice::label() const +{ + return d_ptr->m_data.m_labelText; +} + +void QPieSlice::setValue(qreal value) +{ + value = qAbs(value); // negative values not allowed + if (!qFuzzyCompare(d_ptr->m_data.m_value, value)) { + d_ptr->m_data.m_value = value; + emit valueChanged(); + } +} + +qreal QPieSlice::value() const +{ + return d_ptr->m_data.m_value; +} + +void QPieSlice::setLabelVisible(bool visible) +{ + if (d_ptr->m_data.m_isLabelVisible != visible) { + d_ptr->m_data.m_isLabelVisible = visible; + emit labelVisibleChanged(); + } +} + +bool QPieSlice::isLabelVisible() const +{ + return d_ptr->m_data.m_isLabelVisible; +} + +void QPieSlice::setExploded(bool exploded) +{ + if (d_ptr->m_data.m_isExploded != exploded) { + d_ptr->m_data.m_isExploded = exploded; + emit d_ptr->explodedChanged(); + } +} + +QPieSlice::LabelPosition QPieSlice::labelPosition() +{ + return d_ptr->m_data.m_labelPosition; +} + +void QPieSlice::setLabelPosition(LabelPosition position) +{ + if (d_ptr->m_data.m_labelPosition != position) { + d_ptr->m_data.m_labelPosition = position; + emit d_ptr->labelPositionChanged(); + } +} + +bool QPieSlice::isExploded() const +{ + return d_ptr->m_data.m_isExploded; +} + +void QPieSlice::setPen(const QPen &pen) +{ + d_ptr->setPen(pen, false); +} + +QPen QPieSlice::pen() const +{ + return d_ptr->m_data.m_slicePen; +} + +QColor QPieSlice::borderColor() +{ + return pen().color(); +} + +void QPieSlice::setBorderColor(QColor color) +{ + QPen p = pen(); + if (color != p.color()) { + p.setColor(color); + setPen(p); + } +} + +int QPieSlice::borderWidth() +{ + return pen().width(); +} + +void QPieSlice::setBorderWidth(int width) +{ + QPen p = pen(); + if (width != p.width()) { + p.setWidth(width); + setPen(p); + } +} + +void QPieSlice::setBrush(const QBrush &brush) +{ + d_ptr->setBrush(brush, false); +} + +QBrush QPieSlice::brush() const +{ + return d_ptr->m_data.m_sliceBrush; +} + +QColor QPieSlice::color() +{ + return brush().color(); +} + +void QPieSlice::setColor(QColor color) +{ + QBrush b = brush(); + + if (b == QBrush()) + b.setStyle(Qt::SolidPattern); + b.setColor(color); + setBrush(b); +} + +void QPieSlice::setLabelBrush(const QBrush &brush) +{ + d_ptr->setLabelBrush(brush, false); +} + +QBrush QPieSlice::labelBrush() const +{ + return d_ptr->m_data.m_labelBrush; +} + +QColor QPieSlice::labelColor() +{ + return labelBrush().color(); +} + +void QPieSlice::setLabelColor(QColor color) +{ + QBrush b = labelBrush(); + if (color != b.color()) { + b.setColor(color); + setLabelBrush(b); + } +} + +void QPieSlice::setLabelFont(const QFont &font) +{ + d_ptr->setLabelFont(font, false); +} + +QFont QPieSlice::labelFont() const +{ + return d_ptr->m_data.m_labelFont; +} + +void QPieSlice::setLabelArmLengthFactor(qreal factor) +{ + if (!qFuzzyCompare(d_ptr->m_data.m_labelArmLengthFactor, factor)) { + d_ptr->m_data.m_labelArmLengthFactor = factor; + emit d_ptr->labelArmLengthFactorChanged(); + } +} + +qreal QPieSlice::labelArmLengthFactor() const +{ + return d_ptr->m_data.m_labelArmLengthFactor; +} + +void QPieSlice::setExplodeDistanceFactor(qreal factor) +{ + if (!qFuzzyCompare(d_ptr->m_data.m_explodeDistanceFactor, factor)) { + d_ptr->m_data.m_explodeDistanceFactor = factor; + emit d_ptr->explodeDistanceFactorChanged(); + } +} + +qreal QPieSlice::explodeDistanceFactor() const +{ + return d_ptr->m_data.m_explodeDistanceFactor; +} + +qreal QPieSlice::percentage() const +{ + return d_ptr->m_data.m_percentage; +} + +qreal QPieSlice::startAngle() const +{ + return d_ptr->m_data.m_startAngle; +} + +qreal QPieSlice::angleSpan() const +{ + return d_ptr->m_data.m_angleSpan; +} + +/*! + Returns the series that this slice belongs to. + + \sa QPieSeries::append() +*/ +QPieSeries *QPieSlice::series() const +{ + return d_ptr->m_series; +} + +QPieSlicePrivate::QPieSlicePrivate(QPieSlice *parent) + : QObject(parent), + q_ptr(parent), + m_series(0) +{ + +} + +QPieSlicePrivate::~QPieSlicePrivate() +{ + +} + +QPieSlicePrivate *QPieSlicePrivate::fromSlice(QPieSlice *slice) +{ + return slice->d_func(); +} + +void QPieSlicePrivate::setPen(const QPen &pen, bool themed) +{ + if (m_data.m_slicePen != pen) { + + QPen oldPen = m_data.m_slicePen; + + m_data.m_slicePen = pen; + m_data.m_slicePen.setThemed(themed); + + emit q_ptr->penChanged(); + if (oldPen.color() != pen.color()) + emit q_ptr->borderColorChanged(); + if (oldPen.width() != pen.width()) + emit q_ptr->borderWidthChanged(); + } +} + +void QPieSlicePrivate::setBrush(const QBrush &brush, bool themed) +{ + if (m_data.m_sliceBrush != brush) { + + QBrush oldBrush = m_data.m_sliceBrush; + + m_data.m_sliceBrush = brush; + m_data.m_sliceBrush.setThemed(themed); + + emit q_ptr->brushChanged(); + if (oldBrush.color() != brush.color()) + emit q_ptr->colorChanged(); + } +} + +void QPieSlicePrivate::setLabelBrush(const QBrush &brush, bool themed) +{ + if (m_data.m_labelBrush != brush) { + + QBrush oldBrush = m_data.m_labelBrush; + + m_data.m_labelBrush = brush; + m_data.m_labelBrush.setThemed(themed); + + emit q_ptr->labelBrushChanged(); + if (oldBrush.color() != brush.color()) + emit q_ptr->labelColorChanged(); + } +} + +void QPieSlicePrivate::setLabelFont(const QFont &font, bool themed) +{ + if (m_data.m_labelFont != font) { + m_data.m_labelFont = font; + m_data.m_labelFont.setThemed(themed); + emit q_ptr->labelFontChanged(); + } +} + +void QPieSlicePrivate::setPercentage(qreal percentage) +{ + if (!qFuzzyCompare(m_data.m_percentage, percentage)) { + m_data.m_percentage = percentage; + emit q_ptr->percentageChanged(); + } +} + +void QPieSlicePrivate::setStartAngle(qreal angle) +{ + if (!qFuzzyCompare(m_data.m_startAngle, angle)) { + m_data.m_startAngle = angle; + emit q_ptr->startAngleChanged(); + } +} + +void QPieSlicePrivate::setAngleSpan(qreal span) +{ + if (!qFuzzyCompare(m_data.m_angleSpan, span)) { + m_data.m_angleSpan = span; + emit q_ptr->angleSpanChanged(); + } +} + +QT_CHARTS_END_NAMESPACE + +QT_CHARTS_USE_NAMESPACE +#include "moc_qpieslice.cpp" +#include "moc_qpieslice_p.cpp" diff --git a/src/charts/piechart/qpieslice.h b/src/charts/piechart/qpieslice.h new file mode 100644 index 00000000..8be5d3cc --- /dev/null +++ b/src/charts/piechart/qpieslice.h @@ -0,0 +1,147 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QPIESLICE_H +#define QPIESLICE_H + +#include <QtCharts/qchartglobal.h> +#include <QObject> +#include <QPen> +#include <QBrush> +#include <QFont> + +QT_CHARTS_BEGIN_NAMESPACE +class QPieSlicePrivate; +class QPieSeries; + +class QT_CHARTS_EXPORT QPieSlice : public QObject +{ + Q_OBJECT + Q_ENUMS(LabelPosition) + Q_PROPERTY(QString label READ label WRITE setLabel NOTIFY labelChanged) + Q_PROPERTY(qreal value READ value WRITE setValue NOTIFY valueChanged) + Q_PROPERTY(bool labelVisible READ isLabelVisible WRITE setLabelVisible NOTIFY labelVisibleChanged) + Q_PROPERTY(LabelPosition labelPosition READ labelPosition WRITE setLabelPosition) + Q_PROPERTY(bool exploded READ isExploded WRITE setExploded) + Q_PROPERTY(QPen pen READ pen WRITE setPen NOTIFY penChanged) + Q_PROPERTY(QColor borderColor READ borderColor WRITE setBorderColor NOTIFY borderColorChanged) + Q_PROPERTY(int borderWidth READ borderWidth WRITE setBorderWidth NOTIFY borderWidthChanged) + Q_PROPERTY(QBrush brush READ brush WRITE setBrush NOTIFY brushChanged) + Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) + Q_PROPERTY(QBrush labelBrush READ labelBrush WRITE setLabelBrush NOTIFY labelBrushChanged) + Q_PROPERTY(QColor labelColor READ labelColor WRITE setLabelColor NOTIFY labelColorChanged) + Q_PROPERTY(QFont labelFont READ labelFont WRITE setLabelFont NOTIFY labelFontChanged) + Q_PROPERTY(qreal labelArmLengthFactor READ labelArmLengthFactor WRITE setLabelArmLengthFactor) + Q_PROPERTY(qreal explodeDistanceFactor READ explodeDistanceFactor WRITE setExplodeDistanceFactor) + Q_PROPERTY(qreal percentage READ percentage NOTIFY percentageChanged) + Q_PROPERTY(qreal startAngle READ startAngle NOTIFY startAngleChanged) + Q_PROPERTY(qreal angleSpan READ angleSpan NOTIFY angleSpanChanged) + +public: + enum LabelPosition { + LabelOutside, + LabelInsideHorizontal, + LabelInsideTangential, + LabelInsideNormal + }; + +public: + explicit QPieSlice(QObject *parent = 0); + QPieSlice(QString label, qreal value, QObject *parent = 0); + virtual ~QPieSlice(); + + void setLabel(QString label); + QString label() const; + + void setValue(qreal value); + qreal value() const; + + void setLabelVisible(bool visible = true); + bool isLabelVisible() const; + + LabelPosition labelPosition(); + void setLabelPosition(LabelPosition position); + + void setExploded(bool exploded = true); + bool isExploded() const; + + void setPen(const QPen &pen); + QPen pen() const; + + QColor borderColor(); + void setBorderColor(QColor color); + + int borderWidth(); + void setBorderWidth(int width); + + void setBrush(const QBrush &brush); + QBrush brush() const; + + QColor color(); + void setColor(QColor color); + + void setLabelBrush(const QBrush &brush); + QBrush labelBrush() const; + + QColor labelColor(); + void setLabelColor(QColor color); + + void setLabelFont(const QFont &font); + QFont labelFont() const; + + void setLabelArmLengthFactor(qreal factor); + qreal labelArmLengthFactor() const; + + void setExplodeDistanceFactor(qreal factor); + qreal explodeDistanceFactor() const; + + qreal percentage() const; + qreal startAngle() const; + qreal angleSpan() const; + + QPieSeries *series() const; + +Q_SIGNALS: + void clicked(); + void hovered(bool state); + void labelChanged(); + void valueChanged(); + void labelVisibleChanged(); + void penChanged(); + void brushChanged(); + void labelBrushChanged(); + void labelFontChanged(); + void percentageChanged(); + void startAngleChanged(); + void angleSpanChanged(); + void colorChanged(); + void borderColorChanged(); + void borderWidthChanged(); + void labelColorChanged(); + +private: + QPieSlicePrivate * const d_ptr; + Q_DECLARE_PRIVATE(QPieSlice) + Q_DISABLE_COPY(QPieSlice) +}; + +QT_CHARTS_END_NAMESPACE + +#endif // QPIESLICE_H diff --git a/src/charts/piechart/qpieslice_p.h b/src/charts/piechart/qpieslice_p.h new file mode 100644 index 00000000..340a4753 --- /dev/null +++ b/src/charts/piechart/qpieslice_p.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +// W A R N I N G +// ------------- +// +// This file is not part of the Qt Enterprise Chart API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef QPIESLICE_P_H +#define QPIESLICE_P_H + +#include <QObject> +#include "qpieslice.h" +#include "pieslicedata_p.h" + +QT_CHARTS_BEGIN_NAMESPACE +class QPieSeries; + +class QPieSlicePrivate : public QObject +{ + Q_OBJECT + +public: + QPieSlicePrivate(QPieSlice *parent); + ~QPieSlicePrivate(); + + static QPieSlicePrivate *fromSlice(QPieSlice *slice); + + void setPen(const QPen &pen, bool themed); + void setBrush(const QBrush &brush, bool themed); + void setLabelBrush(const QBrush &brush, bool themed); + void setLabelFont(const QFont &font, bool themed); + + void setPercentage(qreal percentage); + void setStartAngle(qreal angle); + void setAngleSpan(qreal span); + +Q_SIGNALS: + void labelPositionChanged(); + void explodedChanged(); + void labelArmLengthFactorChanged(); + void explodeDistanceFactorChanged(); + +private: + friend class QPieSeries; + friend class QPieSeriesPrivate; + friend class ChartThemeManager; + friend class PieChartItem; + + QPieSlice * const q_ptr; + Q_DECLARE_PUBLIC(QPieSlice) + + PieSliceData m_data; + QPieSeries *m_series; +}; + +QT_CHARTS_END_NAMESPACE + +#endif // QPIESLICE_P_H diff --git a/src/charts/piechart/qvpiemodelmapper.cpp b/src/charts/piechart/qvpiemodelmapper.cpp new file mode 100644 index 00000000..7b9c0a0c --- /dev/null +++ b/src/charts/piechart/qvpiemodelmapper.cpp @@ -0,0 +1,270 @@ +/**************************************************************************** +** +** 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 "qvpiemodelmapper.h" + +QT_CHARTS_BEGIN_NAMESPACE + +/*! + \class QVPieModelMapper + \inmodule Qt Charts + \brief Vertical model mapper for pie series. + \mainclass + + Model mappers allow you to use QAbstractItemModel derived models as a data source for a chart series. + Vertical model mapper is used to create a connection between QPieSeries and QAbstractItemModel derived model object that keeps the consecutive pie slices data in columns. + It is possible to use both QAbstractItemModel and QPieSeries model API. QVPieModelMapper makes sure that Pie and the model are kept in sync. + \note Used model has to support adding/removing rows/columns and modifying the data of the cells. +*/ +/*! + \qmltype VPieModelMapper + \instantiates QVPieModelMapper + \inqmlmodule QtCharts + + \brief Vertical model mapper for pie series. + + VPieModelMapper allows you to use your own QAbstractItemModel derived model with data in columns + as a data source for a pie series. It is possible to use both QAbstractItemModel and PieSeries + data API to manipulate data. VPieModelMapper keeps the Pie and the model in sync. + + The following QML example would create a pie series with four slices (assuming the model has at + least five rows). Each slice would contain a label from column 1 and a value from column 2. + \code + VPieModelMapper { + series: pieSeries + model: customModel + labelsColumn: 1 + valuesColumn: 2 + firstRow: 1 + rowCount: 4 + } + \endcode +*/ + +/*! + \property QVPieModelMapper::series + \brief Defines the QPieSeries object that is used by the mapper. + All the data in the series is discarded when it is set to the mapper. + When new series is specified the old series is disconnected (it preserves its data) +*/ +/*! + \qmlproperty PieSeries VPieModelMapper::series + Defines the PieSeries object that is used by the mapper. If you define the mapper element as a child for a + PieSeries, leave this property undefined. All the data in the series is discarded when it is set to the mapper. + When new series is specified the old series is disconnected (it preserves its data). +*/ + +/*! + \property QVPieModelMapper::model + \brief Defines the model that is used by the mapper. +*/ +/*! + \qmlproperty SomeModel VPieModelMapper::model + The QAbstractItemModel based model that is used by the mapper. You need to implement the model + and expose it to QML. Note: the model has to support adding/removing rows/columns and modifying + the data of the cells. +*/ + +/*! + \property QVPieModelMapper::valuesColumn + \brief Defines which column of the model is kept in sync with the values of the pie's slices. + + Default value is: -1 (invalid mapping) +*/ +/*! + \qmlproperty int VPieModelMapper::valuesColumn + Defines which column of the model is kept in sync with the values of the pie's slices. Default value is -1 (invalid + mapping). +*/ + +/*! + \property QVPieModelMapper::labelsColumn + \brief Defines which column of the model is kept in sync with the labels of the pie's slices. + + Default value is: -1 (invalid mapping) +*/ +/*! + \qmlproperty int VPieModelMapper::labelsColumn + Defines which column of the model is kept in sync with the labels of the pie's slices. Default value is -1 (invalid + mapping). +*/ + +/*! + \property QVPieModelMapper::firstRow + \brief Defines which row of the model contains the first slice value. + + Minimal and default value is: 0 +*/ +/*! + \qmlproperty int VPieModelMapper::firstRow + Defines which row of the model contains the first slice value. + The default value is 0. +*/ + +/*! + \property QVPieModelMapper::rowCount + \brief Defines the number of rows of the model that are mapped as the data for QPieSeries. + + Minimal and default value is: -1 (count limited by the number of rows in the model) +*/ +/*! + \qmlproperty int VPieModelMapper::columnCount + Defines the number of rows of the model that are mapped as the data for QPieSeries. The default value is + -1 (count limited by the number of rows in the model) +*/ + +/*! + \fn void QVPieModelMapper::seriesReplaced() + + Emitted when the series to which mapper is connected to has changed. +*/ + +/*! + \fn void QVPieModelMapper::modelReplaced() + + Emitted when the model to which mapper is connected to has changed. +*/ + +/*! + \fn void QVPieModelMapper::valuesColumnChanged() + + Emitted when the valuesColumn has changed. +*/ + +/*! + \fn void QVPieModelMapper::labelsColumnChanged() + + Emitted when the labelsColumn has changed. +*/ + +/*! + \fn void QVPieModelMapper::firstRowChanged() + Emitted when the firstRow has changed. +*/ + +/*! + \fn void QVPieModelMapper::rowCountChanged() + Emitted when the rowCount has changed. +*/ + +/*! + Constructs a mapper object which is a child of \a parent. +*/ +QVPieModelMapper::QVPieModelMapper(QObject *parent) : + QPieModelMapper(parent) +{ + QPieModelMapper::setOrientation(Qt::Vertical); +} + +QAbstractItemModel *QVPieModelMapper::model() const +{ + return QPieModelMapper::model(); +} + +void QVPieModelMapper::setModel(QAbstractItemModel *model) +{ + if (model != QPieModelMapper::model()) { + QPieModelMapper::setModel(model); + emit modelReplaced(); + } +} + +QPieSeries *QVPieModelMapper::series() const +{ + return QPieModelMapper::series(); +} + +void QVPieModelMapper::setSeries(QPieSeries *series) +{ + if (series != QPieModelMapper::series()) { + QPieModelMapper::setSeries(series); + emit seriesReplaced(); + } +} + +/*! + Returns which column of the model is kept in sync with the values of the pie's slices +*/ +int QVPieModelMapper::valuesColumn() const +{ + return QPieModelMapper::valuesSection(); +} + +/*! + Sets the model column that is kept in sync with the pie slices values. + Parameter \a valuesColumn specifies the row of the model. +*/ +void QVPieModelMapper::setValuesColumn(int valuesColumn) +{ + if (valuesColumn != valuesSection()) { + QPieModelMapper::setValuesSection(valuesColumn); + emit valuesColumnChanged(); + } +} + +/*! + Returns which column of the model is kept in sync with the labels of the pie's slices +*/ +int QVPieModelMapper::labelsColumn() const +{ + return QPieModelMapper::labelsSection(); +} + +/*! + Sets the model column that is kept in sync with the pie's slices labels. + Parameter \a labelsColumn specifies the row of the model. +*/ +void QVPieModelMapper::setLabelsColumn(int labelsColumn) +{ + if (labelsColumn != labelsSection()) { + QPieModelMapper::setLabelsSection(labelsColumn); + emit labelsColumnChanged(); + } +} + +int QVPieModelMapper::firstRow() const +{ + return first(); +} + +void QVPieModelMapper::setFirstRow(int firstRow) +{ + if (firstRow != first()) { + setFirst(firstRow); + emit firstRowChanged(); + } +} + +int QVPieModelMapper::rowCount() const +{ + return count(); +} + +void QVPieModelMapper::setRowCount(int rowCount) +{ + if (rowCount != count()) { + setCount(rowCount); + emit rowCountChanged(); + } +} + +#include "moc_qvpiemodelmapper.cpp" + +QT_CHARTS_END_NAMESPACE diff --git a/src/charts/piechart/qvpiemodelmapper.h b/src/charts/piechart/qvpiemodelmapper.h new file mode 100644 index 00000000..2135ff9b --- /dev/null +++ b/src/charts/piechart/qvpiemodelmapper.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QVPIEMODELMAPPER_H +#define QVPIEMODELMAPPER_H + +#include <QtCharts/qpiemodelmapper.h> + +QT_CHARTS_BEGIN_NAMESPACE +/* Comment line for syncqt to generate the fwd-include correctly, due to QTBUG-22432 */ +class QT_CHARTS_EXPORT QVPieModelMapper : public QPieModelMapper +{ + Q_OBJECT + Q_PROPERTY(QPieSeries *series READ series WRITE setSeries NOTIFY seriesReplaced) + Q_PROPERTY(QAbstractItemModel *model READ model WRITE setModel NOTIFY modelReplaced) + Q_PROPERTY(int valuesColumn READ valuesColumn WRITE setValuesColumn NOTIFY valuesColumnChanged) + Q_PROPERTY(int labelsColumn READ labelsColumn WRITE setLabelsColumn NOTIFY labelsColumnChanged) + Q_PROPERTY(int firstRow READ firstRow WRITE setFirstRow NOTIFY firstRowChanged) + Q_PROPERTY(int rowCount READ rowCount WRITE setRowCount NOTIFY rowCountChanged) + +public: + explicit QVPieModelMapper(QObject *parent = 0); + + QAbstractItemModel *model() const; + void setModel(QAbstractItemModel *model); + + QPieSeries *series() const; + void setSeries(QPieSeries *series); + + int valuesColumn() const; + void setValuesColumn(int valuesColumn); + + int labelsColumn() const; + void setLabelsColumn(int labelsColumn); + + int firstRow() const; + void setFirstRow(int firstRow); + + int rowCount() const; + void setRowCount(int rowCount); + +Q_SIGNALS: + void seriesReplaced(); + void modelReplaced(); + void valuesColumnChanged(); + void labelsColumnChanged(); + void firstRowChanged(); + void rowCountChanged(); +}; + +QT_CHARTS_END_NAMESPACE + +#endif // QVPIEMODELMAPPER_H |