From f4b980d7defa3c3aa15cd9d8aeb50ee669322453 2012-03-16 13:29:37 From: Jani Honkonen Date: 2012-03-16 13:29:37 Subject: [PATCH] Refactoring pie series and animations. --- diff --git a/demos/piechartcustomization/main.cpp b/demos/piechartcustomization/main.cpp index 13d9b17..3bbe47c 100644 --- a/demos/piechartcustomization/main.cpp +++ b/demos/piechartcustomization/main.cpp @@ -351,6 +351,7 @@ public: m_endAngle->setSingleStep(1); QPushButton *addSlice = new QPushButton("Add slice"); + QPushButton *insertSlice = new QPushButton("Insert slice"); QFormLayout* seriesSettingsLayout = new QFormLayout(); seriesSettingsLayout->addRow("Horizontal position", m_hPosition); @@ -359,6 +360,7 @@ public: seriesSettingsLayout->addRow("Start angle", m_startAngle); seriesSettingsLayout->addRow("End angle", m_endAngle); seriesSettingsLayout->addRow(addSlice); + seriesSettingsLayout->addRow(insertSlice); QGroupBox* seriesSettings = new QGroupBox("Series"); seriesSettings->setLayout(seriesSettingsLayout); @@ -368,6 +370,7 @@ public: connect(m_startAngle, SIGNAL(valueChanged(double)), this, SLOT(updateSerieSettings())); connect(m_endAngle, SIGNAL(valueChanged(double)), this, SLOT(updateSerieSettings())); connect(addSlice, SIGNAL(clicked()), this, SLOT(addSlice())); + connect(insertSlice, SIGNAL(clicked()), this, SLOT(insertSlice())); // slice settings m_sliceName = new QLabel(""); @@ -526,6 +529,16 @@ public Q_SLOTS: *m_series << new CustomSlice(10.0, "Slice " + QString::number(m_series->count())); } + void insertSlice() + { + if (!m_slice) + return; + + int i = m_series->slices().indexOf(m_slice); + + m_series->insert(i, new CustomSlice(10.0, "Slice " + QString::number(m_series->count()))); + } + void removeSlice() { if (!m_slice) diff --git a/examples/piechartdrilldown/main.cpp b/examples/piechartdrilldown/main.cpp index 078bcaf..03347a1 100644 --- a/examples/piechartdrilldown/main.cpp +++ b/examples/piechartdrilldown/main.cpp @@ -78,6 +78,7 @@ int main(int argc, char *argv[]) DrilldownChart* drilldownChart = new DrilldownChart(&window); drilldownChart->setRenderHint(QPainter::Antialiasing); drilldownChart->setChartTheme(QChart::ChartThemeVanilla); + drilldownChart->setAnimationOptions(QChart::AllAnimations); QPieSeries* yearSeries = new QPieSeries(&window); yearSeries->setTitle("Sales by year - All"); diff --git a/src/animations/chartanimator.cpp b/src/animations/chartanimator.cpp index 7ef3645..a8964fc 100644 --- a/src/animations/chartanimator.cpp +++ b/src/animations/chartanimator.cpp @@ -184,11 +184,25 @@ void ChartAnimator::updateLayout(XYChartItem* item, QVector& newPoints) QTimer::singleShot(0,animation,SLOT(start())); } -void ChartAnimator::applyLayout(PieChartItem* item, QVector &layout) +void ChartAnimator::addAnimation(PieChartItem* item, QPieSlice *slice, PieSliceLayout &layout) { PieAnimation* animation = static_cast(m_animations.value(item)); Q_ASSERT(animation); - animation->setValues(layout); + animation->addSlice(slice, layout); +} + +void ChartAnimator::removeAnimation(PieChartItem* item, QPieSlice *slice) +{ + PieAnimation* animation = static_cast(m_animations.value(item)); + Q_ASSERT(animation); + animation->removeSlice(slice); +} + +void ChartAnimator::updateLayout(PieChartItem* item, QVector &layout) +{ + PieAnimation* animation = static_cast(m_animations.value(item)); + Q_ASSERT(animation); + animation->updateValues(layout); } void ChartAnimator::updateLayout(PieChartItem* item, PieSliceLayout &layout) diff --git a/src/animations/chartanimator_p.h b/src/animations/chartanimator_p.h index cb28d53..21e8e0d 100644 --- a/src/animations/chartanimator_p.h +++ b/src/animations/chartanimator_p.h @@ -32,7 +32,9 @@ public: void updateLayout(XYChartItem* item, QVector& layout); void applyLayout(AxisItem* item, QVector& layout); - void applyLayout(PieChartItem* item, QVector &layout); + void addAnimation(PieChartItem* item, QPieSlice *slice, PieSliceLayout &layout); + void removeAnimation(PieChartItem* item, QPieSlice *slice); + void updateLayout(PieChartItem* item, QVector &layout); void updateLayout(PieChartItem* item, PieSliceLayout &layout); void setState(State state,const QPointF& point = QPointF()); diff --git a/src/animations/pieanimation.cpp b/src/animations/pieanimation.cpp index 367c782..03e79d9 100644 --- a/src/animations/pieanimation.cpp +++ b/src/animations/pieanimation.cpp @@ -16,53 +16,10 @@ PieAnimation::~PieAnimation() { } -void PieAnimation::setValues(QVector& newValues) +void PieAnimation::updateValues(QVector& newValues) { - PieSliceAnimation *animation = 0; - - foreach (PieSliceLayout endLayout, newValues) { - animation = m_animations.value(endLayout.m_data); - if (animation) { - // existing slice - animation->stop(); - animation->updateValue(endLayout); - } else { - // new slice - animation = new PieSliceAnimation(m_item); - m_animations.insert(endLayout.m_data, animation); - PieSliceLayout startLayout = endLayout; - startLayout.m_radius = 0; - //startLayout.m_startAngle = 0; - //startLayout.m_angleSpan = 0; - animation->setValue(startLayout, endLayout); - } - animation->setDuration(1000); - animation->setEasingCurve(QEasingCurve::OutQuart); - QTimer::singleShot(0, animation, SLOT(start())); // TODO: use sequential animation? - } - - foreach (QPieSlice *s, m_animations.keys()) { - bool isFound = false; - foreach (PieSliceLayout layout, newValues) { - if (s == layout.m_data) - isFound = true; - } - if (!isFound) { - // slice has been deleted - animation = m_animations.value(s); - animation->stop(); - PieSliceLayout endLayout = m_animations.value(s)->currentSliceValue(); - endLayout.m_radius = 0; - // TODO: find the actual angle where this slice disappears - endLayout.m_startAngle = endLayout.m_startAngle + endLayout.m_angleSpan; - endLayout.m_angleSpan = 0; - animation->updateValue(endLayout); - animation->setDuration(1000); - animation->setEasingCurve(QEasingCurve::OutQuart); - connect(animation, SIGNAL(finished()), this, SLOT(destroySliceAnimationComplete())); - QTimer::singleShot(0, animation, SLOT(start())); - } - } + foreach (PieSliceLayout endLayout, newValues) + updateValue(endLayout); } void PieAnimation::updateValue(PieSliceLayout& endLayout) @@ -70,9 +27,47 @@ void PieAnimation::updateValue(PieSliceLayout& endLayout) PieSliceAnimation *animation = m_animations.value(endLayout.m_data); Q_ASSERT(animation); animation->stop(); + + animation->updateValue(endLayout); + animation->setDuration(1000); + animation->setEasingCurve(QEasingCurve::OutQuart); + + QTimer::singleShot(0, animation, SLOT(start())); +} + +void PieAnimation::addSlice(QPieSlice *slice, PieSliceLayout endLayout) +{ + PieSliceAnimation *animation = new PieSliceAnimation(m_item); + m_animations.insert(slice, animation); + + PieSliceLayout startLayout = endLayout; + startLayout.m_radius = 0; + startLayout.m_startAngle = endLayout.m_startAngle + (endLayout.m_angleSpan/2); + startLayout.m_angleSpan = 0; + animation->setValue(startLayout, endLayout); + + animation->setDuration(1000); + animation->setEasingCurve(QEasingCurve::OutQuart); + QTimer::singleShot(0, animation, SLOT(start())); +} + +void PieAnimation::removeSlice(QPieSlice *slice) +{ + PieSliceAnimation *animation = m_animations.value(slice); + Q_ASSERT(animation); + animation->stop(); + + PieSliceLayout endLayout = animation->currentSliceValue(); + endLayout.m_radius = 0; + // TODO: find the actual angle where this slice disappears + endLayout.m_startAngle = endLayout.m_startAngle + endLayout.m_angleSpan; + endLayout.m_angleSpan = 0; + animation->updateValue(endLayout); animation->setDuration(1000); animation->setEasingCurve(QEasingCurve::OutQuart); + + connect(animation, SIGNAL(finished()), this, SLOT(destroySliceAnimationComplete())); QTimer::singleShot(0, animation, SLOT(start())); } diff --git a/src/animations/pieanimation_p.h b/src/animations/pieanimation_p.h index 4e70469..b840008 100644 --- a/src/animations/pieanimation_p.h +++ b/src/animations/pieanimation_p.h @@ -16,8 +16,10 @@ class PieAnimation : public ChartAnimation public: PieAnimation(PieChartItem *item); ~PieAnimation(); - void setValues(QVector& newValues); + void updateValues(QVector& newValues); void updateValue(PieSliceLayout& newValue); + void addSlice(QPieSlice *slice, PieSliceLayout endLayout); + void removeSlice(QPieSlice *slice); public: // from QVariantAnimation void updateCurrentValue(const QVariant &value); diff --git a/src/piechart/piechartitem.cpp b/src/piechart/piechartitem.cpp index 0fd7ba7..22e6eac 100644 --- a/src/piechart/piechartitem.cpp +++ b/src/piechart/piechartitem.cpp @@ -6,7 +6,7 @@ #include "chartanimator_p.h" #include #include - +#include QTCOMMERCIALCHART_BEGIN_NAMESPACE @@ -15,7 +15,12 @@ PieChartItem::PieChartItem(QGraphicsItem *parent, QPieSeries *series) m_series(series) { Q_ASSERT(series); - connect(series, SIGNAL(changed()), this, SLOT(handleSeriesChanged())); + connect(series, SIGNAL(added(QList)), this, SLOT(handleSlicesAdded(QList))); + connect(series, SIGNAL(removed(QList)), this, SLOT(handleSlicesRemoved(QList))); + connect(series, SIGNAL(piePositionChanged()), this, SLOT(handlePieLayoutChanged())); + connect(series, SIGNAL(pieSizeChanged()), this, SLOT(handlePieLayoutChanged())); + + QTimer::singleShot(0, this, SLOT(initialize())); // Note: the following does not affect as long as the item does not have anything to paint setZValue(ChartPresenter::PieSeriesZValue); @@ -35,7 +40,41 @@ void PieChartItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QW //painter->drawRect(m_debugRect); } -void PieChartItem::handleSeriesChanged() +void PieChartItem::initialize() +{ + handleSlicesAdded(m_series->m_slices); +} + +void PieChartItem::handleSlicesAdded(QList slices) +{ + foreach (QPieSlice *s, slices) { + PieSlice* slice = new PieSlice(this); + m_slices.insert(s, slice); + connect(s, SIGNAL(changed()), this, SLOT(handleSliceChanged())); + connect(slice, SIGNAL(clicked()), s, SIGNAL(clicked())); + connect(slice, SIGNAL(hoverEnter()), s, SIGNAL(hoverEnter())); + connect(slice, SIGNAL(hoverLeave()), s, SIGNAL(hoverLeave())); + + PieSliceLayout layout = calculateSliceLayout(s); + + if (m_animator) + m_animator->addAnimation(this, s, layout); + else + setLayout(layout); + } +} + +void PieChartItem::handleSlicesRemoved(QList slices) +{ + foreach (QPieSlice *s, slices) { + if (m_animator) + m_animator->removeAnimation(this, s); + else + destroySlice(s); + } +} + +void PieChartItem::handlePieLayoutChanged() { QVector layout = calculateLayout(); applyLayout(layout); @@ -46,15 +85,8 @@ void PieChartItem::handleSliceChanged() { QPieSlice* slice = qobject_cast(sender()); Q_ASSERT(m_slices.contains(slice)); - - //qDebug() << "PieChartItem::handleSliceChanged" << slice->label(); - - // TODO: Optimize. No need to calculate everything. - QVector layout = calculateLayout(); - foreach (PieSliceLayout sl, layout) { - if (sl.m_data == slice) - updateLayout(sl); - } + PieSliceLayout layout = calculateSliceLayout(slice); + updateLayout(layout); update(); } @@ -67,45 +99,50 @@ void PieChartItem::handleGeometryChanged(const QRectF& rect) { prepareGeometryChange(); m_rect = rect; - QVector sliceLayout = calculateLayout(); - applyLayout(sliceLayout); - update(); + handlePieLayoutChanged(); } - -QVector PieChartItem::calculateLayout() +void PieChartItem::calculatePieLayout() { // find pie center coordinates - QPointF center; - center.setX(m_rect.left() + (m_rect.width() * m_series->pieHorizontalPosition())); - center.setY(m_rect.top() + (m_rect.height() * m_series->pieVerticalPosition())); + m_pieCenter.setX(m_rect.left() + (m_rect.width() * m_series->pieHorizontalPosition())); + m_pieCenter.setY(m_rect.top() + (m_rect.height() * m_series->pieVerticalPosition())); // find maximum radius for pie - qreal radius = m_rect.height() / 2; + m_pieRadius = m_rect.height() / 2; if (m_rect.width() < m_rect.height()) - radius = m_rect.width() / 2; + m_pieRadius = m_rect.width() / 2; // apply size factor - radius *= m_series->pieSize(); + m_pieRadius *= m_series->pieSize(); +} +PieSliceLayout PieChartItem::calculateSliceLayout(QPieSlice *slice) +{ + PieSliceLayout sliceLayout; + sliceLayout.m_data = slice; + sliceLayout.m_center = PieSlice::sliceCenter(m_pieCenter, m_pieRadius, slice); + sliceLayout.m_radius = m_pieRadius; + sliceLayout.m_startAngle = slice->startAngle(); + sliceLayout.m_angleSpan = slice->m_angleSpan; + return sliceLayout; +} + +QVector PieChartItem::calculateLayout() +{ + calculatePieLayout(); QVector layout; foreach (QPieSlice* s, m_series->slices()) { - PieSliceLayout sliceLayout; - sliceLayout.m_data = s; - sliceLayout.m_center = PieSlice::sliceCenter(center, radius, s); - sliceLayout.m_radius = radius; - sliceLayout.m_startAngle = s->startAngle(); - sliceLayout.m_angleSpan = s->m_angleSpan; - layout << sliceLayout; + if (m_slices.contains(s)) // calculate layout only for those slices that are already visible + layout << calculateSliceLayout(s); } - return layout; } void PieChartItem::applyLayout(QVector &layout) { if (m_animator) - m_animator->applyLayout(this, layout); + m_animator->updateLayout(this, layout); else setLayout(layout); } @@ -121,53 +158,20 @@ void PieChartItem::updateLayout(PieSliceLayout &layout) void PieChartItem::setLayout(QVector &layout) { foreach (PieSliceLayout l, layout) { - - // find slice PieSlice *slice = m_slices.value(l.m_data); - if (!slice) { - // add a new slice - slice = new PieSlice(this); - m_slices.insert(l.m_data, slice); - - // connect signals - connect(l.m_data, SIGNAL(changed()), this, SLOT(handleSliceChanged())); - connect(slice, SIGNAL(clicked()), l.m_data, SIGNAL(clicked())); - connect(slice, SIGNAL(hoverEnter()), l.m_data, SIGNAL(hoverEnter())); - connect(slice, SIGNAL(hoverLeave()), l.m_data, SIGNAL(hoverLeave())); - } - - // update + Q_ASSERT(slice); slice->setLayout(l); + slice->updateData(l.m_data); slice->updateGeometry(); slice->update(); } - - // delete slices - foreach (QPieSlice *s, m_slices.keys()) { - - bool found = false; - foreach (PieSliceLayout l, layout) { - if (l.m_data == s) - found = true; - } - - if (!found) - destroySlice(s); - } } void PieChartItem::setLayout(PieSliceLayout &layout) { // find slice PieSlice *slice = m_slices.value(layout.m_data); - if (!slice) { - slice = new PieSlice(this); - m_slices.insert(layout.m_data, slice); - connect(layout.m_data, SIGNAL(changed()), this, SLOT(handleSliceChanged())); - connect(slice, SIGNAL(clicked()), layout.m_data, SIGNAL(clicked())); - connect(slice, SIGNAL(hoverEnter()), layout.m_data, SIGNAL(hoverEnter())); - connect(slice, SIGNAL(hoverLeave()), layout.m_data, SIGNAL(hoverLeave())); - } + Q_ASSERT(slice); slice->setLayout(layout); if (m_series->m_slices.contains(layout.m_data)) // Slice has been deleted if not found. Animations ongoing... slice->updateData(layout.m_data); diff --git a/src/piechart/piechartitem_p.h b/src/piechart/piechartitem_p.h index 6ea55c7..7a3b4db 100644 --- a/src/piechart/piechartitem_p.h +++ b/src/piechart/piechartitem_p.h @@ -23,12 +23,17 @@ public: // from QGraphicsItem void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *); public Q_SLOTS: - void handleSeriesChanged(); + void initialize(); + void handleSlicesAdded(QList slices); + void handleSlicesRemoved(QList slices); + void handlePieLayoutChanged(); void handleSliceChanged(); void handleDomainChanged(qreal, qreal, qreal, qreal); void handleGeometryChanged(const QRectF& rect); public: + void calculatePieLayout(); + PieSliceLayout calculateSliceLayout(QPieSlice *slice); QVector calculateLayout(); void applyLayout(QVector &layout); void updateLayout(PieSliceLayout &layout); diff --git a/src/piechart/pieslice.cpp b/src/piechart/pieslice.cpp index 20f576c..dae1f67 100644 --- a/src/piechart/pieslice.cpp +++ b/src/piechart/pieslice.cpp @@ -39,7 +39,7 @@ PieSlice::~PieSlice() QRectF PieSlice::boundingRect() const { - return m_slicePath.boundingRect().united(m_labelTextRect); + return m_boundingRect; } QPainterPath PieSlice::shape() const @@ -112,7 +112,8 @@ void PieSlice::updateGeometry() // update text position m_labelTextRect.moveBottomLeft(labelTextStart); - //qDebug() << "PieSlice::updateGeometry" << m_labelText << boundingRect() << m_startAngle << m_startAngle + m_angleSpan; + // update bounding rect + m_boundingRect = m_slicePath.boundingRect().united(m_labelArmPath.boundingRect()).united(m_labelTextRect); } void PieSlice::updateData(const QPieSlice* sliceData) diff --git a/src/piechart/pieslice_p.h b/src/piechart/pieslice_p.h index ec50820..bd08fdb 100644 --- a/src/piechart/pieslice_p.h +++ b/src/piechart/pieslice_p.h @@ -58,6 +58,7 @@ public: private: PieSliceLayout m_layout; + QRectF m_boundingRect; QPainterPath m_slicePath; bool m_isExploded; diff --git a/src/piechart/qpieseries.cpp b/src/piechart/qpieseries.cpp index 9fe5bf9..6f69802 100644 --- a/src/piechart/qpieseries.cpp +++ b/src/piechart/qpieseries.cpp @@ -77,7 +77,7 @@ void QPieSeries::add(QList slices) connect(s, SIGNAL(hoverLeave()), this, SLOT(sliceHoverLeave())); } - emit changed(); + emit added(slices); } /*! @@ -124,7 +124,7 @@ void QPieSeries::insert(int i, QPieSlice* slice) connect(slice, SIGNAL(hoverEnter()), this, SLOT(sliceHoverEnter())); connect(slice, SIGNAL(hoverLeave()), this, SLOT(sliceHoverLeave())); - emit changed(); + emit added(QList() << slice); } /*! @@ -138,10 +138,11 @@ void QPieSeries::remove(QPieSlice* slice) Q_ASSERT(0); // TODO: how should this be reported? return; } - emit changed(); updateDerivativeData(); + emit removed(QList() << slice); + delete slice; slice = NULL; } @@ -154,14 +155,15 @@ void QPieSeries::clear() if (m_slices.count() == 0) return; + QList slices = m_slices; foreach (QPieSlice* s, m_slices) { m_slices.removeOne(s); delete s; } - emit changed(); - updateDerivativeData(); + + emit removed(slices); } /*! @@ -173,6 +175,14 @@ int QPieSeries::count() const } /*! + Returns true is the series is empty. +*/ +bool QPieSeries::isEmpty() const +{ + return m_slices.isEmpty(); +} + +/*! Returns a list of slices that belong to this series. */ QList QPieSeries::slices() const @@ -203,7 +213,7 @@ void QPieSeries::setPiePosition(qreal relativeHorizontalPosition, qreal relative if (m_pieRelativeHorPos != relativeHorizontalPosition || m_pieRelativeVerPos != relativeVerticalPosition) { m_pieRelativeHorPos = relativeHorizontalPosition; m_pieRelativeVerPos = relativeVerticalPosition; - emit changed(); + emit piePositionChanged(); } } @@ -257,7 +267,7 @@ void QPieSeries::setPieSize(qreal relativeSize) if (m_pieRelativeSize != relativeSize) { m_pieRelativeSize = relativeSize; - emit changed(); + emit pieSizeChanged(); } } @@ -427,13 +437,9 @@ void QPieSeries::updateDerivativeData() foreach (QPieSlice* s, m_slices) m_total += s->value(); - // TODO: emit totalChanged? - - // we must have some values - if (m_total == 0) { - qDebug() << "QPieSeries::updateDerivativeData() total == 0"; - Q_ASSERT(m_total > 0); // TODO: is this the correct way to handle this? - } + // nothing to show.. + if (m_total == 0) + return; // update slice attributes qreal sliceAngle = m_pieStartAngle; @@ -465,6 +471,7 @@ void QPieSeries::updateDerivativeData() changed << s; } + // emit signals foreach (QPieSlice* s, changed) emit s->changed(); } diff --git a/src/piechart/qpieseries.h b/src/piechart/qpieseries.h index 2b3a976..8d949f1 100644 --- a/src/piechart/qpieseries.h +++ b/src/piechart/qpieseries.h @@ -33,6 +33,7 @@ public: // calculated data int count() const; + bool isEmpty() const; qreal total() const; // pie customization @@ -70,7 +71,12 @@ Q_SIGNALS: void clicked(QPieSlice* slice); void hoverEnter(QPieSlice* slice); void hoverLeave(QPieSlice* slice); - void changed(); // TODO: hide this in PIMPL + + // TODO: hide these in PIMPL + void added(QList slices); + void removed(QList slices); + void piePositionChanged(); + void pieSizeChanged(); private Q_SLOTS: // TODO: should be private and not visible in the interface at all void sliceChanged();