From 8d9e377a106572b4396ea96c0eedbb42611536d8 2012-03-02 14:47:03 From: Jani Honkonen Date: 2012-03-02 14:47:03 Subject: [PATCH] Added a pie chart customization example and refactoring the pie interface. --- diff --git a/example/example.pro b/example/example.pro index 2266f09..738ee74 100644 --- a/example/example.pro +++ b/example/example.pro @@ -7,6 +7,7 @@ SUBDIRS += linechart \ percentbarchart \ scatter \ piechart \ + piechartcustomization \ piechartdrilldown \ dynamiclinechart \ axischart \ diff --git a/example/piechartcustomization/main.cpp b/example/piechartcustomization/main.cpp new file mode 100644 index 0000000..ad4a782 --- /dev/null +++ b/example/piechartcustomization/main.cpp @@ -0,0 +1,224 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QTCOMMERCIALCHART_USE_NAMESPACE + +Q_DECLARE_METATYPE(QPieSeries::PiePosition) + +class CustomSlice : public QPieSlice +{ + Q_OBJECT +public: + CustomSlice(qreal value, QString label) + :QPieSlice(value, label) + { + connect(this, SIGNAL(hoverEnter()), this, SLOT(handleHoverEnter())); + connect(this, SIGNAL(hoverLeave()), this, SLOT(handleHoverLeave())); + } + +public Q_SLOTS: + + void handleHoverEnter() + { + QBrush brush = this->brush(); + m_originalBrush = brush; + brush.setColor(brush.color().lighter()); + setBrush(brush); + } + + void handleHoverLeave() + { + setBrush(m_originalBrush); + } + +private: + QBrush m_originalBrush; +}; + +class MainWidget : public QWidget +{ + Q_OBJECT + +public: + explicit MainWidget(QWidget* parent = 0) + :QWidget(parent) + { + m_chartView = new QChartView(); + m_chartView->setChartTitle("Piechart customization"); + //m_chartView->setRenderHint(QPainter::Antialiasing); + + m_series = new QPieSeries(); + *m_series << new CustomSlice(10.0, "Slice 1"); + *m_series << new CustomSlice(20.0, "Slice 2"); + *m_series << new CustomSlice(30.0, "Slice 3"); + *m_series << new CustomSlice(40.0, "Slice 4"); + *m_series << new CustomSlice(50.0, "Slice 5"); + m_chartView->addSeries(m_series); + + m_vPosition = new QComboBox(); + m_vPosition->addItem("Top", QPieSeries::PiePositionTop); + m_vPosition->addItem("Bottom", QPieSeries::PiePositionBottom); + m_vPosition->addItem("Center", QPieSeries::PiePositionVCenter); + + m_hPosition = new QComboBox(); + m_hPosition->addItem("Left", QPieSeries::PiePositionLeft); + m_hPosition->addItem("Right", QPieSeries::PiePositionRight); + m_hPosition->addItem("Center", QPieSeries::PiePositionHCenter); + + m_sizePolicy = new QComboBox(); + m_sizePolicy->addItem("Maximized", QPieSeries::PieSizePolicyMaximized); + m_sizePolicy->addItem("Space for labels", QPieSeries::PieSizePolicyReserveSpaceForLabels); + m_sizePolicy->addItem("Space for exploding", QPieSeries::PieSizePolicyReserveSpaceForExploding); + m_sizePolicy->addItem("Space for all", QPieSeries::PieSizePolicyReserveSpaceForAll); + + m_sizeFactor = new QDoubleSpinBox(); + m_sizeFactor->setMinimum(0.0); + m_sizeFactor->setMaximum(1.0); + m_sizeFactor->setValue(m_series->sizeFactor()); + m_sizeFactor->setSingleStep(0.1); + + m_startAngle = new QDoubleSpinBox(); + m_startAngle->setMinimum(0.0); + m_startAngle->setMaximum(360); + m_startAngle->setValue(m_series->startAngle()); + m_startAngle->setSingleStep(1); + + m_endAngle = new QDoubleSpinBox(); + m_endAngle->setMinimum(0.0); + m_endAngle->setMaximum(360); + m_endAngle->setValue(m_series->endAngle()); + m_endAngle->setSingleStep(1); + + QFormLayout* seriesSettingsLayout = new QFormLayout(); + seriesSettingsLayout->addRow("Vertical position", m_vPosition); + seriesSettingsLayout->addRow("Horizontal position", m_hPosition); + seriesSettingsLayout->addRow("Size policy", m_sizePolicy); + seriesSettingsLayout->addRow("Size factor", m_sizeFactor); + seriesSettingsLayout->addRow("Start angle", m_startAngle); + seriesSettingsLayout->addRow("End angle", m_endAngle); + QGroupBox* seriesSettings = new QGroupBox("Series"); + seriesSettings->setLayout(seriesSettingsLayout); + + m_sliceName = new QLabel(""); + m_sliceValue = new QDoubleSpinBox(); + m_sliceValue->setMaximum(1000); + m_sliceLabelVisible = new QCheckBox(); + m_sliceExploded = new QCheckBox(); + + QFormLayout* sliceSettingsLayout = new QFormLayout(); + sliceSettingsLayout->addRow("Selected", m_sliceName); + sliceSettingsLayout->addRow("Value", m_sliceValue); + sliceSettingsLayout->addRow("Label visible", m_sliceLabelVisible); + sliceSettingsLayout->addRow("Exploded", m_sliceExploded); + QGroupBox* sliceSettings = new QGroupBox("Slice"); + sliceSettings->setLayout(sliceSettingsLayout); + + QGridLayout* baseLayout = new QGridLayout(); + baseLayout->addWidget(seriesSettings, 0, 0); + baseLayout->addWidget(sliceSettings, 1, 0); + baseLayout->addWidget(m_chartView, 0, 1, 2, 1); + setLayout(baseLayout); + + connect(m_vPosition, SIGNAL(currentIndexChanged(int)), this, SLOT(updateSerieSettings())); + connect(m_hPosition, SIGNAL(currentIndexChanged(int)), this, SLOT(updateSerieSettings())); + connect(m_sizePolicy, SIGNAL(currentIndexChanged(int)), this, SLOT(updateSerieSettings())); + connect(m_sizeFactor, SIGNAL(valueChanged(double)), this, SLOT(updateSerieSettings())); + connect(m_startAngle, SIGNAL(valueChanged(double)), this, SLOT(updateSerieSettings())); + connect(m_endAngle, SIGNAL(valueChanged(double)), this, SLOT(updateSerieSettings())); + + connect(m_sliceValue, SIGNAL(valueChanged(double)), this, SLOT(updateSliceSettings())); + connect(m_sliceLabelVisible, SIGNAL(toggled(bool)), this, SLOT(updateSliceSettings())); + connect(m_sliceExploded, SIGNAL(toggled(bool)), this, SLOT(updateSliceSettings())); + + connect(m_series, SIGNAL(clicked(QPieSlice*)), this, SLOT(handleSliceClicked(QPieSlice*))); + + updateSerieSettings(); + } + +public Q_SLOTS: + + void updateSerieSettings() + { + QPieSeries::PiePosition vPos(m_vPosition->itemData(m_vPosition->currentIndex()).toInt()); + QPieSeries::PiePosition hPos(m_hPosition->itemData(m_hPosition->currentIndex()).toInt()); + m_series->setPosition(vPos | hPos); + + QPieSeries::PieSizePolicy policy(m_sizePolicy->itemData(m_sizePolicy->currentIndex()).toInt()); + m_series->setSizePolicy(policy); + + m_series->setSizeFactor(m_sizeFactor->value()); + + m_series->setStartAngle(m_startAngle->value()); + m_series->setEndAngle(m_endAngle->value()); + } + + void updateSliceSettings() + { + m_slice->setValue(m_sliceValue->value()); + m_slice->setLabelVisible(m_sliceLabelVisible->isChecked()); + m_slice->setExploded(m_sliceExploded->isChecked()); + } + + void handleSliceClicked(QPieSlice* slice) + { + m_slice = slice; + m_sliceName->setText(slice->label()); + + m_sliceValue->blockSignals(true); + m_sliceValue->setValue(slice->value()); + m_sliceValue->blockSignals(false); + + m_sliceLabelVisible->blockSignals(true); + m_sliceLabelVisible->setChecked(slice->isLabelVisible()); + m_sliceLabelVisible->blockSignals(false); + + m_sliceExploded->blockSignals(true); + m_sliceExploded->setChecked(slice->isExploded()); + m_sliceExploded->blockSignals(false); + } + +private: + QChartView* m_chartView; + QPieSeries* m_series; + QPieSlice* m_slice; + + QComboBox* m_vPosition; + QComboBox* m_hPosition; + QComboBox* m_sizePolicy; + QDoubleSpinBox* m_sizeFactor; + QDoubleSpinBox* m_startAngle; + QDoubleSpinBox* m_endAngle; + + QLabel* m_sliceName; + QDoubleSpinBox* m_sliceValue; + QCheckBox* m_sliceLabelVisible; + QCheckBox* m_sliceExploded; +}; + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + + QMainWindow window; + + MainWidget* widget = new MainWidget(); + + window.setCentralWidget(widget); + window.resize(900, 600); + window.show(); + + return a.exec(); +} + +#include "main.moc" diff --git a/example/piechartcustomization/piechartcustomization.pro b/example/piechartcustomization/piechartcustomization.pro new file mode 100644 index 0000000..1230dad --- /dev/null +++ b/example/piechartcustomization/piechartcustomization.pro @@ -0,0 +1,8 @@ +!include( ../example.pri ) { + error( "Couldn't find the example.pri file!" ) +} +TARGET = piechartcustomization +SOURCES += main.cpp +HEADERS += + + diff --git a/src/piechart/piechart.pri b/src/piechart/piechart.pri index dbed0d8..cb4b8b8 100644 --- a/src/piechart/piechart.pri +++ b/src/piechart/piechart.pri @@ -5,13 +5,11 @@ SOURCES += \ $$PWD/qpieseries.cpp \ $$PWD/pieslice.cpp \ $$PWD/piepresenter.cpp \ - $$PWD/pieslicelabel.cpp \ $$PWD/qpieslice.cpp PRIVATE_HEADERS += \ $$PWD/piepresenter_p.h \ $$PWD/pieslice_p.h \ - $$PWD/pieslicelabel_p.h PUBLIC_HEADERS += \ $$PWD/qpieseries.h \ diff --git a/src/piechart/piepresenter.cpp b/src/piechart/piepresenter.cpp index 9be832e..fe2bb36 100644 --- a/src/piechart/piepresenter.cpp +++ b/src/piechart/piepresenter.cpp @@ -1,12 +1,9 @@ - #include "piepresenter_p.h" #include "pieslice_p.h" #include "qpieslice.h" -#include "pieslicelabel_p.h" #include "qpieseries.h" -#include #include -#include +#include QTCOMMERCIALCHART_BEGIN_NAMESPACE @@ -19,6 +16,7 @@ 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())); + connect(series, SIGNAL(sizePolicyChanged()), this, SLOT(updateGeometry())); if (m_series->count()) { QPieSeries::ChangeSet changeSet; @@ -36,6 +34,8 @@ void PiePresenter::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QW { // TODO: paint shadows for all components // - get paths from items & merge & offset and draw with shadow color? + //painter->setBrush(QBrush(Qt::red)); + //painter->drawRect(m_debugRect); } void PiePresenter::handleSeriesChanged(const QPieSeries::ChangeSet& changeSet) @@ -78,84 +78,104 @@ void PiePresenter::updateGeometry() // 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()); - pieRect.moveCenter(m_rect.center()); } else { - pieRect.setHeight(pieRect.height() * m_series->sizeFactor()); pieRect.setWidth(pieRect.height()); - pieRect.moveCenter(m_rect.center()); } // position the pie rectangle - switch (m_series->position()) { - case QPieSeries::PiePositionTopLeft: { - 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: { - 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: { - 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: { - 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; - } + QPointF center = m_rect.center(); // default position is center + qreal dx = pieRect.width() / 2; + qreal dy = pieRect.height() / 2; + if (m_series->position() & QPieSeries::PiePositionLeft) + center.setX(m_rect.left() + dx); + if (m_series->position() & QPieSeries::PiePositionRight) + center.setX(m_rect.right() - dx); + if (m_series->position() & QPieSeries::PiePositionHCenter) + center.setX(m_rect.center().x()); + if (m_series->position() & QPieSeries::PiePositionTop) + center.setY(m_rect.top() + dy); + if (m_series->position() & QPieSeries::PiePositionBottom) + center.setY(m_rect.bottom() - dy); + if (m_series->position() & QPieSeries::PiePositionVCenter) + center.setY(m_rect.center().y()); + pieRect.moveCenter(center); // 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 - - // the arm - qreal centerAngle = s->m_startAngle + (s->m_angleSpan / 2); - qreal len = pieRadius + PIESLICE_LABEL_GAP + s->labelArmLength() + s->explodeDistance(); - QPointF dp(qSin(centerAngle*(PI/180)) * len, -qCos(centerAngle*(PI/180)) * len); - QPointF p = pieRect.center() + dp; - - // the label text - QFontMetricsF fm(s->labelFont()); - QRectF labelRect = fm.boundingRect(s->label()); - if (centerAngle < 90 || centerAngle > 270) - p += QPointF(0, -labelRect.height()); - if (centerAngle < 180) - p += QPointF(labelRect.width(), 0); - else - p += QPointF(-labelRect.width(), 0); - - // 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; + bool exploded = s->isExploded(); + if (m_series->sizePolicy() & QPieSeries::PieSizePolicyReserveSpaceForExploding) + exploded = true; + + bool labelVisible = s->isLabelVisible(); + if (m_series->sizePolicy() & QPieSeries::PieSizePolicyReserveSpaceForLabels) + labelVisible = true; + + qreal centerAngle; + QPointF armStart; + QRectF sliceRect = PieSlice::slicePath(center, pieRadius, s->m_startAngle, s->m_angleSpan, exploded, s->explodeDistance(), ¢erAngle, &armStart).boundingRect(); + + if (labelVisible) { + QRectF textRect = PieSlice::labelTextRect(s->labelFont(), s->label()); + QPointF textStart; + QRectF armRect = PieSlice::labelArmPath(armStart, centerAngle, s->labelArmLength(), textRect.width(), &textStart).boundingRect(); + textRect.moveBottomLeft(textStart); + sliceRect = sliceRect.united(armRect); + sliceRect = sliceRect.united(textRect); + } + + + qreal dt = m_rect.top() - sliceRect.top(); + if (dt > delta) + delta = dt; + qreal dl = m_rect.left() - sliceRect.left(); + if (dl > delta) + delta = dl; + qreal dr = sliceRect.right() - m_rect.right(); + if (dr > delta) + delta = dr; + qreal db = sliceRect.bottom() - m_rect.bottom(); + if (db > delta) + delta = db; + + /* + if (s->label() == "Slice 5") { + m_debugRect = sliceRect; + qDebug() << "dt:" << dt << ", dl:" << dl << ", dr:" << dr << ", db:" << db << ", delta:" << delta; + } + */ } // shrink the pie rectangle so that everything outside it fits the base rectangle pieRect.adjust(delta, delta, -delta, -delta); + /* + // apply size factor (range 0.0 ... 1.0) + pieRect.setWidth(pieRect.width() * m_series->sizeFactor()); + pieRect.setHeight(pieRect.height() * m_series->sizeFactor()); + + // position the pie rectangle (again) + center = m_rect.center(); // default position is center + dx = pieRect.width() / 2; + dy = pieRect.height() / 2; + if (m_series->position() & QPieSeries::PiePositionLeft) + center.setX(m_rect.left() + dx); + if (m_series->position() & QPieSeries::PiePositionRight) + center.setX(m_rect.right() - dx); + if (m_series->position() & QPieSeries::PiePositionHCenter) + center.setX(m_rect.center().x()); + if (m_series->position() & QPieSeries::PiePositionTop) + center.setY(m_rect.top() + dy); + if (m_series->position() & QPieSeries::PiePositionBottom) + center.setY(m_rect.bottom() - dy); + if (m_series->position() & QPieSeries::PiePositionVCenter) + center.setY(m_rect.center().y()); + pieRect.moveCenter(center); + */ + // update slices if (m_pieRect != pieRect) { m_pieRect = pieRect; @@ -166,6 +186,8 @@ void PiePresenter::updateGeometry() s->update(); } } + + update(); } void PiePresenter::addSlice(QPieSlice* sliceData) diff --git a/src/piechart/piepresenter_p.h b/src/piechart/piepresenter_p.h index 9004a15..f238cfe 100644 --- a/src/piechart/piepresenter_p.h +++ b/src/piechart/piepresenter_p.h @@ -44,6 +44,7 @@ private: QPieSeries *m_series; QRectF m_rect; QRectF m_pieRect; + QRectF m_debugRect; }; QTCOMMERCIALCHART_END_NAMESPACE diff --git a/src/piechart/pieslice.cpp b/src/piechart/pieslice.cpp index 756783e..71feff9 100644 --- a/src/piechart/pieslice.cpp +++ b/src/piechart/pieslice.cpp @@ -1,5 +1,4 @@ #include "pieslice_p.h" -#include "pieslicelabel_p.h" #include "piepresenter_p.h" #include "qpieseries.h" #include "qpieslice.h" @@ -20,11 +19,11 @@ QPointF offset(qreal angle, qreal length) PieSlice::PieSlice(QGraphicsItem* parent) :QGraphicsObject(parent), - m_slicelabel(new PieSliceLabel(this)), m_startAngle(0), m_angleSpan(0), m_isExploded(false), - m_explodeDistance(0) + m_explodeDistance(0), + m_labelVisible(false) { setAcceptHoverEvents(true); setAcceptedMouseButtons(Qt::LeftButton); @@ -37,19 +36,31 @@ PieSlice::~PieSlice() QRectF PieSlice::boundingRect() const { - return m_path.boundingRect(); + return m_slicePath.boundingRect(); } QPainterPath PieSlice::shape() const { - return m_path; + return m_slicePath; } void PieSlice::paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget* /*widget*/) { + painter->save(); painter->setPen(m_pen); painter->setBrush(m_brush); - painter->drawPath(m_path); + painter->drawPath(m_slicePath); + painter->restore(); + + if (m_labelVisible) { + painter->save(); + painter->setPen(m_labelArmPen); + painter->drawPath(m_labelArmPath); + painter->restore(); + + painter->setFont(m_labelFont); + painter->drawText(m_labelTextRect.bottomLeft(), m_labelText); + } } void PieSlice::hoverEnterEvent(QGraphicsSceneHoverEvent* /*event*/) @@ -79,32 +90,22 @@ void PieSlice::updateGeometry() prepareGeometryChange(); - // calculate center angle - qreal centerAngle = m_startAngle + (m_angleSpan/2); + // update slice path + QPointF center = m_pieRect.center(); + qreal radius = m_pieRect.height() / 2; + qreal centerAngle; + QPointF armStart; + m_slicePath = slicePath(center, radius, m_startAngle, m_angleSpan, m_isExploded, m_explodeDistance, ¢erAngle, &armStart); - // adjust rect for exploding - QRectF rect = m_pieRect; - if (m_isExploded) { - qreal dx = qSin(centerAngle*(PI/180)) * m_explodeDistance; - qreal dy = -qCos(centerAngle*(PI/180)) * m_explodeDistance; - rect.translate(dx, dy); - } + // update text rect + m_labelTextRect = labelTextRect(m_labelFont, m_labelText); - // 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_startAngle + 90, -m_angleSpan); - path.closeSubpath(); - m_path = path; + // update label arm path + QPointF labelTextStart; + m_labelArmPath = labelArmPath(armStart, centerAngle, m_labelArmLength, m_labelTextRect.width(), &labelTextStart); - // update label position - qreal radius = rect.height() / 2; - QPointF edgeCenter = rect.center() + offset(centerAngle, radius + PIESLICE_LABEL_GAP); - m_slicelabel->setArmStartPoint(edgeCenter); - m_slicelabel->setArmAngle(centerAngle); - m_slicelabel->updateGeometry(); - m_slicelabel->update(); + // update text position + m_labelTextRect.moveBottomLeft(labelTextStart); //qDebug() << "PieSlice::updateGeometry" << m_slicelabel->text() << boundingRect() << m_angle << m_span; } @@ -116,20 +117,85 @@ void PieSlice::updateData(const QPieSlice* sliceData) m_startAngle = sliceData->startAngle(); m_angleSpan = sliceData->m_angleSpan; m_isExploded = sliceData->isExploded(); - m_explodeDistance = sliceData->explodeDistance(); // TODO: expose to public API + m_explodeDistance = sliceData->explodeDistance(); m_pen = sliceData->pen(); m_brush = sliceData->brush(); - m_slicelabel->setVisible(sliceData->isLabelVisible()); - m_slicelabel->setText(sliceData->label()); - m_slicelabel->setPen(sliceData->labelPen()); - m_slicelabel->setFont(sliceData->labelFont()); - m_slicelabel->setArmLength(sliceData->labelArmLength()); + m_labelVisible = sliceData->isLabelVisible(); + m_labelText = sliceData->label(); + m_labelFont = sliceData->labelFont(); + m_labelArmLength = sliceData->labelArmLength(); + m_labelArmPen = sliceData->labelPen(); updateGeometry(); update(); } +QPainterPath PieSlice::slicePath(QPointF center, qreal radius, qreal startAngle, qreal angleSpan, bool exploded, qreal explodeDistance, qreal* centerAngle, QPointF* armStart) +{ + // calculate center angle + *centerAngle = startAngle + (angleSpan/2); + + // calculate slice rectangle + QRectF rect(center.x()-radius, center.y()-radius, radius*2, radius*2); + + // adjust rect for exploding + if (exploded) { + qreal dx = qSin(*centerAngle*(PI/180)) * explodeDistance; + qreal dy = -qCos(*centerAngle*(PI/180)) * explodeDistance; + rect.translate(dx, dy); + } + + // 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, -startAngle + 90, -angleSpan); + path.closeSubpath(); + + // calculate label arm start point + *armStart = center; + if (exploded) + *armStart += offset(*centerAngle, explodeDistance + radius + PIESLICE_LABEL_GAP); + else + *armStart += offset(*centerAngle, radius + PIESLICE_LABEL_GAP); + + return path; +} + +QPainterPath PieSlice::labelArmPath(QPointF start, qreal angle, qreal length, qreal textWidth, QPointF* textStart) +{ + qreal dx = qSin(angle*(PI/180)) * length; + qreal dy = -qCos(angle*(PI/180)) * length; + QPointF parm1 = start + QPointF(dx, dy); + + QPointF parm2 = parm1; + if (angle < 180) { // arm swings the other way on the left side + parm2 += QPointF(textWidth, 0); + *textStart = parm1; + } + else { + parm2 += QPointF(-textWidth,0); + *textStart = parm2; + } + + // elevate the text position a bit so that it does not hit the line + *textStart += QPointF(0, -5); + + QPainterPath path; + path.moveTo(start); + path.lineTo(parm1); + path.lineTo(parm2); + + return path; +} + +QRectF PieSlice::labelTextRect(QFont font, QString text) +{ + QFontMetricsF fm(font); + return fm.boundingRect(text); +} + #include "moc_pieslice_p.cpp" QTCOMMERCIALCHART_END_NAMESPACE diff --git a/src/piechart/pieslice_p.h b/src/piechart/pieslice_p.h index f6623d6..378f052 100644 --- a/src/piechart/pieslice_p.h +++ b/src/piechart/pieslice_p.h @@ -43,22 +43,29 @@ public Q_SLOTS: void updateData(const QPieSlice *sliceData); public: - PieSliceLabel* label() { return m_slicelabel; } + static QPainterPath slicePath(QPointF center, qreal radius, qreal startAngle, qreal angleSpan, bool exploded, qreal explodeDistance, qreal* centerAngle, QPointF* armStart); + static QPainterPath labelArmPath(QPointF start, qreal angle, qreal length, qreal textWidth, QPointF* textStart); + static QRectF labelTextRect(QFont font, QString text); private: - PieSliceLabel* m_slicelabel; - QRectF m_pieRect; - QPainterPath m_path; + QPainterPath m_slicePath; qreal m_startAngle; qreal m_angleSpan; - bool m_isExploded; qreal m_explodeDistance; - + bool m_labelVisible; QPen m_pen; QBrush m_brush; + + QPainterPath m_labelArmPath; + qreal m_labelArmLength; + QPen m_labelArmPen; + + QRectF m_labelTextRect; + QFont m_labelFont; + QString m_labelText; }; QTCOMMERCIALCHART_END_NAMESPACE diff --git a/src/piechart/pieslicelabel.cpp b/src/piechart/pieslicelabel.cpp deleted file mode 100644 index 495566f..0000000 --- a/src/piechart/pieslicelabel.cpp +++ /dev/null @@ -1,72 +0,0 @@ -#include "pieslicelabel_p.h" -#include -#include -#include -#include - -QTCOMMERCIALCHART_BEGIN_NAMESPACE - -#define PI 3.14159265 - -PieSliceLabel::PieSliceLabel(QGraphicsItem* parent) - :QGraphicsItem(parent), - m_armAngle(0), - m_armLength(0) -{ - -} - -void PieSliceLabel::paint(QPainter *painter, const QStyleOptionGraphicsItem* /*option*/, QWidget* /*widget*/) -{ - painter->setPen(m_pen); - painter->drawPath(m_armPath); - - // TODO: do we need a pen for text? - painter->setFont(m_font); - painter->drawText(m_textRect.bottomLeft(), m_text); - - //qDebug() << "PieSliceLabel::paint" << m_text << m_textRect; -} - -void PieSliceLabel::updateGeometry() -{ - prepareGeometryChange(); - - // calculate text size - QFontMetricsF fm(m_font); - QRectF textRect = fm.boundingRect(m_text); - - // calculate path for arm and text start point - qreal dx = qSin(m_armAngle*(PI/180)) * m_armLength; - qreal dy = -qCos(m_armAngle*(PI/180)) * m_armLength; - QPointF parm1 = m_armStartPoint + QPointF(dx, dy); - - // calculate horizontal arm and text position - QPointF parm2 = parm1; - textRect.moveBottomLeft(parm1); - if (m_armAngle < 180) { // arm swings the other way on the left side - parm2 += QPointF(textRect.width(), 0); - } else { - parm2 += QPointF(-textRect.width(),0); - textRect.moveBottomLeft(parm2); - } - - // add a little offset to text so that it does not touch the arm - qreal yOffset = m_pen.widthF() ? m_pen.widthF() : 2; - textRect.translate(0, -yOffset); - - // update arm path - QPainterPath path; - path.moveTo(m_armStartPoint); - path.lineTo(parm1); - path.lineTo(parm2); - - // update paths & rects - m_armPath = path; - m_textRect = textRect; - m_rect = path.boundingRect().united(m_textRect); - - //qDebug() << "PieSliceLabel::updateGeometry" << m_text << m_armStartPoint << m_armLength << m_armAngle << m_textRect; -} - -QTCOMMERCIALCHART_END_NAMESPACE diff --git a/src/piechart/pieslicelabel_p.h b/src/piechart/pieslicelabel_p.h deleted file mode 100644 index 0ce3bea..0000000 --- a/src/piechart/pieslicelabel_p.h +++ /dev/null @@ -1,57 +0,0 @@ -#ifndef PIELABEL_H -#define PIELABEL_H - -#include "qchartglobal.h" -#include -#include -#include - -class QGraphicsTextItem; -QTCOMMERCIALCHART_BEGIN_NAMESPACE - -class PieSliceLabel : public QGraphicsItem -{ -public: - PieSliceLabel(QGraphicsItem* parent = 0); - ~PieSliceLabel() {}; - -public: // from QGraphicsItem - QRectF boundingRect() const { return m_rect; } - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); - -public: - void updateGeometry(); - - void setArmStartPoint(QPointF point) { m_armStartPoint = point; } - QPointF armStartPoint() const { return m_armStartPoint; } - - void setArmAngle(qreal angle) { m_armAngle = angle; } - qreal armAngle() const { return m_armAngle; } - - void setArmLength(qreal len) { m_armLength = len; } - qreal armLength() const { return m_armLength; } - - void setText(QString text) { m_text = text; } - QString text() const { return m_text; } - - void setPen(QPen pen) { m_pen = pen; } - QPen pen() const { return m_pen; } - - void setFont(QFont font) { m_font = font; } - QFont font() const { return m_font; } - -private: - QPointF m_armStartPoint; - qreal m_armAngle; - qreal m_armLength; - QString m_text; - QRectF m_rect; - QPainterPath m_armPath; - QRectF m_textRect; - QPen m_pen; - QFont m_font; -}; - -QTCOMMERCIALCHART_END_NAMESPACE - -#endif // PIELABEL_H diff --git a/src/piechart/qpieseries.cpp b/src/piechart/qpieseries.cpp index 8922cdb..5de0795 100644 --- a/src/piechart/qpieseries.cpp +++ b/src/piechart/qpieseries.cpp @@ -118,9 +118,10 @@ bool QPieSeries::ChangeSet::isEmpty() const QPieSeries::QPieSeries(QObject *parent) : QSeries(parent), m_sizeFactor(1.0), - m_position(PiePositionMaximized), + m_position(PiePositionCenter), + m_sizePolicy(PieSizePolicyMaximized), m_pieStartAngle(0), - m_pieAngleSpan(360) + m_pieEndAngle(360) { } @@ -295,6 +296,7 @@ qreal QPieSeries::sizeFactor() const */ void QPieSeries::setPosition(PiePosition position) { + // TODO: sanity check if (m_position != position) { m_position = position; emit positionChanged(); @@ -310,22 +312,55 @@ QPieSeries::PiePosition QPieSeries::position() const return m_position; } - /*! - Sets the \a startAngle and \a angleSpan of this series. + Sets the \a sizePolicy of the pie. + \sa PieSizePolicy, sizePolicy() +*/ +void QPieSeries::setSizePolicy(PieSizePolicy sizePolicy) +{ + // TODO: sanity check + if (m_sizePolicy != sizePolicy) { + m_sizePolicy = sizePolicy; + emit sizePolicyChanged(); + } +} - Full pie is 360 degrees where 0 degrees is at 12 a'clock. +/*! + Gets the size policy of the pie. + \sa PieSizePolicy, setSizePolicy() */ -void QPieSeries::setSpan(qreal startAngle, qreal angleSpan) +QPieSeries::PieSizePolicy QPieSeries::sizePolicy() const { - if (startAngle >= 0 && startAngle < 360 && - angleSpan > 0 && angleSpan <= 360) { + return m_sizePolicy; +} + + +void QPieSeries::setStartAngle(qreal startAngle) +{ + if (startAngle >= 0 && startAngle <= 360 && startAngle != m_pieStartAngle && startAngle <= m_pieEndAngle) { m_pieStartAngle = startAngle; - m_pieAngleSpan = angleSpan; updateDerivativeData(); } } +qreal QPieSeries::startAngle() const +{ + return m_pieStartAngle; +} + +void QPieSeries::setEndAngle(qreal endAngle) +{ + if (endAngle >= 0 && endAngle <= 360 && endAngle != m_pieEndAngle && endAngle >= m_pieStartAngle) { + m_pieEndAngle = endAngle; + updateDerivativeData(); + } +} + +qreal QPieSeries::endAngle() const +{ + return m_pieEndAngle; +} + /*! Sets the all the slice labels \a visible or invisible. @@ -449,6 +484,7 @@ void QPieSeries::updateDerivativeData() // update slice attributes qreal sliceAngle = m_pieStartAngle; + qreal pieSpan = m_pieEndAngle - m_pieStartAngle; foreach (QPieSlice* s, m_slices) { bool changed = false; @@ -459,7 +495,7 @@ void QPieSeries::updateDerivativeData() changed = true; } - qreal sliceSpan = m_pieAngleSpan * percentage; + qreal sliceSpan = pieSpan * percentage; if (s->m_angleSpan != sliceSpan) { s->m_angleSpan = sliceSpan; changed = true; diff --git a/src/piechart/qpieseries.h b/src/piechart/qpieseries.h index dff1be2..bc0f410 100644 --- a/src/piechart/qpieseries.h +++ b/src/piechart/qpieseries.h @@ -20,14 +20,28 @@ class QTCOMMERCIALCHART_EXPORT QPieSeries : public QSeries Q_OBJECT public: - enum PiePosition { - PiePositionMaximized = 0, - PiePositionTopLeft, - PiePositionTopRight, - PiePositionBottomLeft, - PiePositionBottomRight + + enum PiePositionFlag { + PiePositionLeft = 0x1, + PiePositionRight = 0x2, + PiePositionHCenter = 0x4, + PiePositionTop = 0x10, + PiePositionBottom = 0x20, + PiePositionVCenter = 0x40, + PiePositionCenter = PiePositionHCenter | PiePositionVCenter + }; + + Q_DECLARE_FLAGS(PiePosition, PiePositionFlag) + + enum PieSizePolicyFlag { + PieSizePolicyMaximized = 0, + PieSizePolicyReserveSpaceForLabels = 0x1, + PieSizePolicyReserveSpaceForExploding = 0x2, + PieSizePolicyReserveSpaceForAll = PieSizePolicyReserveSpaceForLabels | PieSizePolicyReserveSpaceForExploding }; + Q_DECLARE_FLAGS(PieSizePolicy, PieSizePolicyFlag) + class ChangeSet { public: @@ -58,27 +72,38 @@ public: // from QChartSeries QSeriesType type() const; public: - void replace(QList slices); - void add(QList slices); + + // slice setters void add(QPieSlice* slice); - QPieSlice* add(qreal value, QString name); - QPieSeries& operator << (QPieSlice* slice); + void add(QList slices); + void replace(QList slices); void remove(QPieSlice* slice); void clear(); - int count() const; + // sluce getters QList slices() const; - void setSizeFactor(qreal sizeFactor); - qreal sizeFactor() const; + // calculated data + int count() const; + qreal total() const; + + // pie customization void setPosition(PiePosition position); PiePosition position() const; - void setSpan(qreal startAngle, qreal angleSpan); + void setSizePolicy(PieSizePolicy policy); + PieSizePolicy sizePolicy() const; + void setSizeFactor(qreal sizeFactor); + qreal sizeFactor() const; + void setStartAngle(qreal startAngle); + qreal startAngle() const; + void setEndAngle(qreal endAngle); + qreal endAngle() const; + // convenience function + QPieSeries& operator << (QPieSlice* slice); + QPieSlice* add(qreal value, QString name); void setLabelsVisible(bool visible = true); - qreal total() const; - // TODO: find slices? // QList findByValue(qreal value); // ... @@ -88,15 +113,19 @@ public: // TODO: general graphics customization // setDrawStyle(2d|3d) - // setDropShadows(bool) + // setDropShadows Q_SIGNALS: + void changed(const QPieSeries::ChangeSet& changeSet); + void clicked(QPieSlice* slice); void hoverEnter(QPieSlice* slice); void hoverLeave(QPieSlice* slice); + void sizeFactorChanged(); void positionChanged(); + void sizePolicyChanged(); private Q_SLOTS: // TODO: should be private and not visible in the interface at all void sliceChanged(); @@ -117,9 +146,10 @@ private: QList m_slices; qreal m_sizeFactor; PiePosition m_position; + PieSizePolicy m_sizePolicy; qreal m_total; qreal m_pieStartAngle; - qreal m_pieAngleSpan; + qreal m_pieEndAngle; }; QTCOMMERCIALCHART_END_NAMESPACE diff --git a/src/piechart/qpieslice.h b/src/piechart/qpieslice.h index 009ede5..d97cd49 100644 --- a/src/piechart/qpieslice.h +++ b/src/piechart/qpieslice.h @@ -21,10 +21,15 @@ public: virtual ~QPieSlice(); // data + void setValue(qreal value); qreal value() const; + void setLabel(QString label); QString label() const; + void setLabelVisible(bool visible); bool isLabelVisible() const; + void setExploded(bool exploded); bool isExploded() const; + void setExplodeDistance(qreal distance); qreal explodeDistance() const; // generated data @@ -33,38 +38,27 @@ public: qreal endAngle() const; // customization + void setPen(QPen pen); QPen pen() const; + void setBrush(QBrush brush); QBrush brush() const; + void setLabelPen(QPen pen); QPen labelPen() const; + void setLabelFont(QFont font); QFont labelFont() const; + void setLabelArmLength(qreal len); qreal labelArmLength() const; + // TODO: label position in general + // setLabelFlags(inside|outside|labelArmOn|labelArmOff|???) + // setLabelOrientation(horizontal|vertical|same as slice center angle|???) + 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); - void setExplodeDistance(qreal distance); - - // customization - void setPen(QPen pen); - void setBrush(QBrush brush); - void setLabelFont(QFont font); - 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