From 0d781305aa75c389aee22eb4b1e18e94576f88a9 2012-02-14 14:46:41 From: Jani Honkonen Date: 2012-02-14 14:46:41 Subject: [PATCH] Refactoring piechart API (and internals) --- diff --git a/example/piechart/main.cpp b/example/piechart/main.cpp index 4e52351..203b6b5 100644 --- a/example/piechart/main.cpp +++ b/example/piechart/main.cpp @@ -17,11 +17,11 @@ int main(int argc, char *argv[]) Q_ASSERT(series); // Add test data to the series - series->add(QPieSlice(1, "test1", Qt::red, true)); - series->add(QPieSlice(2, "test2", Qt::green)); - series->add(QPieSlice(3, "test3", Qt::blue)); - series->add(QPieSlice(4, "test4", Qt::darkRed, true)); - series->add(QPieSlice(5, "test5", Qt::darkGreen)); + series->add(QPieSlice(1, "test1", true, true, QPen(Qt::red, 2), QBrush(Qt::red))); + series->add(QPieSlice(2, "test2")); + series->add(QPieSlice(3, "test3")); + series->add(QPieSlice(4, "test4")); + series->add(QPieSlice(5, "test5")); // Use the chart widget as the central widget QMainWindow w; diff --git a/src/charttheme.cpp b/src/charttheme.cpp index 6e2df46..b2928f9 100644 --- a/src/charttheme.cpp +++ b/src/charttheme.cpp @@ -198,14 +198,12 @@ void ChartTheme::decorate(PiePresenter* item, QPieSeries* series, int /*count*/) } // finally update colors - QList slices; - for (int i=0; icount(); i++) { - QPieSlice slice = series->slice(i); - slice.m_color = colors.at(i); - slices << slice; + foreach (QPieSliceId id, series->ids()) { + QPieSlice s = series->slice(id); + s.setPen(QPen(Qt::black)); // TODO: get from theme + s.setBrush(colors.takeFirst()); + series->update(s); } - - series->set(slices); } diff --git a/src/piechart/piechart.pri b/src/piechart/piechart.pri index 26dbcd0..133f19c 100644 --- a/src/piechart/piechart.pri +++ b/src/piechart/piechart.pri @@ -4,11 +4,13 @@ DEPENDPATH += $$PWD SOURCES += \ $$PWD/qpieseries.cpp \ $$PWD/pieslice.cpp \ - $$PWD/piepresenter.cpp + $$PWD/piepresenter.cpp \ + $$PWD/pieslicelabel.cpp PRIVATE_HEADERS += \ $$PWD/piepresenter.h \ - $$PWD/pieslice.h + $$PWD/pieslice.h \ + $$PWD/pieslicelabel.h PUBLIC_HEADERS += \ $$PWD/qpieseries.h diff --git a/src/piechart/piepresenter.cpp b/src/piechart/piepresenter.cpp index 6765892..87f0701 100644 --- a/src/piechart/piepresenter.cpp +++ b/src/piechart/piepresenter.cpp @@ -6,57 +6,50 @@ QTCOMMERCIALCHART_BEGIN_NAMESPACE -PiePresenter::PiePresenter(QGraphicsItem *parent, QPieSeries *series) : - ChartItem(parent), - m_pieSeries(series) +PiePresenter::PiePresenter(QGraphicsItem *parent, QPieSeries *series) + :ChartItem(parent), + m_series(series) { - Q_ASSERT(parent); Q_ASSERT(series); - - m_rect = parentItem()->boundingRect(); - setAcceptHoverEvents(true); - qsrand(QTime::currentTime().msec()); // for random color generation - - connect(series, SIGNAL(changed(const PieChangeSet&)), this, SLOT(handleSeriesChanged(const PieChangeSet&))); + 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())); } PiePresenter::~PiePresenter() { - while (m_slices.count()) - delete m_slices.takeLast(); + // slices deleted automatically through QGraphicsItem } -void PiePresenter::handleSeriesChanged(const PieChangeSet& /*changeSet*/) +void PiePresenter::paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *) { - const qreal fullPie = 360; - qreal total = 0; - - // calculate total and set random color if there is no color - for (int i=0; icount(); i++) { - QPieSlice& slice = m_pieSeries->m_slices[i]; - total += slice.m_value; - if (slice.m_color == QColor::Invalid) { - slice.m_color.setRed(qrand() % 255); - slice.m_color.setGreen(qrand() % 255); - slice.m_color.setBlue(qrand() % 255); - } + // TODO: paint shadows for all components + // - get paths from items & merge & offset and draw with shadow color? +} + +void PiePresenter::handleSeriesChanged(const QPieSeries::ChangeSet& changeSet) +{ + qDebug() << "PiePresenter::handleSeriesChanged()"; + qDebug() << " added : " << changeSet.m_added; + qDebug() << " changed: " << changeSet.m_changed; + qDebug() << " removed: " << changeSet.m_removed; + + // ignore changeset when there are no visual slices + // changeset might not be valid about the added slices + if (m_slices.count() == 0) { + foreach (QPieSliceId id, m_series->m_slices.keys()) + addSlice(id); + return; } - // TODO: no need to create new slices in case size changed; we should re-use the existing ones - while (m_slices.count()) - delete m_slices.takeLast(); + foreach (QPieSliceId id, changeSet.m_removed) + deleteSlice(id); - // create slices - qreal angle = 0; - for (int i=0; icount(); i++) { - QPieSlice sliceData = m_pieSeries->slice(i); - qreal span = sliceData.m_value / total * fullPie; - PieSlice *slice = new PieSlice(this, i, angle, span); - m_slices.append(slice); - angle += span; - } + foreach (QPieSliceId id, changeSet.m_changed) + updateSlice(id); + + foreach (QPieSliceId id, changeSet.m_added) + addSlice(id); } void PiePresenter::updateGeometry() @@ -66,16 +59,16 @@ void PiePresenter::updateGeometry() m_pieRect = m_rect; if (m_pieRect.width() < m_pieRect.height()) { - m_pieRect.setWidth(m_pieRect.width() * m_pieSeries->sizeFactor()); + m_pieRect.setWidth(m_pieRect.width() * m_series->sizeFactor()); m_pieRect.setHeight(m_pieRect.width()); m_pieRect.moveCenter(m_rect.center()); } else { - m_pieRect.setHeight(m_pieRect.height() * m_pieSeries->sizeFactor()); + m_pieRect.setHeight(m_pieRect.height() * m_series->sizeFactor()); m_pieRect.setWidth(m_pieRect.height()); m_pieRect.moveCenter(m_rect.center()); } - switch (m_pieSeries->position()) { + switch (m_series->position()) { case QPieSeries::PiePositionTopLeft: { m_pieRect.setHeight(m_pieRect.height() / 2); m_pieRect.setWidth(m_pieRect.height()); @@ -104,10 +97,16 @@ void PiePresenter::updateGeometry() break; } - qDebug() << "presentation rect:" << m_rect; - qDebug() << "pie rect:" << m_pieRect; - foreach (PieSlice *slice, m_slices) - slice->updateGeometry(); + // update slice geometry + const qreal fullPie = 360; + qreal angle = 0; + foreach (QPieSliceId id, m_slices.keys()) { + qreal span = fullPie * m_series->slice(id).percentage(); + m_slices[id]->updateGeometry(m_pieRect, angle, span); + angle += span; + } + + //qDebug() << "PiePresenter::updateGeometry" << m_rect << m_pieRect; } void PiePresenter::handleDomainChanged(const Domain& domain) @@ -119,7 +118,51 @@ void PiePresenter::handleGeometryChanged(const QRectF& rect) { m_rect = rect; updateGeometry(); +} +void PiePresenter::addSlice(QPieSliceId id) +{ + qDebug() << "PiePresenter::addSlice()" << id; + + if (m_slices.contains(id)) { + qWarning() << "PiePresenter::addSlice(): slice already exists!" << id; + updateSlice(id); + return; + } + + // create slice + PieSlice *slice = new PieSlice(id, m_series, this); + m_slices.insert(id, slice); + + updateGeometry(); +} + +void PiePresenter::updateSlice(QPieSliceId id) +{ + qDebug() << "PiePresenter::updateSlice()" << id; + + // TODO: animation + if (m_slices.contains(id)) + m_slices.value(id)->updateData(); + else { + qWarning() << "PiePresenter::updateSlice(): slice does not exist!" << id; + addSlice(id); + } + + updateGeometry(); +} + +void PiePresenter::deleteSlice(QPieSliceId id) +{ + qDebug() << "PiePresenter::deleteSlice()" << id; + + // TODO: animation + if (m_slices.contains(id)) + delete m_slices.take(id); + else + qWarning() << "PiePresenter::deleteSlice(): slice does not exist!" << id; + + updateGeometry(); } #include "moc_piepresenter.cpp" diff --git a/src/piechart/piepresenter.h b/src/piechart/piepresenter.h index f24489f..50e8a5d 100644 --- a/src/piechart/piepresenter.h +++ b/src/piechart/piepresenter.h @@ -3,6 +3,7 @@ #include "chartitem_p.h" #include "qpieseries.h" +#include class QGraphicsItem; QTCOMMERCIALCHART_BEGIN_NAMESPACE @@ -19,21 +20,26 @@ public: public: // from QGraphicsItem QRectF boundingRect() const { return m_rect; } - void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *) {} + void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *); public: QRectF pieRect() const { return m_pieRect; } public Q_SLOTS: - void handleSeriesChanged(const PieChangeSet& changeSet); + void handleSeriesChanged(const QPieSeries::ChangeSet& changeSet); void handleDomainChanged(const Domain& domain); void handleGeometryChanged(const QRectF& rect); void updateGeometry(); private: + void addSlice(QPieSliceId id); + void updateSlice(QPieSliceId id); + void deleteSlice(QPieSliceId id); + +private: friend class PieSlice; - QList m_slices; - QPieSeries *m_pieSeries; + QHash m_slices; + QPieSeries *m_series; QRectF m_rect; QRectF m_pieRect; }; diff --git a/src/piechart/pieslice.cpp b/src/piechart/pieslice.cpp index f43f7d7..9ae8a90 100644 --- a/src/piechart/pieslice.cpp +++ b/src/piechart/pieslice.cpp @@ -1,10 +1,12 @@ #include "pieslice.h" +#include "pieslicelabel.h" #include "piepresenter.h" #include "qpieseries.h" #include #include #include #include +#include QTCOMMERCIALCHART_BEGIN_NAMESPACE @@ -18,25 +20,22 @@ QPointF offset(qreal angle, qreal length) return QPointF(dx, -dy); } -PieSlice::PieSlice(PiePresenter *presenter, int seriesIndex, qreal startAngle, qreal span) - :QGraphicsItem(presenter), - m_label(new QGraphicsTextItem(this)), - m_seriesIndex(seriesIndex), - m_startAngle(startAngle), - m_span(span) +PieSlice::PieSlice(QPieSliceId id, QPieSeries *series, QGraphicsItem* parent) + :QGraphicsObject(parent), + m_id(id), + m_series(series), + m_slicelabel(new PieSliceLabel(this)), + m_isHovering(false) { - Q_ASSERT(presenter); + Q_ASSERT(series); setAcceptHoverEvents(true); setAcceptedMouseButtons(Qt::LeftButton); - updateGeometry(); - - // TODO: use themes - m_pen = QPen(Qt::black); - m_brush = QBrush(sliceData().m_color); + updateData(); } PieSlice::~PieSlice() { + qDebug() << "~PieSlice()" << m_id; } QRectF PieSlice::boundingRect() const @@ -51,72 +50,86 @@ QPainterPath PieSlice::shape() const void PieSlice::paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget* /*widget*/) { - painter->setRenderHint(QPainter::Antialiasing); + // set hover brush + // TODO: what if we are using gradients... + QBrush brush = m_data.brush(); + if (m_isHovering) + brush.setColor(brush.color().lighter()); - // TODO: themes - painter->setPen(m_pen); - painter->setBrush(m_brush); + painter->setRenderHint(QPainter::Antialiasing); + painter->setPen(m_data.pen()); + painter->setBrush(brush); painter->drawPath(m_path); - - // Draw the label - // TODO: do this better - //QTextItem text; - painter->drawText(m_center, sliceData().m_label); } -void PieSlice::hoverEnterEvent(QGraphicsSceneHoverEvent* event) +void PieSlice::hoverEnterEvent(QGraphicsSceneHoverEvent* /*event*/) { - m_brush = QBrush(sliceData().m_color.lighter()); + m_isHovering = true; update(); + // TODO: emit hoverEnter() } void PieSlice::hoverLeaveEvent(QGraphicsSceneHoverEvent* /*event*/) { - m_brush = QBrush(sliceData().m_color); + m_isHovering = false; update(); + // TODO: emit hoverLeave() } void PieSlice::mousePressEvent(QGraphicsSceneMouseEvent* /*event*/) { - QPieSlice data = sliceData(); - data.m_isExploded = !data.m_isExploded; - (static_cast(parentItem()))->m_pieSeries->update(m_seriesIndex, data); + // TODO: emit clicked + // TODO: should we let the user decide if this can be exploded? + m_data.setExploded(!m_data.isExploded()); + m_series->update(m_data); } -void PieSlice::updateGeometry() +void PieSlice::updateGeometry(QRectF rect, qreal startAngle, qreal span) { prepareGeometryChange(); - PiePresenter *presenter = static_cast(parentItem()); - QRectF rect = presenter->pieRect(); - rect.adjust(EXPLODE_OFFSET, EXPLODE_OFFSET, -EXPLODE_OFFSET ,-EXPLODE_OFFSET); + // calculate center angle + qreal centerAngle = startAngle + (span/2); - qreal centerAngle = m_startAngle + (m_span/2); - - if (presenter->m_pieSeries->slice(m_seriesIndex).m_isExploded) { + // adjust rect for exploding + rect.adjust(EXPLODE_OFFSET, EXPLODE_OFFSET, -EXPLODE_OFFSET ,-EXPLODE_OFFSET); + if (m_data.isExploded()) { QPointF d = offset((centerAngle), EXPLODE_OFFSET); rect.translate(d.x(), d.y()); } - qreal angle = (-m_startAngle) + (90); - qreal span = -m_span; - + // 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, angle, span); - - // TODO: draw the shape so that it might have a hole in the center - + path.arcTo(rect, -startAngle + 90, -span); m_path = path; m_rect = path.boundingRect(); + // update label position qreal radius = rect.height() / 2; - m_center = rect.center() + offset(centerAngle, radius / 2); + QPointF edgeCenter = rect.center() + offset(centerAngle, radius + 5); + m_slicelabel->updateGeometry(edgeCenter, centerAngle, 50); + + //qDebug() << "PieSlice::updateGeometry" << m_rect; } -QPieSlice PieSlice::sliceData() +void PieSlice::updateData() { - return (static_cast(parentItem()))->m_pieSeries->slice(m_seriesIndex); + if (!m_series->m_slices.contains(m_id)) + qWarning() << "PieSlice::updateData(): cannot find slice data!" << m_id; + + QPieSlice data = m_series->slice(m_id); + // TODO: find out what has changed and trigger some animation + m_data = data; + + update(); + + m_slicelabel->setLabel(m_data.label()); + m_slicelabel->setVisible(m_data.isLabelVisible()); + m_slicelabel->update(); } +#include "moc_pieslice.cpp" + QTCOMMERCIALCHART_END_NAMESPACE diff --git a/src/piechart/pieslice.h b/src/piechart/pieslice.h index 5795394..f9c6476 100644 --- a/src/piechart/pieslice.h +++ b/src/piechart/pieslice.h @@ -11,11 +11,14 @@ QTCOMMERCIALCHART_BEGIN_NAMESPACE class PiePresenter; +class PieSliceLabel; -class PieSlice : public QGraphicsItem +class PieSlice : public QGraphicsObject { + Q_OBJECT + public: - PieSlice(PiePresenter *piePresenter, int seriesIndex, qreal startAngle, qreal span); + PieSlice(QPieSliceId id, QPieSeries *series, QGraphicsItem* parent = 0); ~PieSlice(); public: // from QGraphicsItem @@ -26,22 +29,21 @@ public: // from QGraphicsItem void hoverLeaveEvent(QGraphicsSceneHoverEvent *event); void mousePressEvent(QGraphicsSceneMouseEvent *event); -public: - void updateGeometry(); +Q_SIGNALS: + void clicked(); -private: - QPieSlice sliceData(); +public: + void updateGeometry(QRectF rect, qreal startAngle, qreal span); + void updateData(); private: - QGraphicsTextItem* m_label; - int m_seriesIndex; - qreal m_startAngle; - qreal m_span; + QPieSliceId m_id; + QPieSeries* m_series; + QPieSlice m_data; + PieSliceLabel* m_slicelabel; QPainterPath m_path; QRectF m_rect; - QPointF m_center; - QPen m_pen; - QBrush m_brush; + bool m_isHovering; }; QTCOMMERCIALCHART_END_NAMESPACE diff --git a/src/piechart/pieslicelabel.cpp b/src/piechart/pieslicelabel.cpp new file mode 100644 index 0000000..8d6b4ba --- /dev/null +++ b/src/piechart/pieslicelabel.cpp @@ -0,0 +1,76 @@ +#include "pieslicelabel.h" +#include +#include + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +#define PI 3.14159265 + +PieSliceLabel::PieSliceLabel(QGraphicsItem* parent) + :QGraphicsItem(parent) +{ + m_pen = QPen(Qt::black); +} + +PieSliceLabel::~PieSliceLabel() +{ + +} + +QRectF PieSliceLabel::boundingRect() const +{ + return m_rect; +} + +QPainterPath PieSliceLabel::shape() const +{ + return m_path; +} + +void PieSliceLabel::paint(QPainter *painter, const QStyleOptionGraphicsItem* /*option*/, QWidget* /*widget*/) +{ + painter->setRenderHint(QPainter::Antialiasing); + painter->setPen(m_pen); + painter->drawPath(m_path); +} + +void PieSliceLabel::updateGeometry(const QPointF& startPoint, qreal armAngle, qreal armLength) +{ + prepareGeometryChange(); + + QPainterPath path; + path.moveTo(startPoint); + + // draw arm + qreal dx = qSin(armAngle*(PI/180)) * armLength; + qreal dy = -qCos(armAngle*(PI/180)) * armLength; + QPointF p1 = startPoint + QPointF(dx, dy); + path.lineTo(p1); + + QPointF p2 = p1; + QPointF pt = p1; + if (armAngle < 180) { + p2 += QPointF(50, 0); + } else { + p2 += QPointF(-50,0); + pt = p2; + } + path.lineTo(p2); + + QFont font; + pt += QPointF(0,-2); + path.addText(pt, font, m_label); + + m_path = path; + m_rect = path.boundingRect(); +} + +void PieSliceLabel::setLabel(QString label) +{ + m_label = label; + // TODO: animation? +} + + + +QTCOMMERCIALCHART_END_NAMESPACE diff --git a/src/piechart/pieslicelabel.h b/src/piechart/pieslicelabel.h new file mode 100644 index 0000000..7b39f6e --- /dev/null +++ b/src/piechart/pieslicelabel.h @@ -0,0 +1,36 @@ +#ifndef PIELABEL_H +#define PIELABEL_H + +#include "qchartglobal.h" +#include +#include + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +class PieSliceLabel : public QGraphicsItem +{ +public: + PieSliceLabel(QGraphicsItem* parent = 0); + ~PieSliceLabel(); + +public: // from QGraphicsItem + QRectF boundingRect() const; + QPainterPath shape() const; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + +public: + void updateGeometry(const QPointF& startPoint, qreal armAngle, qreal armLength); + void setLabel(QString label); + QString label() const {return m_label;} + void setPen(QPen pen); + +private: + QString m_label; + QPainterPath m_path; + QRectF m_rect; + QPen m_pen; +}; + +QTCOMMERCIALCHART_END_NAMESPACE + +#endif // PIELABEL_H diff --git a/src/piechart/qpieseries.cpp b/src/piechart/qpieseries.cpp index 880818e..ac5eadc 100644 --- a/src/piechart/qpieseries.cpp +++ b/src/piechart/qpieseries.cpp @@ -8,7 +8,8 @@ QTCOMMERCIALCHART_BEGIN_NAMESPACE QPieSeries::QPieSeries(QObject *parent) : QChartSeries(parent), m_sizeFactor(1.0), - m_position(PiePositionMaximized) + m_position(PiePositionMaximized), + m_sliceIdSeed(0) { } @@ -19,70 +20,93 @@ QPieSeries::~QPieSeries() bool QPieSeries::setData(QList data) { + // TODO: remove this function QList slices; foreach (int value, data) slices << QPieSlice(value, QString::number(value)); return set(slices); } -bool QPieSeries::set(QList slices) +bool QPieSeries::set(const QList& slices) { if (!slices.count()) return false; - PieChangeSet changeSet; + ChangeSet changeSet; - for (int i=slices.count(); i slices) +bool QPieSeries::add(const QList& slices) { if (!slices.count()) return false; - PieChangeSet changeSet; - for (int i=0; i() << slice); } -QPieSlice QPieSeries::slice(int index) const +bool QPieSeries::update(const QPieSlice& slice) { - if ((index >= 0) && (index < m_slices.count())) - return m_slices.at(index); - return QPieSlice(); + if (!m_slices.contains(slice.id())) + return false; // series does not contain this slice + + m_slices[slice.id()] = slice; + + ChangeSet changeSet; + changeSet.m_changed << slice.id(); + updateDerivativeData(); + emit changed(changeSet); + + return true; } -bool QPieSeries::update(int index, QPieSlice slice) +bool QPieSeries::remove(QPieSliceId id) { - if ((index >= 0) && (index < m_slices.count())) { - m_slices[index] = slice; - PieChangeSet changeSet; - changeSet.m_changed << index; - emit changed(changeSet); - return true; - } - return false; + if (!m_slices.contains(id)) + return false; // series does not contain this slice + + m_slices.remove(id); + + ChangeSet changeSet; + changeSet.m_removed << id; + updateDerivativeData(); + emit changed(changeSet); + + return true; +} + +QPieSlice QPieSeries::slice(QPieSliceId id) const +{ + return m_slices.value(id); } void QPieSeries::setSizeFactor(qreal factor) @@ -104,6 +128,25 @@ void QPieSeries::setPosition(PiePosition position) } } +QPieSliceId QPieSeries::generateSliceId() +{ + // Id is quint64 so it should be enough for us. + // Note that id is not unique between pie series. + return m_sliceIdSeed++; +} + +void QPieSeries::updateDerivativeData() +{ + m_total = 0; + foreach (const QPieSlice& s, m_slices.values()) + m_total += s.value(); + + Q_ASSERT(m_total > 0); // TODO: remove this before release + + foreach (QPieSliceId id, m_slices.keys()) + m_slices[id].m_percentage = m_slices.value(id).value() / m_total; +} + #include "moc_qpieseries.cpp" QTCOMMERCIALCHART_END_NAMESPACE diff --git a/src/piechart/qpieseries.h b/src/piechart/qpieseries.h index f423702..34120ad 100644 --- a/src/piechart/qpieseries.h +++ b/src/piechart/qpieseries.h @@ -5,33 +5,62 @@ #include #include #include +#include +#include class QGraphicsObject; QTCOMMERCIALCHART_BEGIN_NAMESPACE class PiePresenter; class PieSlice; +typedef quint64 QPieSliceId; + class QPieSlice { public: QPieSlice() - :m_value(0), m_label(""), m_color(QColor::Invalid), m_isExploded(false) {} + :m_id(-1), m_value(0), m_isLabelVisible(true), m_isExploded(false), m_percentage(0) {} - QPieSlice(qreal value, QString label = "", QColor color = QColor::Invalid, bool exploded = false) - :m_value(value), m_label(label), m_color(color), m_isExploded(exploded) {} -public: + QPieSlice(qreal value, QString label = QString(), bool labelVisible = true, bool exploded = false, QPen pen = QPen(), QBrush brush = QBrush()) + :m_id(-1), m_value(value), m_label(label), m_isLabelVisible(labelVisible), m_isExploded(exploded), m_pen(pen), m_brush(brush), m_percentage(0) {} + + QPieSliceId id() const { return m_id; } + + void setValue(qreal value) { m_value = value; } + qreal value() const { return m_value; } + + void setLabel(QString label) { m_label = label; } + QString label() const { return m_label; } + + void setLabelVisible(bool visible) { m_isLabelVisible = visible; } + bool isLabelVisible() const { return m_isLabelVisible; } + + void setExploded(bool exploded) { m_isExploded = exploded; } + bool isExploded() const { return m_isExploded; } + + void setPen(QPen pen) { m_pen = pen; } + QPen pen() const { return m_pen; } + + void setBrush(QBrush brush) { m_brush = brush; } + QBrush brush() const { return m_brush; } + + qreal percentage() const { return m_percentage; } + +private: + + // TODO: use private class + friend class QPieSeries; + + QPieSliceId m_id; qreal m_value; QString m_label; - QColor m_color; // TODO: should we even define color here? + bool m_isLabelVisible; bool m_isExploded; -}; -class PieChangeSet -{ -public: - QList m_added; - QList m_removed; - QList m_changed; + QPen m_pen; + QBrush m_brush; + + qreal m_percentage; // generated content }; class QTCOMMERCIALCHART_EXPORT QPieSeries : public QChartSeries @@ -47,24 +76,38 @@ public: PiePositionBottomRight }; + class ChangeSet + { + public: + QList m_added; + QList m_removed; + QList m_changed; + }; + public: QPieSeries(QObject *parent = 0); ~QPieSeries(); public: // from QChartSeries QChartSeriesType type() const { return QChartSeries::SeriesTypePie; } - virtual bool setData(QList data); + virtual bool setData(QList data); // TODO: remove this public: - bool set(QList slices); - bool add(QList slices); - bool add(QPieSlice slice); + // TODO: should we return id/bool or what? + // TODO: should we prefer passing a modifiable reference? + bool set(const QList& slices); + bool add(const QList& slices); + bool add(const QPieSlice& slice); + bool update(const QPieSlice& slice); + bool remove(QPieSliceId id); int count() const { return m_slices.count(); } - QList slices() const { return m_slices; } - QPieSlice slice(int index) const; - bool update(int index, QPieSlice slice); + QList slices() const { return m_slices.values(); } + QList ids() const { return m_slices.keys(); } + QPieSlice slice(QPieSliceId id) const; + + // TODO: sorting? // TODO: convenience functions //void updateValue(int sliceIndex, qreal value); @@ -72,6 +115,11 @@ public: //void updateColor(int sliceIndex, QColor color); //void updateExploded(int slizeIndex, bool exploded); + // TODO: customization + // set/get pen/brush + // - for label + // - for whole pie/slice + void setSizeFactor(qreal sizeFactor); qreal sizeFactor() const { return m_sizeFactor; } @@ -79,17 +127,29 @@ public: PiePosition position() const { return m_position; } Q_SIGNALS: - void changed(const PieChangeSet& changeSet); + void changed(const QPieSeries::ChangeSet& changeSet); void sizeFactorChanged(); void positionChanged(); + //void sliceClicked(QPieSliceId id); + //void sliceHoverEnter(QPieSliceId id); + //void sliceHoverLeave(QPieSliceId id); + +private: + QPieSliceId generateSliceId(); + void updateDerivativeData(); private: Q_DISABLE_COPY(QPieSeries) - friend class PiePresenter; + // TODO: use PIML - QList m_slices; + friend class PiePresenter; + friend class PieSlice; + + QHash m_slices; qreal m_sizeFactor; PiePosition m_position; + qreal m_total; + QPieSliceId m_sliceIdSeed; }; QTCOMMERCIALCHART_END_NAMESPACE