Refactoring pie series and animations.

This commit is contained in:
Jani Honkonen 2012-03-16 15:29:37 +02:00
parent 51f99c2be3
commit f4b980d7de
12 changed files with 192 additions and 141 deletions

View File

@ -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("<click a slice>");
@ -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)

View File

@ -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");

View File

@ -184,11 +184,25 @@ void ChartAnimator::updateLayout(XYChartItem* item, QVector<QPointF>& newPoints)
QTimer::singleShot(0,animation,SLOT(start()));
}
void ChartAnimator::applyLayout(PieChartItem* item, QVector<PieSliceLayout> &layout)
void ChartAnimator::addAnimation(PieChartItem* item, QPieSlice *slice, PieSliceLayout &layout)
{
PieAnimation* animation = static_cast<PieAnimation*>(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<PieAnimation*>(m_animations.value(item));
Q_ASSERT(animation);
animation->removeSlice(slice);
}
void ChartAnimator::updateLayout(PieChartItem* item, QVector<PieSliceLayout> &layout)
{
PieAnimation* animation = static_cast<PieAnimation*>(m_animations.value(item));
Q_ASSERT(animation);
animation->updateValues(layout);
}
void ChartAnimator::updateLayout(PieChartItem* item, PieSliceLayout &layout)

View File

@ -32,7 +32,9 @@ public:
void updateLayout(XYChartItem* item, QVector<QPointF>& layout);
void applyLayout(AxisItem* item, QVector<qreal>& layout);
void applyLayout(PieChartItem* item, QVector<PieSliceLayout> &layout);
void addAnimation(PieChartItem* item, QPieSlice *slice, PieSliceLayout &layout);
void removeAnimation(PieChartItem* item, QPieSlice *slice);
void updateLayout(PieChartItem* item, QVector<PieSliceLayout> &layout);
void updateLayout(PieChartItem* item, PieSliceLayout &layout);
void setState(State state,const QPointF& point = QPointF());

View File

@ -16,53 +16,10 @@ PieAnimation::~PieAnimation()
{
}
void PieAnimation::setValues(QVector<PieSliceLayout>& newValues)
void PieAnimation::updateValues(QVector<PieSliceLayout>& 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()));
}

View File

@ -16,8 +16,10 @@ class PieAnimation : public ChartAnimation
public:
PieAnimation(PieChartItem *item);
~PieAnimation();
void setValues(QVector<PieSliceLayout>& newValues);
void updateValues(QVector<PieSliceLayout>& newValues);
void updateValue(PieSliceLayout& newValue);
void addSlice(QPieSlice *slice, PieSliceLayout endLayout);
void removeSlice(QPieSlice *slice);
public: // from QVariantAnimation
void updateCurrentValue(const QVariant &value);

View File

@ -6,7 +6,7 @@
#include "chartanimator_p.h"
#include <QDebug>
#include <QPainter>
#include <QTimer>
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<QPieSlice*>)), this, SLOT(handleSlicesAdded(QList<QPieSlice*>)));
connect(series, SIGNAL(removed(QList<QPieSlice*>)), this, SLOT(handleSlicesRemoved(QList<QPieSlice*>)));
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<QPieSlice*> 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<QPieSlice*> slices)
{
foreach (QPieSlice *s, slices) {
if (m_animator)
m_animator->removeAnimation(this, s);
else
destroySlice(s);
}
}
void PieChartItem::handlePieLayoutChanged()
{
QVector<PieSliceLayout> layout = calculateLayout();
applyLayout(layout);
@ -46,15 +85,8 @@ void PieChartItem::handleSliceChanged()
{
QPieSlice* slice = qobject_cast<QPieSlice *>(sender());
Q_ASSERT(m_slices.contains(slice));
//qDebug() << "PieChartItem::handleSliceChanged" << slice->label();
// TODO: Optimize. No need to calculate everything.
QVector<PieSliceLayout> 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<PieSliceLayout> sliceLayout = calculateLayout();
applyLayout(sliceLayout);
update();
handlePieLayoutChanged();
}
void PieChartItem::calculatePieLayout()
{
// find pie center coordinates
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
m_pieRadius = m_rect.height() / 2;
if (m_rect.width() < m_rect.height())
m_pieRadius = m_rect.width() / 2;
// apply size factor
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<PieSliceLayout> PieChartItem::calculateLayout()
{
// 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()));
// find maximum radius for pie
qreal radius = m_rect.height() / 2;
if (m_rect.width() < m_rect.height())
radius = m_rect.width() / 2;
// apply size factor
radius *= m_series->pieSize();
calculatePieLayout();
QVector<PieSliceLayout> 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<PieSliceLayout> &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<PieSliceLayout> &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);

View File

@ -23,12 +23,17 @@ public: // from QGraphicsItem
void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *);
public Q_SLOTS:
void handleSeriesChanged();
void initialize();
void handleSlicesAdded(QList<QPieSlice*> slices);
void handleSlicesRemoved(QList<QPieSlice*> slices);
void handlePieLayoutChanged();
void handleSliceChanged();
void handleDomainChanged(qreal, qreal, qreal, qreal);
void handleGeometryChanged(const QRectF& rect);
public:
void calculatePieLayout();
PieSliceLayout calculateSliceLayout(QPieSlice *slice);
QVector<PieSliceLayout> calculateLayout();
void applyLayout(QVector<PieSliceLayout> &layout);
void updateLayout(PieSliceLayout &layout);

View File

@ -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)

View File

@ -58,6 +58,7 @@ public:
private:
PieSliceLayout m_layout;
QRectF m_boundingRect;
QPainterPath m_slicePath;
bool m_isExploded;

View File

@ -77,7 +77,7 @@ void QPieSeries::add(QList<QPieSlice*> 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<QPieSlice*>() << 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<QPieSlice*>() << slice);
delete slice;
slice = NULL;
}
@ -154,14 +155,15 @@ void QPieSeries::clear()
if (m_slices.count() == 0)
return;
QList<QPieSlice*> slices = m_slices;
foreach (QPieSlice* s, m_slices) {
m_slices.removeOne(s);
delete s;
}
emit changed();
updateDerivativeData();
emit removed(slices);
}
/*!
@ -172,6 +174,14 @@ int QPieSeries::count() const
return m_slices.count();
}
/*!
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.
*/
@ -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();
}

View File

@ -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<QPieSlice*> slices);
void removed(QList<QPieSlice*> slices);
void piePositionChanged();
void pieSizeChanged();
private Q_SLOTS: // TODO: should be private and not visible in the interface at all
void sliceChanged();