diff --git a/example/piechart/main.cpp b/example/piechart/main.cpp index d60cb4d..74d2315 100644 --- a/example/piechart/main.cpp +++ b/example/piechart/main.cpp @@ -26,9 +26,6 @@ int main(int argc, char *argv[]) series->enableClickExplodes(true); series->enableHoverHighlight(true); - foreach (QPieSlice*s, series->slices()) - qDebug() << s->angle() << s->span() << s->percentage(); - QChartView* chartView = new QChartView(&window); chartView->setRenderHint(QPainter::Antialiasing); chartView->addSeries(series); diff --git a/src/piechart/piepresenter.cpp b/src/piechart/piepresenter.cpp index 49e9408..9e5f4fd 100644 --- a/src/piechart/piepresenter.cpp +++ b/src/piechart/piepresenter.cpp @@ -2,8 +2,12 @@ #include "piepresenter.h" #include "pieslice.h" #include "qpieslice.h" +#include "pieslicelabel.h" +#include "qpieseries.h" +#include #include -#include +#include + QTCOMMERCIALCHART_BEGIN_NAMESPACE @@ -15,6 +19,12 @@ PiePresenter::PiePresenter(QGraphicsItem *parent, QPieSeries *series) connect(series, SIGNAL(changed(const QPieSeries::ChangeSet&)), this, SLOT(handleSeriesChanged(const QPieSeries::ChangeSet&))); connect(series, SIGNAL(sizeFactorChanged()), this, SLOT(updateGeometry())); connect(series, SIGNAL(positionChanged()), this, SLOT(updateGeometry())); + + if (m_series->count()) { + QPieSeries::ChangeSet changeSet; + changeSet.appendAdded(m_series->m_slices); + handleSeriesChanged(changeSet); + } } PiePresenter::~PiePresenter() @@ -22,7 +32,7 @@ PiePresenter::~PiePresenter() // slices deleted automatically through QGraphicsItem } -void PiePresenter::paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *) +void PiePresenter::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) { // TODO: paint shadows for all components // - get paths from items & merge & offset and draw with shadow color? @@ -35,19 +45,17 @@ void PiePresenter::handleSeriesChanged(const QPieSeries::ChangeSet& changeSet) //qDebug() << " changed: " << changeSet.changed(); //qDebug() << " removed: " << changeSet.removed(); - // ignore changeset when there are no visual slices - // changeset might not be valid about the added slices - if (m_slices.count() == 0) { - foreach (QPieSlice* s, m_series->m_slices) - addSlice(s); - return; - } + foreach (QPieSlice* s, changeSet.added()) + addSlice(s); + + foreach (QPieSlice* s, changeSet.changed()) + updateSlice(s); foreach (QPieSlice* s, changeSet.removed()) deleteSlice(s); - foreach (QPieSlice* s, changeSet.added()) - addSlice(s); + // every change possibly changes the actual pie size + updateGeometry(); } void PiePresenter::handleDomainChanged(const Domain& domain) @@ -58,15 +66,17 @@ void PiePresenter::handleDomainChanged(const Domain& domain) void PiePresenter::handleGeometryChanged(const QRectF& rect) { m_rect = rect; + prepareGeometryChange(); updateGeometry(); } void PiePresenter::updateGeometry() { - prepareGeometryChange(); + if (!m_rect.isValid() || m_rect.isEmpty()) + return; + // calculate maximum rectangle for pie QRectF pieRect = m_rect; - if (pieRect.width() < pieRect.height()) { pieRect.setWidth(pieRect.width() * m_series->sizeFactor()); pieRect.setHeight(pieRect.width()); @@ -77,6 +87,7 @@ void PiePresenter::updateGeometry() pieRect.moveCenter(m_rect.center()); } + // position the pie rectangle switch (m_series->position()) { case QPieSeries::PiePositionTopLeft: { pieRect.setHeight(pieRect.height() / 2); @@ -106,12 +117,43 @@ void PiePresenter::updateGeometry() break; } + // calculate how much space we need around the pie rectangle (labels & exploding) + qreal delta = 0; + qreal pieRadius = pieRect.height() / 2; + foreach (QPieSlice* s, m_series->m_slices) { + + // calculate the farthest point in the slice from the pie center + qreal centerAngle = s->angle() + (s->angleSpan() / 2); + qreal len = pieRadius + s->labelArmLength() + s->explodeDistance(); + QPointF dp(qSin(centerAngle*(PI/180)) * len, -qCos(centerAngle*(PI/180)) * len); + QPointF p = pieRect.center() + dp; + + // TODO: consider the label text + + // calculate how much the radius must get smaller to fit that point in the base rectangle + qreal dt = m_rect.top() - p.y(); + if (dt > delta) delta = dt; + qreal dl = m_rect.left() - p.x(); + if (dl > delta) delta = dl; + qreal dr = p.x() - m_rect.right(); + if (dr > delta) delta = dr; + qreal db = p.y() - m_rect.bottom(); + if (db > delta) delta = db; + + //if (!m_rect.contains(p)) qDebug() << s->label() << dt << dl << dr << db << "delta" << delta; + } + + // shrink the pie rectangle so that everything outside it fits the base rectangle + pieRect.adjust(delta, delta, -delta, -delta); + + // update slices if (m_pieRect != pieRect) { m_pieRect = pieRect; - //qDebug() << "PiePresenter::updateGeometry()" << m_pieRect; + //qDebug() << "PiePresenter::updateGeometry()" << m_rect << m_pieRect; foreach (PieSlice* s, m_slices.values()) { s->setPieRect(m_pieRect); s->updateGeometry(); + s->update(); } } } @@ -121,8 +163,7 @@ void PiePresenter::addSlice(QPieSlice* sliceData) //qDebug() << "PiePresenter::addSlice()" << sliceData; if (m_slices.keys().contains(sliceData)) { - //qWarning() << "PiePresenter::addSlice(): slice already exists!" << sliceData; - Q_ASSERT(0); + Q_ASSERT(0); // TODO: how to handle this nicely? return; } @@ -135,22 +176,33 @@ void PiePresenter::addSlice(QPieSlice* sliceData) m_slices.insert(sliceData, slice); // connect signals - connect(sliceData, SIGNAL(changed()), slice, SLOT(handleSliceDataChanged())); connect(slice, SIGNAL(clicked()), sliceData, SIGNAL(clicked())); connect(slice, SIGNAL(hoverEnter()), sliceData, SIGNAL(hoverEnter())); connect(slice, SIGNAL(hoverLeave()), sliceData, SIGNAL(hoverLeave())); } +void PiePresenter::updateSlice(QPieSlice* sliceData) +{ + //qDebug() << "PiePresenter::updateSlice()" << sliceData; + + if (!m_slices.contains(sliceData)) { + Q_ASSERT(0); // TODO: how to handle this nicely? + return; + } + + m_slices[sliceData]->updateData(sliceData); +} + void PiePresenter::deleteSlice(QPieSlice* sliceData) { //qDebug() << "PiePresenter::deleteSlice()" << sliceData; - if (m_slices.contains(sliceData)) - delete m_slices.take(sliceData); - else { - // nothing to remove - Q_ASSERT(0); // TODO: remove before release + if (!m_slices.contains(sliceData)) { + Q_ASSERT(0); // TODO: how to handle this nicely? + return; } + + delete m_slices.take(sliceData); } #include "moc_piepresenter.cpp" diff --git a/src/piechart/piepresenter.h b/src/piechart/piepresenter.h index a3348f9..9004a15 100644 --- a/src/piechart/piepresenter.h +++ b/src/piechart/piepresenter.h @@ -9,6 +9,8 @@ class QGraphicsItem; QTCOMMERCIALCHART_BEGIN_NAMESPACE class PieSlice; +#define PI 3.14159265 // TODO: is this defined in some header? + class PiePresenter : public QObject, public ChartItem { Q_OBJECT @@ -33,6 +35,7 @@ public Q_SLOTS: private: void addSlice(QPieSlice* sliceData); + void updateSlice(QPieSlice* sliceData); void deleteSlice(QPieSlice* sliceData); private: diff --git a/src/piechart/pieslice.cpp b/src/piechart/pieslice.cpp index bb241c3..9893cfc 100644 --- a/src/piechart/pieslice.cpp +++ b/src/piechart/pieslice.cpp @@ -11,9 +11,6 @@ QTCOMMERCIALCHART_BEGIN_NAMESPACE -#define PI 3.14159265 -#define EXPLODE_OFFSET 20 - QPointF offset(qreal angle, qreal length) { qreal dx = qSin(angle*(PI/180)) * length; @@ -25,8 +22,9 @@ PieSlice::PieSlice(QGraphicsItem* parent) :QGraphicsObject(parent), m_slicelabel(new PieSliceLabel(this)), m_angle(0), - m_span(0), - m_isExploded(false) + m_angleSpan(0), + m_isExploded(false), + m_explodeDistance(0) { setAcceptHoverEvents(true); setAcceptedMouseButtons(Qt::LeftButton); @@ -76,24 +74,27 @@ void PieSlice::setPieRect(QRectF rect) void PieSlice::updateGeometry() { + if (!m_pieRect.isValid() || m_pieRect.isEmpty()) + return; + prepareGeometryChange(); // calculate center angle - qreal centerAngle = m_angle + (m_span/2); + qreal centerAngle = m_angle + (m_angleSpan/2); // adjust rect for exploding QRectF rect = m_pieRect; - rect.adjust(EXPLODE_OFFSET, EXPLODE_OFFSET, -EXPLODE_OFFSET ,-EXPLODE_OFFSET); if (m_isExploded) { - QPointF d = offset((centerAngle), EXPLODE_OFFSET); - rect.translate(d.x(), d.y()); + qreal dx = qSin(centerAngle*(PI/180)) * m_explodeDistance; + qreal dy = -qCos(centerAngle*(PI/180)) * m_explodeDistance; + rect.translate(dx, dy); } // update slice path // TODO: draw the shape so that it might have a hole in the center QPainterPath path; path.moveTo(rect.center()); - path.arcTo(rect, -m_angle + 90, -m_span); + path.arcTo(rect, -m_angle + 90, -m_angleSpan); path.closeSubpath(); m_path = path; @@ -103,24 +104,19 @@ void PieSlice::updateGeometry() m_slicelabel->setArmStartPoint(edgeCenter); m_slicelabel->setArmAngle(centerAngle); m_slicelabel->updateGeometry(); + m_slicelabel->update(); //qDebug() << "PieSlice::updateGeometry" << m_slicelabel->text() << boundingRect() << m_angle << m_span; } -void PieSlice::handleSliceDataChanged() -{ - QPieSlice *slice = qobject_cast(sender()); - Q_ASSERT(slice); - updateData(slice); -} - void PieSlice::updateData(const QPieSlice* sliceData) { // TODO: compare what has changes to avoid unneccesary geometry updates m_angle = sliceData->angle(); - m_span = sliceData->span(); + m_angleSpan = sliceData->angleSpan(); m_isExploded = sliceData->isExploded(); + m_explodeDistance = sliceData->explodeDistance(); // TODO: expose to public API m_pen = sliceData->pen(); m_brush = sliceData->brush(); @@ -128,11 +124,10 @@ void PieSlice::updateData(const QPieSlice* sliceData) m_slicelabel->setText(sliceData->label()); m_slicelabel->setPen(sliceData->labelPen()); m_slicelabel->setFont(sliceData->labelFont()); - m_slicelabel->setArmLength(sliceData->labelArmLenght()); + m_slicelabel->setArmLength(sliceData->labelArmLength()); updateGeometry(); update(); - m_slicelabel->update(); } #include "moc_pieslice.cpp" diff --git a/src/piechart/pieslice.h b/src/piechart/pieslice.h index 8a67e72..aca1f3f 100644 --- a/src/piechart/pieslice.h +++ b/src/piechart/pieslice.h @@ -36,11 +36,13 @@ Q_SIGNALS: void hoverLeave(); public Q_SLOTS: - void handleSliceDataChanged(); void setPieRect(QRectF rect); void updateGeometry(); void updateData(const QPieSlice *sliceData); +public: + PieSliceLabel* label() { return m_slicelabel; } + private: PieSliceLabel* m_slicelabel; @@ -48,8 +50,10 @@ private: QPainterPath m_path; qreal m_angle; - qreal m_span; + qreal m_angleSpan; + bool m_isExploded; + qreal m_explodeDistance; QPen m_pen; QBrush m_brush; diff --git a/src/piechart/qpieseries.cpp b/src/piechart/qpieseries.cpp index 0a94f51..690556c 100644 --- a/src/piechart/qpieseries.cpp +++ b/src/piechart/qpieseries.cpp @@ -2,6 +2,7 @@ #include "qpieslice.h" #include "piepresenter.h" #include "pieslice.h" +#include #include QTCOMMERCIALCHART_BEGIN_NAMESPACE @@ -12,6 +13,12 @@ void QPieSeries::ChangeSet::appendAdded(QPieSlice* slice) m_added << slice; } +void QPieSeries::ChangeSet::appendAdded(QList slices) +{ + foreach (QPieSlice* s, slices) + appendAdded(s); +} + void QPieSeries::ChangeSet::appendChanged(QPieSlice* slice) { if (!m_changed.contains(slice)) @@ -114,7 +121,7 @@ QPieSlice* QPieSeries::add(qreal value, QString name) void QPieSeries::remove(QPieSlice* slice) { if (!m_slices.removeOne(slice)) { - Q_ASSERT(0); // TODO: remove before release + Q_ASSERT(0); // TODO: how should this be reported? return; } @@ -263,7 +270,7 @@ void QPieSeries::updateDerivativeData() m_total += s->value(); // we must have some values - Q_ASSERT(m_total > 0); // TODO + Q_ASSERT(m_total > 0); // TODO: is this the correct way to handle this? // update slice attributes qreal sliceAngle = m_pieStartAngle; @@ -278,8 +285,8 @@ void QPieSeries::updateDerivativeData() } qreal sliceSpan = m_pieSpan * percentage; - if (s->m_span != sliceSpan) { - s->m_span = sliceSpan; + if (s->m_angleSpan != sliceSpan) { + s->m_angleSpan = sliceSpan; changed = true; } diff --git a/src/piechart/qpieseries.h b/src/piechart/qpieseries.h index 65e3e72..977af05 100644 --- a/src/piechart/qpieseries.h +++ b/src/piechart/qpieseries.h @@ -32,6 +32,7 @@ public: { public: void appendAdded(QPieSlice* slice); + void appendAdded(QList slices); void appendChanged(QPieSlice* slice); void appendRemoved(QPieSlice* slice); @@ -67,13 +68,6 @@ public: QList slices() const { return m_slices; } - // TODO: find slices? - // QList findByValue(qreal value); - // ... - - // TODO: sorting slices? - // void sort(QPieSeries::SortByValue) - void setSizeFactor(qreal sizeFactor); qreal sizeFactor() const { return m_sizeFactor; } @@ -86,6 +80,17 @@ public: void enableClickExplodes(bool enable); void enableHoverHighlight(bool enable); + // TODO: find slices? + // QList findByValue(qreal value); + // ... + + // TODO: sorting slices? + // void sort(QPieSeries::SortByValue|label|??) + + // TODO: general graphics customization + // setDrawStyle(2d|3d) + // setDropShadows(bool) + Q_SIGNALS: void changed(const QPieSeries::ChangeSet& changeSet); void clicked(QPieSlice* slice); @@ -94,12 +99,11 @@ Q_SIGNALS: void sizeFactorChanged(); void positionChanged(); -private Q_SLOTS: // should be private and not in the interface +private Q_SLOTS: // TODO: should be private and not visible in the interface at all void sliceChanged(); void sliceClicked(); void sliceHoverEnter(); void sliceHoverLeave(); - void toggleExploded(QPieSlice* slice); void highlightOn(QPieSlice* slice); void highlightOff(QPieSlice* slice); diff --git a/src/piechart/qpieslice.cpp b/src/piechart/qpieslice.cpp index 595575a..55c14b4 100644 --- a/src/piechart/qpieslice.cpp +++ b/src/piechart/qpieslice.cpp @@ -2,18 +2,20 @@ QTCOMMERCIALCHART_BEGIN_NAMESPACE -#define DEFAULT_PEN_COLOR Qt::black -#define DEFAULT_BRUSH_COLOR Qt::white -#define DEFAULT_LABEL_ARM_LENGTH 50 +#define DEFAULT_PEN_COLOR Qt::black +#define DEFAULT_BRUSH_COLOR Qt::white +#define DEFAULT_LABEL_ARM_LENGTH 50 +#define DEFAULT_EXPOLODE_DISTANCE 20 QPieSlice::QPieSlice(QObject *parent) :QObject(parent), m_value(0), m_isLabelVisible(true), m_isExploded(false), + m_explodeDistance(DEFAULT_EXPOLODE_DISTANCE), m_percentage(0), m_angle(0), - m_span(0), + m_angleSpan(0), m_pen(DEFAULT_PEN_COLOR), m_brush(DEFAULT_BRUSH_COLOR), m_labelPen(DEFAULT_PEN_COLOR), @@ -28,9 +30,10 @@ QPieSlice::QPieSlice(qreal value, QString label, bool labelVisible, QObject *par m_label(label), m_isLabelVisible(labelVisible), m_isExploded(false), + m_explodeDistance(DEFAULT_EXPOLODE_DISTANCE), m_percentage(0), m_angle(0), - m_span(0), + m_angleSpan(0), m_pen(DEFAULT_PEN_COLOR), m_brush(DEFAULT_BRUSH_COLOR), m_labelPen(DEFAULT_PEN_COLOR), @@ -64,6 +67,11 @@ bool QPieSlice::isExploded() const return m_isExploded; } +qreal QPieSlice::explodeDistance() const +{ + return m_explodeDistance; +} + qreal QPieSlice::percentage() const { return m_percentage; @@ -74,9 +82,9 @@ qreal QPieSlice::angle() const return m_angle; } -qreal QPieSlice::span() const +qreal QPieSlice::angleSpan() const { - return m_span; + return m_angleSpan; } QPen QPieSlice::pen() const @@ -99,7 +107,7 @@ QFont QPieSlice::labelFont() const return m_labelFont; } -qreal QPieSlice::labelArmLenght() const +qreal QPieSlice::labelArmLength() const { return m_labelArmLength; } @@ -136,6 +144,14 @@ void QPieSlice::setExploded(bool exploded) } } +void QPieSlice::setExplodeDistance(qreal distance) +{ + if (m_explodeDistance != distance) { + m_explodeDistance = distance; + emit changed(); + } +} + void QPieSlice::setPen(QPen pen) { if (m_pen != pen) { diff --git a/src/piechart/qpieslice.h b/src/piechart/qpieslice.h index 1d42328..c84225b 100644 --- a/src/piechart/qpieslice.h +++ b/src/piechart/qpieslice.h @@ -25,18 +25,19 @@ public: QString label() const; bool isLabelVisible() const; bool isExploded() const; + qreal explodeDistance() const; // generated data qreal percentage() const; qreal angle() const; - qreal span() const; + qreal angleSpan() const; // customization QPen pen() const; QBrush brush() const; QPen labelPen() const; QFont labelFont() const; - qreal labelArmLenght() const; + qreal labelArmLength() const; Q_SIGNALS: void clicked(); @@ -51,6 +52,7 @@ public Q_SLOTS: void setLabelVisible(bool visible); void setValue(qreal value); void setExploded(bool exploded); + void setExplodeDistance(qreal distance); // customization void setPen(QPen pen); @@ -59,21 +61,27 @@ public Q_SLOTS: void setLabelPen(QPen pen); void setLabelArmLength(qreal len); + // TODO: label position in general + // setLabelFlags(inside|outside|labelArmOn|labelArmOff|???) + // setLabelOrientation(horizontal|vertical|same as slice center angle|???) + private: // TODO: use private class friend class QPieSeries; + friend class PiePresenter; // data qreal m_value; QString m_label; bool m_isLabelVisible; bool m_isExploded; + qreal m_explodeDistance; // generated data qreal m_percentage; qreal m_angle; - qreal m_span; + qreal m_angleSpan; // customization QPen m_pen;