From 7350e0387b3f72a41c2a4a9805e5bcb37085c6aa 2012-02-17 10:37:23 From: Jani Honkonen Date: 2012-02-17 10:37:23 Subject: [PATCH] Refactor pie (again). QPieSlice's now emit signals and no id's anymore. Just pointers in the interface. --- diff --git a/example/multichart/multichartwidget.cpp b/example/multichart/multichartwidget.cpp index 9df1afc..eeb5bcd 100644 --- a/example/multichart/multichartwidget.cpp +++ b/example/multichart/multichartwidget.cpp @@ -16,8 +16,8 @@ MultiChartWidget::MultiChartWidget(QWidget *parent) : QChartView *chartView1 = new QChartView(); l->addWidget(chartView1); QPieSeries *pie = new QPieSeries(); - pie->add(QPieSlice(1.1, "label1")); - pie->add(QPieSlice(1.2, "label2")); + pie->add(1.1, "label1"); + pie->add(1.2, "label2"); chartView1->addSeries(pie); // Create chart 2 and add a simple scatter series onto it diff --git a/example/piechart/customslice.cpp b/example/piechart/customslice.cpp new file mode 100644 index 0000000..46c223e --- /dev/null +++ b/example/piechart/customslice.cpp @@ -0,0 +1,20 @@ +#include "customslice.h" + +CustomSlice::CustomSlice(qreal value, QObject* parent) + :QPieSlice(parent) +{ + setValue(value); + connect(this, SIGNAL(changed()), this, SLOT(updateLabel())); + connect(this, SIGNAL(hoverEnter()), this, SLOT(toggleExploded())); + connect(this, SIGNAL(hoverLeave()), this, SLOT(toggleExploded())); +} + +void CustomSlice::updateLabel() +{ + setLabel(QString::number(this->percentage())); +} + +void CustomSlice::toggleExploded() +{ + setExploded(!isExploded()); +} diff --git a/example/piechart/customslice.h b/example/piechart/customslice.h new file mode 100644 index 0000000..3c6ccf4 --- /dev/null +++ b/example/piechart/customslice.h @@ -0,0 +1,15 @@ +#include + +QTCOMMERCIALCHART_USE_NAMESPACE + +class CustomSlice : public QPieSlice +{ + Q_OBJECT + +public: + CustomSlice(qreal value, QObject* parent = 0); + +public Q_SLOTS: + void updateLabel(); + void toggleExploded(); +}; diff --git a/example/piechart/main.cpp b/example/piechart/main.cpp index 203b6b5..18b737c 100644 --- a/example/piechart/main.cpp +++ b/example/piechart/main.cpp @@ -1,9 +1,10 @@ #include #include -#include #include #include #include +#include +#include "customslice.h" QTCOMMERCIALCHART_USE_NAMESPACE @@ -11,23 +12,31 @@ int main(int argc, char *argv[]) { QApplication a(argc, argv); - // Create widget and scatter series - QChartView *chartWidget = new QChartView(); - QPieSeries *series = qobject_cast(chartWidget->createSeries(QChartSeries::SeriesTypePie)); - Q_ASSERT(series); - - // Add test data to the series - 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; - w.resize(640, 480); - w.setCentralWidget(chartWidget); - w.show(); + QMainWindow window; + + QPieSeries *series = new QPieSeries(); + series->add(5, "Slice 1"); + series->add(2, "Slice 2"); + series->add(3, "Slice 3"); + series->add(4, "Slice 4"); + series->add(5, "Slice 5"); + series->add(6, "Slice 6"); + series->add(7, "Slice 7"); + series->add(new CustomSlice(8)); + series->enableClickExplodes(true); + series->enableHoverHighlight(true); + + foreach (QPieSlice*s, series->slices()) + qDebug() << s->angle() << s->span() << s->percentage(); + + QChartView* chartView = new QChartView(&window); + chartView->addSeries(series); + chartView->setChartTitle("simple piechart"); + chartView->setChartTheme(QChart::ChartThemeIcy); + + window.setCentralWidget(chartView); + window.resize(600, 600); + window.show(); return a.exec(); } diff --git a/example/piechart/piechart.pro b/example/piechart/piechart.pro index 2d7c9ad..29de32e 100644 --- a/example/piechart/piechart.pro +++ b/example/piechart/piechart.pro @@ -10,8 +10,10 @@ QT += core gui TARGET = piechart TEMPLATE = app -SOURCES += main.cpp +SOURCES += main.cpp customslice.cpp +HEADERS += customslice.h -HEADERS += +OBJECTS_DIR = tmp +MOC_DIR = tmp diff --git a/src/charttheme.cpp b/src/charttheme.cpp index 0a19336..8d3b5d3 100644 --- a/src/charttheme.cpp +++ b/src/charttheme.cpp @@ -10,6 +10,7 @@ #include "qlinechartseries.h" #include "qscatterseries.h" #include "qpieseries.h" +#include "qpieslice.h" //items #include "axisitem_p.h" @@ -232,11 +233,9 @@ void ChartTheme::decorate(PiePresenter* item, QPieSeries* series, int /*count*/) } // finally update colors - 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); + foreach (QPieSlice* s, series->slices()) { + s->setPen(QPen(Qt::black)); // TODO: get from theme + s->setBrush(colors.takeFirst()); } } diff --git a/src/piechart/piechart.pri b/src/piechart/piechart.pri index 133f19c..d5018df 100644 --- a/src/piechart/piechart.pri +++ b/src/piechart/piechart.pri @@ -5,7 +5,8 @@ SOURCES += \ $$PWD/qpieseries.cpp \ $$PWD/pieslice.cpp \ $$PWD/piepresenter.cpp \ - $$PWD/pieslicelabel.cpp + $$PWD/pieslicelabel.cpp \ + $$PWD/qpieslice.cpp PRIVATE_HEADERS += \ $$PWD/piepresenter.h \ @@ -13,4 +14,5 @@ PRIVATE_HEADERS += \ $$PWD/pieslicelabel.h PUBLIC_HEADERS += \ - $$PWD/qpieseries.h + $$PWD/qpieseries.h \ + $$PWD/qpieslice.h diff --git a/src/piechart/piepresenter.cpp b/src/piechart/piepresenter.cpp index 87f0701..362f828 100644 --- a/src/piechart/piepresenter.cpp +++ b/src/piechart/piepresenter.cpp @@ -1,6 +1,7 @@ #include "piepresenter.h" #include "pieslice.h" +#include "qpieslice.h" #include #include @@ -29,140 +30,124 @@ void PiePresenter::paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget * 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; + //qDebug() << "PiePresenter::handleSeriesChanged()"; + //qDebug() << " added : " << changeSet.added(); + //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 (QPieSliceId id, m_series->m_slices.keys()) - addSlice(id); + foreach (QPieSlice* s, m_series->m_slices) + addSlice(s); return; } - foreach (QPieSliceId id, changeSet.m_removed) - deleteSlice(id); + foreach (QPieSlice* s, changeSet.removed()) + deleteSlice(s); - foreach (QPieSliceId id, changeSet.m_changed) - updateSlice(id); + foreach (QPieSlice* s, changeSet.added()) + addSlice(s); +} - foreach (QPieSliceId id, changeSet.m_added) - addSlice(id); +void PiePresenter::handleDomainChanged(const Domain& domain) +{ + // TODO +} + +void PiePresenter::handleGeometryChanged(const QRectF& rect) +{ + m_rect = rect; + updateGeometry(); } void PiePresenter::updateGeometry() { prepareGeometryChange(); - m_pieRect = m_rect; + QRectF pieRect = m_rect; - if (m_pieRect.width() < m_pieRect.height()) { - m_pieRect.setWidth(m_pieRect.width() * m_series->sizeFactor()); - m_pieRect.setHeight(m_pieRect.width()); - m_pieRect.moveCenter(m_rect.center()); + if (pieRect.width() < pieRect.height()) { + pieRect.setWidth(pieRect.width() * m_series->sizeFactor()); + pieRect.setHeight(pieRect.width()); + pieRect.moveCenter(m_rect.center()); } else { - m_pieRect.setHeight(m_pieRect.height() * m_series->sizeFactor()); - m_pieRect.setWidth(m_pieRect.height()); - m_pieRect.moveCenter(m_rect.center()); + pieRect.setHeight(pieRect.height() * m_series->sizeFactor()); + pieRect.setWidth(pieRect.height()); + pieRect.moveCenter(m_rect.center()); } switch (m_series->position()) { case QPieSeries::PiePositionTopLeft: { - m_pieRect.setHeight(m_pieRect.height() / 2); - m_pieRect.setWidth(m_pieRect.height()); - m_pieRect.moveCenter(QPointF(m_rect.center().x() / 2, m_rect.center().y() / 2)); + pieRect.setHeight(pieRect.height() / 2); + pieRect.setWidth(pieRect.height()); + pieRect.moveCenter(QPointF(m_rect.center().x() / 2, m_rect.center().y() / 2)); break; } case QPieSeries::PiePositionTopRight: { - m_pieRect.setHeight(m_pieRect.height() / 2); - m_pieRect.setWidth(m_pieRect.height()); - m_pieRect.moveCenter(QPointF((m_rect.center().x() / 2) * 3, m_rect.center().y() / 2)); + pieRect.setHeight(pieRect.height() / 2); + pieRect.setWidth(pieRect.height()); + pieRect.moveCenter(QPointF((m_rect.center().x() / 2) * 3, m_rect.center().y() / 2)); break; } case QPieSeries::PiePositionBottomLeft: { - m_pieRect.setHeight(m_pieRect.height() / 2); - m_pieRect.setWidth(m_pieRect.height()); - m_pieRect.moveCenter(QPointF(m_rect.center().x() / 2, (m_rect.center().y() / 2) * 3)); + pieRect.setHeight(pieRect.height() / 2); + pieRect.setWidth(pieRect.height()); + pieRect.moveCenter(QPointF(m_rect.center().x() / 2, (m_rect.center().y() / 2) * 3)); break; } case QPieSeries::PiePositionBottomRight: { - m_pieRect.setHeight(m_pieRect.height() / 2); - m_pieRect.setWidth(m_pieRect.height()); - m_pieRect.moveCenter(QPointF((m_rect.center().x() / 2) * 3, (m_rect.center().y() / 2) * 3)); + pieRect.setHeight(pieRect.height() / 2); + pieRect.setWidth(pieRect.height()); + pieRect.moveCenter(QPointF((m_rect.center().x() / 2) * 3, (m_rect.center().y() / 2) * 3)); break; } default: break; } - // 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; + if (m_pieRect != pieRect) { + m_pieRect = pieRect; + //qDebug() << "PiePresenter::updateGeometry()" << m_pieRect; + foreach (PieSlice* s, m_slices.values()) { + s->setPieRect(m_pieRect); + s->updateGeometry(); + } } - - //qDebug() << "PiePresenter::updateGeometry" << m_rect << m_pieRect; } -void PiePresenter::handleDomainChanged(const Domain& domain) +void PiePresenter::addSlice(QPieSlice* sliceData) { - // TODO -} + //qDebug() << "PiePresenter::addSlice()" << sliceData; -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); + if (m_slices.keys().contains(sliceData)) { + //qWarning() << "PiePresenter::addSlice(): slice already exists!" << sliceData; + Q_ASSERT(0); return; } // create slice - PieSlice *slice = new PieSlice(id, m_series, this); - m_slices.insert(id, slice); - - updateGeometry(); + PieSlice *slice = new PieSlice(this); + slice->updateData(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(QPieSliceId id) +void PiePresenter::deleteSlice(QPieSlice* sliceData) { - qDebug() << "PiePresenter::updateSlice()" << id; + //qDebug() << "PiePresenter::deleteSlice()" << sliceData; - // TODO: animation - if (m_slices.contains(id)) - m_slices.value(id)->updateData(); + if (m_slices.contains(sliceData)) + delete m_slices.take(sliceData); else { - qWarning() << "PiePresenter::updateSlice(): slice does not exist!" << id; - addSlice(id); + // nothing to remove + Q_ASSERT(0); // TODO: remove before release } - - 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 50e8a5d..a3348f9 100644 --- a/src/piechart/piepresenter.h +++ b/src/piechart/piepresenter.h @@ -32,13 +32,12 @@ public Q_SLOTS: void updateGeometry(); private: - void addSlice(QPieSliceId id); - void updateSlice(QPieSliceId id); - void deleteSlice(QPieSliceId id); + void addSlice(QPieSlice* sliceData); + void deleteSlice(QPieSlice* sliceData); private: friend class PieSlice; - QHash m_slices; + 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 321890d..898e3c3 100644 --- a/src/piechart/pieslice.cpp +++ b/src/piechart/pieslice.cpp @@ -2,6 +2,7 @@ #include "pieslicelabel.h" #include "piepresenter.h" #include "qpieseries.h" +#include "qpieslice.h" #include #include #include @@ -20,27 +21,25 @@ QPointF offset(qreal angle, qreal length) return QPointF(dx, -dy); } -PieSlice::PieSlice(QPieSliceId id, QPieSeries *series, QGraphicsItem* parent) +PieSlice::PieSlice(QGraphicsItem* parent) :QGraphicsObject(parent), - m_id(id), - m_series(series), m_slicelabel(new PieSliceLabel(this)), - m_isHovering(false) + m_angle(0), + m_span(0), + m_isExploded(false) { - Q_ASSERT(series); setAcceptHoverEvents(true); setAcceptedMouseButtons(Qt::LeftButton); - updateData(); } PieSlice::~PieSlice() { - qDebug() << "~PieSlice()" << m_id; + } QRectF PieSlice::boundingRect() const { - return m_rect; + return m_path.boundingRect(); } QPainterPath PieSlice::shape() const @@ -50,50 +49,43 @@ QPainterPath PieSlice::shape() const void PieSlice::paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget* /*widget*/) { - // set hover brush - // TODO: what if we are using gradients... - QBrush brush = m_data.brush(); - if (m_isHovering) - brush.setColor(brush.color().lighter()); - painter->setRenderHint(QPainter::Antialiasing); - painter->setPen(m_data.pen()); - painter->setBrush(brush); + painter->setPen(m_pen); + painter->setBrush(m_brush); painter->drawPath(m_path); } void PieSlice::hoverEnterEvent(QGraphicsSceneHoverEvent* /*event*/) { - m_isHovering = true; - update(); - // TODO: emit hoverEnter() + emit hoverEnter(); } void PieSlice::hoverLeaveEvent(QGraphicsSceneHoverEvent* /*event*/) { - m_isHovering = false; - update(); - // TODO: emit hoverLeave() + emit hoverLeave(); } void PieSlice::mousePressEvent(QGraphicsSceneMouseEvent* /*event*/) { - // 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); + emit clicked(); +} + +void PieSlice::setPieRect(QRectF rect) +{ + m_pieRect = rect; } -void PieSlice::updateGeometry(QRectF rect, qreal startAngle, qreal span) +void PieSlice::updateGeometry() { prepareGeometryChange(); // calculate center angle - qreal centerAngle = startAngle + (span/2); + qreal centerAngle = m_angle + (m_span/2); // adjust rect for exploding + QRectF rect = m_pieRect; rect.adjust(EXPLODE_OFFSET, EXPLODE_OFFSET, -EXPLODE_OFFSET ,-EXPLODE_OFFSET); - if (m_data.isExploded()) { + if (m_isExploded) { QPointF d = offset((centerAngle), EXPLODE_OFFSET); rect.translate(d.x(), d.y()); } @@ -102,9 +94,9 @@ void PieSlice::updateGeometry(QRectF rect, qreal startAngle, qreal span) // TODO: draw the shape so that it might have a hole in the center QPainterPath path; path.moveTo(rect.center()); - path.arcTo(rect, -startAngle + 90, -span); + path.arcTo(rect, -m_angle + 90, -m_span); + path.closeSubpath(); m_path = path; - m_rect = path.boundingRect(); // update label position qreal radius = rect.height() / 2; @@ -112,27 +104,36 @@ void PieSlice::updateGeometry(QRectF rect, qreal startAngle, qreal span) m_slicelabel->setArmStartPoint(edgeCenter); m_slicelabel->setArmAngle(centerAngle); - m_slicelabel->setArmLength(50); m_slicelabel->updateGeometry(); - //qDebug() << "PieSlice::updateGeometry" << m_rect; + //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() +void PieSlice::updateData(const QPieSlice* sliceData) { - if (!m_series->m_slices.contains(m_id)) - qWarning() << "PieSlice::updateData(): cannot find slice data!" << m_id; + // TODO: compare what has changes to avoid unneccesary geometry updates - QPieSlice data = m_series->slice(m_id); - // TODO: find out what has changed and trigger some animation - m_data = data; + m_angle = sliceData->angle(); + m_span = sliceData->span(); + m_isExploded = sliceData->isExploded(); + m_pen = sliceData->pen(); + m_brush = sliceData->brush(); + updateGeometry(); update(); - m_slicelabel->setVisible(m_data.isLabelVisible()); - m_slicelabel->setText(m_data.label()); - //m_slicelabel->setPen(m_data.labelPen()); - //m_slicelabel->setFont(m_data.labelFont()); + m_slicelabel->setVisible(sliceData->isLabelVisible()); + m_slicelabel->setText(sliceData->label()); + m_slicelabel->setPen(sliceData->labelPen()); + m_slicelabel->setFont(sliceData->labelFont()); + m_slicelabel->setArmLength(sliceData->labelArmLenght()); m_slicelabel->updateGeometry(); // text size & font modifies the geometry m_slicelabel->update(); } diff --git a/src/piechart/pieslice.h b/src/piechart/pieslice.h index f9c6476..8a67e72 100644 --- a/src/piechart/pieslice.h +++ b/src/piechart/pieslice.h @@ -12,13 +12,14 @@ QTCOMMERCIALCHART_BEGIN_NAMESPACE class PiePresenter; class PieSliceLabel; +class QPieSlice; class PieSlice : public QGraphicsObject { Q_OBJECT public: - PieSlice(QPieSliceId id, QPieSeries *series, QGraphicsItem* parent = 0); + PieSlice(QGraphicsItem* parent = 0); ~PieSlice(); public: // from QGraphicsItem @@ -31,19 +32,27 @@ public: // from QGraphicsItem Q_SIGNALS: void clicked(); + void hoverEnter(); + void hoverLeave(); -public: - void updateGeometry(QRectF rect, qreal startAngle, qreal span); - void updateData(); +public Q_SLOTS: + void handleSliceDataChanged(); + void setPieRect(QRectF rect); + void updateGeometry(); + void updateData(const QPieSlice *sliceData); private: - QPieSliceId m_id; - QPieSeries* m_series; - QPieSlice m_data; PieSliceLabel* m_slicelabel; + + QRectF m_pieRect; QPainterPath m_path; - QRectF m_rect; - bool m_isHovering; + + qreal m_angle; + qreal m_span; + bool m_isExploded; + + QPen m_pen; + QBrush m_brush; }; QTCOMMERCIALCHART_END_NAMESPACE diff --git a/src/piechart/qpieseries.cpp b/src/piechart/qpieseries.cpp index ac5eadc..40217db 100644 --- a/src/piechart/qpieseries.cpp +++ b/src/piechart/qpieseries.cpp @@ -1,112 +1,147 @@ #include "qpieseries.h" +#include "qpieslice.h" #include "piepresenter.h" #include "pieslice.h" #include QTCOMMERCIALCHART_BEGIN_NAMESPACE -QPieSeries::QPieSeries(QObject *parent) : - QChartSeries(parent), - m_sizeFactor(1.0), - m_position(PiePositionMaximized), - m_sliceIdSeed(0) +void QPieSeries::ChangeSet::appendAdded(QPieSlice* slice) { + if (!m_added.contains(slice)) + m_added << slice; } -QPieSeries::~QPieSeries() +void QPieSeries::ChangeSet::appendChanged(QPieSlice* slice) { + if (!m_changed.contains(slice)) + m_changed << slice; +} +void QPieSeries::ChangeSet::appendRemoved(QPieSlice* slice) +{ + if (!m_removed.contains(slice)) + m_removed << slice; } -bool QPieSeries::setData(QList data) +QList QPieSeries::ChangeSet::added() const { - // TODO: remove this function - QList slices; - foreach (int value, data) - slices << QPieSlice(value, QString::number(value)); - return set(slices); + return m_added; } -bool QPieSeries::set(const QList& slices) +QList QPieSeries::ChangeSet::changed() const { - if (!slices.count()) - return false; + return m_changed; +} - ChangeSet changeSet; +QList QPieSeries::ChangeSet::removed() const +{ + return m_removed; +} - foreach (QPieSlice s, m_slices.values()) - changeSet.m_removed << s.id(); +bool QPieSeries::ChangeSet::isEmpty() const +{ + if (m_added.count() || m_changed.count() || m_removed.count()) + return false; + return true; +} - m_slices.clear(); - foreach (QPieSlice s, slices) { - s.m_id = generateSliceId(); - m_slices.insert(s.id(), s); - changeSet.m_added << s.id(); - } +QPieSeries::QPieSeries(QObject *parent) : + QChartSeries(parent), + m_sizeFactor(1.0), + m_position(PiePositionMaximized), + m_pieStartAngle(0), + m_pieSpan(360) +{ - updateDerivativeData(); - emit changed(changeSet); +} +QPieSeries::~QPieSeries() +{ + // slices destroyed by parent object + qDebug() << "~QPieSeries"; +} + +bool QPieSeries::setData(QList data) +{ + // TODO: remove this function + QList slices; + foreach (int value, data) + slices << new QPieSlice(value, QString::number(value)); + set(slices); return true; } -bool QPieSeries::add(const QList& slices) +void QPieSeries::set(QList slices) { - if (!slices.count()) - return false; + clear(); + add(slices); +} +void QPieSeries::add(QList slices) +{ ChangeSet changeSet; - foreach (QPieSlice s, slices) { - s.m_id = generateSliceId(); - m_slices.insert(s.id(), s); - changeSet.m_added << s.id(); + foreach (QPieSlice* s, slices) { + s->setParent(this); + m_slices << s; + changeSet.appendAdded(s); } updateDerivativeData(); - emit changed(changeSet); - return true; + foreach (QPieSlice* s, slices) { + connect(s, SIGNAL(changed()), this, SLOT(sliceChanged())); + connect(s, SIGNAL(clicked()), this, SLOT(sliceClicked())); + connect(s, SIGNAL(hoverEnter()), this, SLOT(sliceHoverEnter())); + connect(s, SIGNAL(hoverLeave()), this, SLOT(sliceHoverLeave())); + } + + emit changed(changeSet); } -bool QPieSeries::add(const QPieSlice& slice) +void QPieSeries::add(QPieSlice* slice) { - return add(QList() << slice); + add(QList() << slice); } -bool QPieSeries::update(const QPieSlice& slice) +QPieSlice* QPieSeries::add(qreal value, QString name) { - if (!m_slices.contains(slice.id())) - return false; // series does not contain this slice + QPieSlice* slice = new QPieSlice(value, name); + add(slice); + return slice; +} - m_slices[slice.id()] = slice; +void QPieSeries::remove(QPieSlice* slice) +{ + if (!m_slices.removeOne(slice)) { + Q_ASSERT(0); // TODO: remove before release + return; + } ChangeSet changeSet; - changeSet.m_changed << slice.id(); - updateDerivativeData(); + changeSet.appendRemoved(slice); emit changed(changeSet); - return true; + delete slice; + slice = NULL; + + updateDerivativeData(); } -bool QPieSeries::remove(QPieSliceId id) +void QPieSeries::clear() { - if (!m_slices.contains(id)) - return false; // series does not contain this slice - - m_slices.remove(id); + if (m_slices.count() == 0) + return; ChangeSet changeSet; - changeSet.m_removed << id; - updateDerivativeData(); + foreach (QPieSlice* s, m_slices) { + changeSet.appendRemoved(s); + m_slices.removeOne(s); + delete s; + } emit changed(changeSet); - - return true; -} - -QPieSlice QPieSeries::slice(QPieSliceId id) const -{ - return m_slices.value(id); + updateDerivativeData(); } void QPieSeries::setSizeFactor(qreal factor) @@ -128,23 +163,136 @@ void QPieSeries::setPosition(PiePosition position) } } -QPieSliceId QPieSeries::generateSliceId() +void QPieSeries::setSpan(qreal startAngle, qreal span) +{ + if (startAngle >= 0 && startAngle < 360 && + span > 0 && span <= 360) { + m_pieStartAngle = startAngle; + m_pieSpan = span; + updateDerivativeData(); + } +} + +void QPieSeries::setLabelsVisible(bool visible) +{ + foreach (QPieSlice* s, m_slices) + s->setLabelVisible(visible); +} + +void QPieSeries::enableClickExplodes(bool enable) +{ + if (enable) + connect(this, SIGNAL(clicked(QPieSlice*)), this, SLOT(toggleExploded(QPieSlice*))); + else + disconnect(this, SLOT(toggleExploded(QPieSlice*))); +} + +void QPieSeries::enableHoverHighlight(bool enable) +{ + if (enable) { + connect(this, SIGNAL(hoverEnter(QPieSlice*)), this, SLOT(highlightOn(QPieSlice*))); + connect(this, SIGNAL(hoverLeave(QPieSlice*)), this, SLOT(highlightOff(QPieSlice*))); + } else { + disconnect(this, SLOT(hoverEnter(QPieSlice*))); + disconnect(this, SLOT(hoverLeave(QPieSlice*))); + } +} + +void QPieSeries::sliceChanged() +{ + QPieSlice* slice = qobject_cast(sender()); + Q_ASSERT(m_slices.contains(slice)); + + ChangeSet changeSet; + changeSet.appendChanged(slice); + emit changed(changeSet); + + updateDerivativeData(); +} + +void QPieSeries::sliceClicked() +{ + QPieSlice* slice = qobject_cast(sender()); + Q_ASSERT(m_slices.contains(slice)); + emit clicked(slice); +} + +void QPieSeries::sliceHoverEnter() +{ + QPieSlice* slice = qobject_cast(sender()); + Q_ASSERT(m_slices.contains(slice)); + emit hoverEnter(slice); +} + +void QPieSeries::sliceHoverLeave() { - // Id is quint64 so it should be enough for us. - // Note that id is not unique between pie series. - return m_sliceIdSeed++; + QPieSlice* slice = qobject_cast(sender()); + Q_ASSERT(m_slices.contains(slice)); + emit hoverLeave(slice); +} + +void QPieSeries::toggleExploded(QPieSlice* slice) +{ + Q_ASSERT(slice); + slice->setExploded(!slice->isExploded()); +} + +void QPieSeries::highlightOn(QPieSlice* slice) +{ + Q_ASSERT(slice); + QColor c = slice->brush().color().lighter(); + slice->setBrush(c); +} + +void QPieSeries::highlightOff(QPieSlice* slice) +{ + Q_ASSERT(slice); + QColor c = slice->brush().color().darker(150); + slice->setBrush(c); } 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 + // nothing to do? + if (m_slices.count() == 0) + return; + + // calculate total + foreach (QPieSlice* s, m_slices) + m_total += s->value(); + + // we must have some values + Q_ASSERT(m_total > 0); // TODO - foreach (QPieSliceId id, m_slices.keys()) - m_slices[id].m_percentage = m_slices.value(id).value() / m_total; + // update slice attributes + qreal sliceAngle = m_pieStartAngle; + foreach (QPieSlice* s, m_slices) { + + bool changed = false; + + qreal percentage = s->value() / m_total; + if (s->m_percentage != percentage) { + s->m_percentage = percentage; + changed = true; + } + + qreal sliceSpan = m_pieSpan * percentage; + if (s->m_span != sliceSpan) { + s->m_span = sliceSpan; + changed = true; + } + + if (s->m_angle != sliceAngle) { + s->m_angle = sliceAngle; + changed = true; + } + sliceAngle += sliceSpan; + + if (changed) + emit s->changed(); + } } #include "moc_qpieseries.cpp" diff --git a/src/piechart/qpieseries.h b/src/piechart/qpieseries.h index d3400b7..65e3e72 100644 --- a/src/piechart/qpieseries.h +++ b/src/piechart/qpieseries.h @@ -7,69 +7,13 @@ #include #include #include +#include class QGraphicsObject; QTCOMMERCIALCHART_BEGIN_NAMESPACE class PiePresenter; class PieSlice; - -typedef quint64 QPieSliceId; - -class QPieSlice -{ -public: - QPieSlice() - :m_id(-1), m_value(0), m_isLabelVisible(true), m_isExploded(false), m_percentage(0) {} - - 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; } - - // TODO: - //void setLabelPen(QPen pen) {}; - //QPen labelPen() const {}; - //void setLabelFont(QFont font); - //QFont labelFont() const; - //void setLabelArmLenght(qreal len) {}; - //qreal labelArmLenght() const {}; - - 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; - bool m_isLabelVisible; - bool m_isExploded; - - QPen m_pen; - QBrush m_brush; - - qreal m_percentage; // generated content -}; +class QPieSlice; class QTCOMMERCIALCHART_EXPORT QPieSeries : public QChartSeries { @@ -87,42 +31,48 @@ public: class ChangeSet { public: - QList m_added; - QList m_removed; - QList m_changed; + void appendAdded(QPieSlice* slice); + void appendChanged(QPieSlice* slice); + void appendRemoved(QPieSlice* slice); + + QList added() const; + QList changed() const; + QList removed() const; + + bool isEmpty() const; + + private: + QList m_added; + QList m_changed; + QList m_removed; }; public: QPieSeries(QObject *parent = 0); - ~QPieSeries(); + virtual ~QPieSeries(); public: // from QChartSeries QChartSeriesType type() const { return QChartSeries::SeriesTypePie; } virtual bool setData(QList data); // TODO: remove this public: - // 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); + void set(QList slices); + void add(QList slices); + void add(QPieSlice* slice); + QPieSlice* add(qreal value, QString name); + void remove(QPieSlice* slice); + void clear(); int count() const { return m_slices.count(); } - QList slices() const { return m_slices.values(); } - QList ids() const { return m_slices.keys(); } - QPieSlice slice(QPieSliceId id) const; + QList slices() const { return m_slices; } - // TODO: sorting? + // TODO: find slices? + // QList findByValue(qreal value); + // ... - // TODO: convenience functions? - //void setValue(QPieSliceId id, qreal value); - //void setLabel(QPieSliceId id, QString label); - //void setPen(QPieSliceId id, QPen pen); - //void setBrush(QPieSliceId id, QBrush brush); - //void setExploded(QPieSliceId id, bool exploded); + // TODO: sorting slices? + // void sort(QPieSeries::SortByValue) void setSizeFactor(qreal sizeFactor); qreal sizeFactor() const { return m_sizeFactor; } @@ -130,18 +80,31 @@ public: void setPosition(PiePosition position); PiePosition position() const { return m_position; } + void setSpan(qreal startAngle, qreal span); + + void setLabelsVisible(bool visible); + void enableClickExplodes(bool enable); + void enableHoverHighlight(bool enable); + Q_SIGNALS: void changed(const QPieSeries::ChangeSet& changeSet); + void clicked(QPieSlice* slice); + void hoverEnter(QPieSlice* slice); + void hoverLeave(QPieSlice* slice); void sizeFactorChanged(); void positionChanged(); - // TODO: - //void sliceClicked(QPieSliceId id); - // ?? void sliceHoverEnter(QPieSliceId id); - // ?? void sliceHoverLeave(QPieSliceId id); +private Q_SLOTS: // should be private and not in the interface + void sliceChanged(); + void sliceClicked(); + void sliceHoverEnter(); + void sliceHoverLeave(); + + void toggleExploded(QPieSlice* slice); + void highlightOn(QPieSlice* slice); + void highlightOff(QPieSlice* slice); private: - QPieSliceId generateSliceId(); void updateDerivativeData(); private: @@ -151,11 +114,12 @@ private: friend class PiePresenter; friend class PieSlice; - QHash m_slices; + QList m_slices; qreal m_sizeFactor; PiePosition m_position; qreal m_total; - QPieSliceId m_sliceIdSeed; + qreal m_pieStartAngle; + qreal m_pieSpan; }; QTCOMMERCIALCHART_END_NAMESPACE diff --git a/src/piechart/qpieslice.cpp b/src/piechart/qpieslice.cpp new file mode 100644 index 0000000..595575a --- /dev/null +++ b/src/piechart/qpieslice.cpp @@ -0,0 +1,181 @@ +#include "qpieslice.h" + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +#define DEFAULT_PEN_COLOR Qt::black +#define DEFAULT_BRUSH_COLOR Qt::white +#define DEFAULT_LABEL_ARM_LENGTH 50 + +QPieSlice::QPieSlice(QObject *parent) + :QObject(parent), + m_value(0), + m_isLabelVisible(true), + m_isExploded(false), + m_percentage(0), + m_angle(0), + m_span(0), + m_pen(DEFAULT_PEN_COLOR), + m_brush(DEFAULT_BRUSH_COLOR), + m_labelPen(DEFAULT_PEN_COLOR), + m_labelArmLength(DEFAULT_LABEL_ARM_LENGTH) +{ + +} + +QPieSlice::QPieSlice(qreal value, QString label, bool labelVisible, QObject *parent) + :QObject(parent), + m_value(value), + m_label(label), + m_isLabelVisible(labelVisible), + m_isExploded(false), + m_percentage(0), + m_angle(0), + m_span(0), + m_pen(DEFAULT_PEN_COLOR), + m_brush(DEFAULT_BRUSH_COLOR), + m_labelPen(DEFAULT_PEN_COLOR), + m_labelArmLength(DEFAULT_LABEL_ARM_LENGTH) +{ + +} + +QPieSlice::~QPieSlice() +{ + +} + +qreal QPieSlice::value() const +{ + return m_value; +} + +QString QPieSlice::label() const +{ + return m_label; +} + +bool QPieSlice::isLabelVisible() const +{ + return m_isLabelVisible; +} + +bool QPieSlice::isExploded() const +{ + return m_isExploded; +} + +qreal QPieSlice::percentage() const +{ + return m_percentage; +} + +qreal QPieSlice::angle() const +{ + return m_angle; +} + +qreal QPieSlice::span() const +{ + return m_span; +} + +QPen QPieSlice::pen() const +{ + return m_pen; +} + +QBrush QPieSlice::brush() const +{ + return m_brush; +} + +QPen QPieSlice::labelPen() const +{ + return m_labelPen; +} + +QFont QPieSlice::labelFont() const +{ + return m_labelFont; +} + +qreal QPieSlice::labelArmLenght() const +{ + return m_labelArmLength; +} + +void QPieSlice::setValue(qreal value) +{ + if (m_value != value) { + m_value = value; + emit changed(); + } +} + +void QPieSlice::setLabel(QString label) +{ + if (m_label != label) { + m_label = label; + emit changed(); + } +} + +void QPieSlice::setLabelVisible(bool visible) +{ + if (m_isLabelVisible != visible) { + m_isLabelVisible = visible; + emit changed(); + } +} + +void QPieSlice::setExploded(bool exploded) +{ + if (m_isExploded != exploded) { + m_isExploded = exploded; + emit changed(); + } +} + +void QPieSlice::setPen(QPen pen) +{ + if (m_pen != pen) { + m_pen = pen; + emit changed(); + } +} + +void QPieSlice::setBrush(QBrush brush) +{ + if (m_brush != brush) { + m_brush = brush; + emit changed(); + } +} + +void QPieSlice::setLabelFont(QFont font) +{ + if (m_labelFont != font) { + m_labelFont = font; + emit changed(); + } +} + +void QPieSlice::setLabelPen(QPen pen) +{ + if (m_labelPen != pen) { + m_labelPen = pen; + emit changed(); + } +} + +void QPieSlice::setLabelArmLength(qreal len) +{ + if (m_labelArmLength != len) { + m_labelArmLength = len; + emit changed(); + } +} + +#include "moc_qpieslice.cpp" + +QTCOMMERCIALCHART_END_NAMESPACE diff --git a/src/piechart/qpieslice.h b/src/piechart/qpieslice.h new file mode 100644 index 0000000..4a3bddf --- /dev/null +++ b/src/piechart/qpieslice.h @@ -0,0 +1,86 @@ +#ifndef QPIESLICE_H +#define QPIESLICE_H + +#include +#include +#include +#include +#include + +QTCOMMERCIALCHART_BEGIN_NAMESPACE + +class QTCOMMERCIALCHART_EXPORT QPieSlice : public QObject +{ + Q_OBJECT + +public: + QPieSlice(QObject *parent = 0); + QPieSlice(qreal value, QString label, bool labelVisible = true, QObject *parent = 0); + virtual ~QPieSlice(); + + // data + qreal value() const; + QString label() const; + bool isLabelVisible() const; + bool isExploded() const; + + // generated data + qreal percentage() const; + qreal angle() const; + qreal span() const; + + // customization + QPen pen() const; + QBrush brush() const; + QPen labelPen() const; + QFont labelFont() const; + qreal labelArmLenght() const; + +Q_SIGNALS: + void clicked(); + void hoverEnter(); + void hoverLeave(); + void changed(); + +public Q_SLOTS: + + // data + void setLabel(QString label); + void setLabelVisible(bool visible); + void setValue(qreal value); + void setExploded(bool exploded); + + // customization + void setPen(QPen pen); + void setBrush(QBrush brush); + void setLabelFont(QFont font); + void setLabelPen(QPen pen); + void setLabelArmLength(qreal len); + +private: + + // TODO: use private class + friend class QPieSeries; + + // data + qreal m_value; + QString m_label; + bool m_isLabelVisible; + bool m_isExploded; + + // generated data + qreal m_percentage; + qreal m_angle; + qreal m_span; + + // customization + QPen m_pen; + QBrush m_brush; + QPen m_labelPen; + QFont m_labelFont; + qreal m_labelArmLength; +}; + +QTCOMMERCIALCHART_END_NAMESPACE + +#endif // QPIESLICE_H