##// END OF EJS Templates
Make pie fit better inside its given rectangle. Label texts still go outside. Needs a bit more work...
Jani Honkonen -
r289:46889b1144e0
parent child
Show More
@@ -26,9 +26,6 int main(int argc, char *argv[])
26 26 series->enableClickExplodes(true);
27 27 series->enableHoverHighlight(true);
28 28
29 foreach (QPieSlice*s, series->slices())
30 qDebug() << s->angle() << s->span() << s->percentage();
31
32 29 QChartView* chartView = new QChartView(&window);
33 30 chartView->setRenderHint(QPainter::Antialiasing);
34 31 chartView->addSeries(series);
@@ -2,8 +2,12
2 2 #include "piepresenter.h"
3 3 #include "pieslice.h"
4 4 #include "qpieslice.h"
5 #include "pieslicelabel.h"
6 #include "qpieseries.h"
7 #include <qmath.h>
5 8 #include <QDebug>
6 #include <QTime>
9 #include <QFontMetrics>
10
7 11
8 12 QTCOMMERCIALCHART_BEGIN_NAMESPACE
9 13
@@ -15,6 +19,12 PiePresenter::PiePresenter(QGraphicsItem *parent, QPieSeries *series)
15 19 connect(series, SIGNAL(changed(const QPieSeries::ChangeSet&)), this, SLOT(handleSeriesChanged(const QPieSeries::ChangeSet&)));
16 20 connect(series, SIGNAL(sizeFactorChanged()), this, SLOT(updateGeometry()));
17 21 connect(series, SIGNAL(positionChanged()), this, SLOT(updateGeometry()));
22
23 if (m_series->count()) {
24 QPieSeries::ChangeSet changeSet;
25 changeSet.appendAdded(m_series->m_slices);
26 handleSeriesChanged(changeSet);
27 }
18 28 }
19 29
20 30 PiePresenter::~PiePresenter()
@@ -22,7 +32,7 PiePresenter::~PiePresenter()
22 32 // slices deleted automatically through QGraphicsItem
23 33 }
24 34
25 void PiePresenter::paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *)
35 void PiePresenter::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
26 36 {
27 37 // TODO: paint shadows for all components
28 38 // - get paths from items & merge & offset and draw with shadow color?
@@ -35,19 +45,17 void PiePresenter::handleSeriesChanged(const QPieSeries::ChangeSet& changeSet)
35 45 //qDebug() << " changed: " << changeSet.changed();
36 46 //qDebug() << " removed: " << changeSet.removed();
37 47
38 // ignore changeset when there are no visual slices
39 // changeset might not be valid about the added slices
40 if (m_slices.count() == 0) {
41 foreach (QPieSlice* s, m_series->m_slices)
42 addSlice(s);
43 return;
44 }
48 foreach (QPieSlice* s, changeSet.added())
49 addSlice(s);
50
51 foreach (QPieSlice* s, changeSet.changed())
52 updateSlice(s);
45 53
46 54 foreach (QPieSlice* s, changeSet.removed())
47 55 deleteSlice(s);
48 56
49 foreach (QPieSlice* s, changeSet.added())
50 addSlice(s);
57 // every change possibly changes the actual pie size
58 updateGeometry();
51 59 }
52 60
53 61 void PiePresenter::handleDomainChanged(const Domain& domain)
@@ -58,15 +66,17 void PiePresenter::handleDomainChanged(const Domain& domain)
58 66 void PiePresenter::handleGeometryChanged(const QRectF& rect)
59 67 {
60 68 m_rect = rect;
69 prepareGeometryChange();
61 70 updateGeometry();
62 71 }
63 72
64 73 void PiePresenter::updateGeometry()
65 74 {
66 prepareGeometryChange();
75 if (!m_rect.isValid() || m_rect.isEmpty())
76 return;
67 77
78 // calculate maximum rectangle for pie
68 79 QRectF pieRect = m_rect;
69
70 80 if (pieRect.width() < pieRect.height()) {
71 81 pieRect.setWidth(pieRect.width() * m_series->sizeFactor());
72 82 pieRect.setHeight(pieRect.width());
@@ -77,6 +87,7 void PiePresenter::updateGeometry()
77 87 pieRect.moveCenter(m_rect.center());
78 88 }
79 89
90 // position the pie rectangle
80 91 switch (m_series->position()) {
81 92 case QPieSeries::PiePositionTopLeft: {
82 93 pieRect.setHeight(pieRect.height() / 2);
@@ -106,12 +117,43 void PiePresenter::updateGeometry()
106 117 break;
107 118 }
108 119
120 // calculate how much space we need around the pie rectangle (labels & exploding)
121 qreal delta = 0;
122 qreal pieRadius = pieRect.height() / 2;
123 foreach (QPieSlice* s, m_series->m_slices) {
124
125 // calculate the farthest point in the slice from the pie center
126 qreal centerAngle = s->angle() + (s->angleSpan() / 2);
127 qreal len = pieRadius + s->labelArmLength() + s->explodeDistance();
128 QPointF dp(qSin(centerAngle*(PI/180)) * len, -qCos(centerAngle*(PI/180)) * len);
129 QPointF p = pieRect.center() + dp;
130
131 // TODO: consider the label text
132
133 // calculate how much the radius must get smaller to fit that point in the base rectangle
134 qreal dt = m_rect.top() - p.y();
135 if (dt > delta) delta = dt;
136 qreal dl = m_rect.left() - p.x();
137 if (dl > delta) delta = dl;
138 qreal dr = p.x() - m_rect.right();
139 if (dr > delta) delta = dr;
140 qreal db = p.y() - m_rect.bottom();
141 if (db > delta) delta = db;
142
143 //if (!m_rect.contains(p)) qDebug() << s->label() << dt << dl << dr << db << "delta" << delta;
144 }
145
146 // shrink the pie rectangle so that everything outside it fits the base rectangle
147 pieRect.adjust(delta, delta, -delta, -delta);
148
149 // update slices
109 150 if (m_pieRect != pieRect) {
110 151 m_pieRect = pieRect;
111 //qDebug() << "PiePresenter::updateGeometry()" << m_pieRect;
152 //qDebug() << "PiePresenter::updateGeometry()" << m_rect << m_pieRect;
112 153 foreach (PieSlice* s, m_slices.values()) {
113 154 s->setPieRect(m_pieRect);
114 155 s->updateGeometry();
156 s->update();
115 157 }
116 158 }
117 159 }
@@ -121,8 +163,7 void PiePresenter::addSlice(QPieSlice* sliceData)
121 163 //qDebug() << "PiePresenter::addSlice()" << sliceData;
122 164
123 165 if (m_slices.keys().contains(sliceData)) {
124 //qWarning() << "PiePresenter::addSlice(): slice already exists!" << sliceData;
125 Q_ASSERT(0);
166 Q_ASSERT(0); // TODO: how to handle this nicely?
126 167 return;
127 168 }
128 169
@@ -135,22 +176,33 void PiePresenter::addSlice(QPieSlice* sliceData)
135 176 m_slices.insert(sliceData, slice);
136 177
137 178 // connect signals
138 connect(sliceData, SIGNAL(changed()), slice, SLOT(handleSliceDataChanged()));
139 179 connect(slice, SIGNAL(clicked()), sliceData, SIGNAL(clicked()));
140 180 connect(slice, SIGNAL(hoverEnter()), sliceData, SIGNAL(hoverEnter()));
141 181 connect(slice, SIGNAL(hoverLeave()), sliceData, SIGNAL(hoverLeave()));
142 182 }
143 183
184 void PiePresenter::updateSlice(QPieSlice* sliceData)
185 {
186 //qDebug() << "PiePresenter::updateSlice()" << sliceData;
187
188 if (!m_slices.contains(sliceData)) {
189 Q_ASSERT(0); // TODO: how to handle this nicely?
190 return;
191 }
192
193 m_slices[sliceData]->updateData(sliceData);
194 }
195
144 196 void PiePresenter::deleteSlice(QPieSlice* sliceData)
145 197 {
146 198 //qDebug() << "PiePresenter::deleteSlice()" << sliceData;
147 199
148 if (m_slices.contains(sliceData))
149 delete m_slices.take(sliceData);
150 else {
151 // nothing to remove
152 Q_ASSERT(0); // TODO: remove before release
200 if (!m_slices.contains(sliceData)) {
201 Q_ASSERT(0); // TODO: how to handle this nicely?
202 return;
153 203 }
204
205 delete m_slices.take(sliceData);
154 206 }
155 207
156 208 #include "moc_piepresenter.cpp"
@@ -9,6 +9,8 class QGraphicsItem;
9 9 QTCOMMERCIALCHART_BEGIN_NAMESPACE
10 10 class PieSlice;
11 11
12 #define PI 3.14159265 // TODO: is this defined in some header?
13
12 14 class PiePresenter : public QObject, public ChartItem
13 15 {
14 16 Q_OBJECT
@@ -33,6 +35,7 public Q_SLOTS:
33 35
34 36 private:
35 37 void addSlice(QPieSlice* sliceData);
38 void updateSlice(QPieSlice* sliceData);
36 39 void deleteSlice(QPieSlice* sliceData);
37 40
38 41 private:
@@ -11,9 +11,6
11 11
12 12 QTCOMMERCIALCHART_BEGIN_NAMESPACE
13 13
14 #define PI 3.14159265
15 #define EXPLODE_OFFSET 20
16
17 14 QPointF offset(qreal angle, qreal length)
18 15 {
19 16 qreal dx = qSin(angle*(PI/180)) * length;
@@ -25,8 +22,9 PieSlice::PieSlice(QGraphicsItem* parent)
25 22 :QGraphicsObject(parent),
26 23 m_slicelabel(new PieSliceLabel(this)),
27 24 m_angle(0),
28 m_span(0),
29 m_isExploded(false)
25 m_angleSpan(0),
26 m_isExploded(false),
27 m_explodeDistance(0)
30 28 {
31 29 setAcceptHoverEvents(true);
32 30 setAcceptedMouseButtons(Qt::LeftButton);
@@ -76,24 +74,27 void PieSlice::setPieRect(QRectF rect)
76 74
77 75 void PieSlice::updateGeometry()
78 76 {
77 if (!m_pieRect.isValid() || m_pieRect.isEmpty())
78 return;
79
79 80 prepareGeometryChange();
80 81
81 82 // calculate center angle
82 qreal centerAngle = m_angle + (m_span/2);
83 qreal centerAngle = m_angle + (m_angleSpan/2);
83 84
84 85 // adjust rect for exploding
85 86 QRectF rect = m_pieRect;
86 rect.adjust(EXPLODE_OFFSET, EXPLODE_OFFSET, -EXPLODE_OFFSET ,-EXPLODE_OFFSET);
87 87 if (m_isExploded) {
88 QPointF d = offset((centerAngle), EXPLODE_OFFSET);
89 rect.translate(d.x(), d.y());
88 qreal dx = qSin(centerAngle*(PI/180)) * m_explodeDistance;
89 qreal dy = -qCos(centerAngle*(PI/180)) * m_explodeDistance;
90 rect.translate(dx, dy);
90 91 }
91 92
92 93 // update slice path
93 94 // TODO: draw the shape so that it might have a hole in the center
94 95 QPainterPath path;
95 96 path.moveTo(rect.center());
96 path.arcTo(rect, -m_angle + 90, -m_span);
97 path.arcTo(rect, -m_angle + 90, -m_angleSpan);
97 98 path.closeSubpath();
98 99 m_path = path;
99 100
@@ -103,24 +104,19 void PieSlice::updateGeometry()
103 104 m_slicelabel->setArmStartPoint(edgeCenter);
104 105 m_slicelabel->setArmAngle(centerAngle);
105 106 m_slicelabel->updateGeometry();
107 m_slicelabel->update();
106 108
107 109 //qDebug() << "PieSlice::updateGeometry" << m_slicelabel->text() << boundingRect() << m_angle << m_span;
108 110 }
109 111
110 void PieSlice::handleSliceDataChanged()
111 {
112 QPieSlice *slice = qobject_cast<QPieSlice*>(sender());
113 Q_ASSERT(slice);
114 updateData(slice);
115 }
116
117 112 void PieSlice::updateData(const QPieSlice* sliceData)
118 113 {
119 114 // TODO: compare what has changes to avoid unneccesary geometry updates
120 115
121 116 m_angle = sliceData->angle();
122 m_span = sliceData->span();
117 m_angleSpan = sliceData->angleSpan();
123 118 m_isExploded = sliceData->isExploded();
119 m_explodeDistance = sliceData->explodeDistance(); // TODO: expose to public API
124 120 m_pen = sliceData->pen();
125 121 m_brush = sliceData->brush();
126 122
@@ -128,11 +124,10 void PieSlice::updateData(const QPieSlice* sliceData)
128 124 m_slicelabel->setText(sliceData->label());
129 125 m_slicelabel->setPen(sliceData->labelPen());
130 126 m_slicelabel->setFont(sliceData->labelFont());
131 m_slicelabel->setArmLength(sliceData->labelArmLenght());
127 m_slicelabel->setArmLength(sliceData->labelArmLength());
132 128
133 129 updateGeometry();
134 130 update();
135 m_slicelabel->update();
136 131 }
137 132
138 133 #include "moc_pieslice.cpp"
@@ -36,11 +36,13 Q_SIGNALS:
36 36 void hoverLeave();
37 37
38 38 public Q_SLOTS:
39 void handleSliceDataChanged();
40 39 void setPieRect(QRectF rect);
41 40 void updateGeometry();
42 41 void updateData(const QPieSlice *sliceData);
43 42
43 public:
44 PieSliceLabel* label() { return m_slicelabel; }
45
44 46 private:
45 47 PieSliceLabel* m_slicelabel;
46 48
@@ -48,8 +50,10 private:
48 50 QPainterPath m_path;
49 51
50 52 qreal m_angle;
51 qreal m_span;
53 qreal m_angleSpan;
54
52 55 bool m_isExploded;
56 qreal m_explodeDistance;
53 57
54 58 QPen m_pen;
55 59 QBrush m_brush;
@@ -2,6 +2,7
2 2 #include "qpieslice.h"
3 3 #include "piepresenter.h"
4 4 #include "pieslice.h"
5 #include <QFontMetrics>
5 6 #include <QDebug>
6 7
7 8 QTCOMMERCIALCHART_BEGIN_NAMESPACE
@@ -12,6 +13,12 void QPieSeries::ChangeSet::appendAdded(QPieSlice* slice)
12 13 m_added << slice;
13 14 }
14 15
16 void QPieSeries::ChangeSet::appendAdded(QList<QPieSlice*> slices)
17 {
18 foreach (QPieSlice* s, slices)
19 appendAdded(s);
20 }
21
15 22 void QPieSeries::ChangeSet::appendChanged(QPieSlice* slice)
16 23 {
17 24 if (!m_changed.contains(slice))
@@ -114,7 +121,7 QPieSlice* QPieSeries::add(qreal value, QString name)
114 121 void QPieSeries::remove(QPieSlice* slice)
115 122 {
116 123 if (!m_slices.removeOne(slice)) {
117 Q_ASSERT(0); // TODO: remove before release
124 Q_ASSERT(0); // TODO: how should this be reported?
118 125 return;
119 126 }
120 127
@@ -263,7 +270,7 void QPieSeries::updateDerivativeData()
263 270 m_total += s->value();
264 271
265 272 // we must have some values
266 Q_ASSERT(m_total > 0); // TODO
273 Q_ASSERT(m_total > 0); // TODO: is this the correct way to handle this?
267 274
268 275 // update slice attributes
269 276 qreal sliceAngle = m_pieStartAngle;
@@ -278,8 +285,8 void QPieSeries::updateDerivativeData()
278 285 }
279 286
280 287 qreal sliceSpan = m_pieSpan * percentage;
281 if (s->m_span != sliceSpan) {
282 s->m_span = sliceSpan;
288 if (s->m_angleSpan != sliceSpan) {
289 s->m_angleSpan = sliceSpan;
283 290 changed = true;
284 291 }
285 292
@@ -32,6 +32,7 public:
32 32 {
33 33 public:
34 34 void appendAdded(QPieSlice* slice);
35 void appendAdded(QList<QPieSlice*> slices);
35 36 void appendChanged(QPieSlice* slice);
36 37 void appendRemoved(QPieSlice* slice);
37 38
@@ -67,13 +68,6 public:
67 68
68 69 QList<QPieSlice*> slices() const { return m_slices; }
69 70
70 // TODO: find slices?
71 // QList<QPieSlice*> findByValue(qreal value);
72 // ...
73
74 // TODO: sorting slices?
75 // void sort(QPieSeries::SortByValue)
76
77 71 void setSizeFactor(qreal sizeFactor);
78 72 qreal sizeFactor() const { return m_sizeFactor; }
79 73
@@ -86,6 +80,17 public:
86 80 void enableClickExplodes(bool enable);
87 81 void enableHoverHighlight(bool enable);
88 82
83 // TODO: find slices?
84 // QList<QPieSlice*> findByValue(qreal value);
85 // ...
86
87 // TODO: sorting slices?
88 // void sort(QPieSeries::SortByValue|label|??)
89
90 // TODO: general graphics customization
91 // setDrawStyle(2d|3d)
92 // setDropShadows(bool)
93
89 94 Q_SIGNALS:
90 95 void changed(const QPieSeries::ChangeSet& changeSet);
91 96 void clicked(QPieSlice* slice);
@@ -94,12 +99,11 Q_SIGNALS:
94 99 void sizeFactorChanged();
95 100 void positionChanged();
96 101
97 private Q_SLOTS: // should be private and not in the interface
102 private Q_SLOTS: // TODO: should be private and not visible in the interface at all
98 103 void sliceChanged();
99 104 void sliceClicked();
100 105 void sliceHoverEnter();
101 106 void sliceHoverLeave();
102
103 107 void toggleExploded(QPieSlice* slice);
104 108 void highlightOn(QPieSlice* slice);
105 109 void highlightOff(QPieSlice* slice);
@@ -2,18 +2,20
2 2
3 3 QTCOMMERCIALCHART_BEGIN_NAMESPACE
4 4
5 #define DEFAULT_PEN_COLOR Qt::black
6 #define DEFAULT_BRUSH_COLOR Qt::white
7 #define DEFAULT_LABEL_ARM_LENGTH 50
5 #define DEFAULT_PEN_COLOR Qt::black
6 #define DEFAULT_BRUSH_COLOR Qt::white
7 #define DEFAULT_LABEL_ARM_LENGTH 50
8 #define DEFAULT_EXPOLODE_DISTANCE 20
8 9
9 10 QPieSlice::QPieSlice(QObject *parent)
10 11 :QObject(parent),
11 12 m_value(0),
12 13 m_isLabelVisible(true),
13 14 m_isExploded(false),
15 m_explodeDistance(DEFAULT_EXPOLODE_DISTANCE),
14 16 m_percentage(0),
15 17 m_angle(0),
16 m_span(0),
18 m_angleSpan(0),
17 19 m_pen(DEFAULT_PEN_COLOR),
18 20 m_brush(DEFAULT_BRUSH_COLOR),
19 21 m_labelPen(DEFAULT_PEN_COLOR),
@@ -28,9 +30,10 QPieSlice::QPieSlice(qreal value, QString label, bool labelVisible, QObject *par
28 30 m_label(label),
29 31 m_isLabelVisible(labelVisible),
30 32 m_isExploded(false),
33 m_explodeDistance(DEFAULT_EXPOLODE_DISTANCE),
31 34 m_percentage(0),
32 35 m_angle(0),
33 m_span(0),
36 m_angleSpan(0),
34 37 m_pen(DEFAULT_PEN_COLOR),
35 38 m_brush(DEFAULT_BRUSH_COLOR),
36 39 m_labelPen(DEFAULT_PEN_COLOR),
@@ -64,6 +67,11 bool QPieSlice::isExploded() const
64 67 return m_isExploded;
65 68 }
66 69
70 qreal QPieSlice::explodeDistance() const
71 {
72 return m_explodeDistance;
73 }
74
67 75 qreal QPieSlice::percentage() const
68 76 {
69 77 return m_percentage;
@@ -74,9 +82,9 qreal QPieSlice::angle() const
74 82 return m_angle;
75 83 }
76 84
77 qreal QPieSlice::span() const
85 qreal QPieSlice::angleSpan() const
78 86 {
79 return m_span;
87 return m_angleSpan;
80 88 }
81 89
82 90 QPen QPieSlice::pen() const
@@ -99,7 +107,7 QFont QPieSlice::labelFont() const
99 107 return m_labelFont;
100 108 }
101 109
102 qreal QPieSlice::labelArmLenght() const
110 qreal QPieSlice::labelArmLength() const
103 111 {
104 112 return m_labelArmLength;
105 113 }
@@ -136,6 +144,14 void QPieSlice::setExploded(bool exploded)
136 144 }
137 145 }
138 146
147 void QPieSlice::setExplodeDistance(qreal distance)
148 {
149 if (m_explodeDistance != distance) {
150 m_explodeDistance = distance;
151 emit changed();
152 }
153 }
154
139 155 void QPieSlice::setPen(QPen pen)
140 156 {
141 157 if (m_pen != pen) {
@@ -25,18 +25,19 public:
25 25 QString label() const;
26 26 bool isLabelVisible() const;
27 27 bool isExploded() const;
28 qreal explodeDistance() const;
28 29
29 30 // generated data
30 31 qreal percentage() const;
31 32 qreal angle() const;
32 qreal span() const;
33 qreal angleSpan() const;
33 34
34 35 // customization
35 36 QPen pen() const;
36 37 QBrush brush() const;
37 38 QPen labelPen() const;
38 39 QFont labelFont() const;
39 qreal labelArmLenght() const;
40 qreal labelArmLength() const;
40 41
41 42 Q_SIGNALS:
42 43 void clicked();
@@ -51,6 +52,7 public Q_SLOTS:
51 52 void setLabelVisible(bool visible);
52 53 void setValue(qreal value);
53 54 void setExploded(bool exploded);
55 void setExplodeDistance(qreal distance);
54 56
55 57 // customization
56 58 void setPen(QPen pen);
@@ -59,21 +61,27 public Q_SLOTS:
59 61 void setLabelPen(QPen pen);
60 62 void setLabelArmLength(qreal len);
61 63
64 // TODO: label position in general
65 // setLabelFlags(inside|outside|labelArmOn|labelArmOff|???)
66 // setLabelOrientation(horizontal|vertical|same as slice center angle|???)
67
62 68 private:
63 69
64 70 // TODO: use private class
65 71 friend class QPieSeries;
72 friend class PiePresenter;
66 73
67 74 // data
68 75 qreal m_value;
69 76 QString m_label;
70 77 bool m_isLabelVisible;
71 78 bool m_isExploded;
79 qreal m_explodeDistance;
72 80
73 81 // generated data
74 82 qreal m_percentage;
75 83 qreal m_angle;
76 qreal m_span;
84 qreal m_angleSpan;
77 85
78 86 // customization
79 87 QPen m_pen;
General Comments 0
You need to be logged in to leave comments. Login now