##// END OF EJS Templates
Add HTML support for pie slice labels...
Titta Heikkala -
r2626:ec2eee97ed6a
parent child
Show More
@@ -1,300 +1,305
1 1 /****************************************************************************
2 2 **
3 3 ** Copyright (C) 2013 Digia Plc
4 4 ** All rights reserved.
5 5 ** For any questions to Digia, please use contact form at http://qt.digia.com
6 6 **
7 7 ** This file is part of the Qt Enterprise Charts Add-on.
8 8 **
9 9 ** $QT_BEGIN_LICENSE$
10 10 ** Licensees holding valid Qt Enterprise licenses may use this file in
11 11 ** accordance with the Qt Enterprise License Agreement provided with the
12 12 ** Software or, alternatively, in accordance with the terms contained in
13 13 ** a written agreement between you and Digia.
14 14 **
15 15 ** If you have questions regarding the use of this file, please use
16 16 ** contact form at http://qt.digia.com
17 17 ** $QT_END_LICENSE$
18 18 **
19 19 ****************************************************************************/
20 20
21 21 #include "piesliceitem_p.h"
22 22 #include "piechartitem_p.h"
23 23 #include "qpieseries.h"
24 24 #include "qpieslice.h"
25 25 #include "chartpresenter_p.h"
26 26 #include <QPainter>
27 27 #include <qmath.h>
28 28 #include <QGraphicsSceneEvent>
29 29 #include <QTime>
30 #include <QTextDocument>
30 31 #include <QDebug>
31 32
32 33 QTCOMMERCIALCHART_BEGIN_NAMESPACE
33 34
34 35 QPointF offset(qreal angle, qreal length)
35 36 {
36 37 qreal dx = qSin(angle * (M_PI / 180)) * length;
37 38 qreal dy = qCos(angle * (M_PI / 180)) * length;
38 39 return QPointF(dx, -dy);
39 40 }
40 41
41 42 PieSliceItem::PieSliceItem(QGraphicsItem *parent)
42 43 : QGraphicsObject(parent),
43 44 m_hovered(false)
44 45 {
45 46 setAcceptHoverEvents(true);
46 47 setAcceptedMouseButtons(Qt::MouseButtonMask);
47 48 setZValue(ChartPresenter::PieSeriesZValue);
49 m_labelItem = new QGraphicsTextItem(this);
50 m_labelItem->document()->setDocumentMargin(1.0);
48 51 }
49 52
50 53 PieSliceItem::~PieSliceItem()
51 54 {
52 55 // If user is hovering over the slice and it gets destroyed we do
53 56 // not get a hover leave event. So we must emit the signal here.
54 57 if (m_hovered)
55 58 emit hovered(false);
56 59 }
57 60
58 61 QRectF PieSliceItem::boundingRect() const
59 62 {
60 63 return m_boundingRect;
61 64 }
62 65
63 66 QPainterPath PieSliceItem::shape() const
64 67 {
65 68 // Don't include the label and label arm.
66 69 // This is used to detect a mouse clicks. We do not want clicks from label.
67 70 return m_slicePath;
68 71 }
69 72
70 73 void PieSliceItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
71 74 {
72 75 painter->save();
73 76 painter->setClipRect(parentItem()->boundingRect());
74 77 painter->setPen(m_data.m_slicePen);
75 78 painter->setBrush(m_data.m_sliceBrush);
76 79 painter->drawPath(m_slicePath);
77 80 painter->restore();
78 81
79 82 if (m_data.m_isLabelVisible) {
80 83 painter->save();
81 84
82 85 // Pen for label arm not defined in the QPieSeries api, let's use brush's color instead
83 // Also, the drawText actually uses the pen color for the text color (unlike QGraphicsSimpleTextItem)
84 painter->setPen(m_data.m_labelBrush.color());
85 86 painter->setBrush(m_data.m_labelBrush);
86 painter->setFont(m_data.m_labelFont);
87 87
88 QFontMetricsF fm(m_data.m_labelFont);
89 QString label = m_data.m_labelText;
90 QRectF labelBoundingRect;
91
92 switch (m_data.m_labelPosition) {
93 case QPieSlice::LabelOutside:
88 if (m_data.m_labelPosition == QPieSlice::LabelOutside) {
94 89 painter->setClipRect(parentItem()->boundingRect());
95 90 painter->strokePath(m_labelArmPath, m_data.m_labelBrush.color());
96 if (fm.width(m_data.m_labelText) > m_labelTextRect.width()) {
97 // Only one line label text is supported currently.
98 // The height for the label is set one pixel over the font metrics.
99 label = ChartPresenter::truncatedText(m_data.m_labelFont, m_data.m_labelText,
100 qreal(0.0), m_labelTextRect.width(),
101 fm.height() + 1.0, labelBoundingRect);
102 }
103 painter->drawText(m_labelTextRect, Qt::AlignCenter, label);
104 break;
105 case QPieSlice::LabelInsideHorizontal:
106 painter->setClipPath(m_slicePath);
107 painter->drawText(m_labelTextRect, Qt::AlignCenter, m_data.m_labelText);
108 break;
109 case QPieSlice::LabelInsideTangential:
110 painter->setClipPath(m_slicePath);
111 painter->translate(m_labelTextRect.center());
112 painter->rotate(m_data.m_startAngle + m_data.m_angleSpan / 2);
113 painter->drawText(-m_labelTextRect.width() / 2, -m_labelTextRect.height() / 2, m_labelTextRect.width(), m_labelTextRect.height(), Qt::AlignCenter, m_data.m_labelText);
114 break;
115 case QPieSlice::LabelInsideNormal:
116 painter->setClipPath(m_slicePath);
117 painter->translate(m_labelTextRect.center());
118 if (m_data.m_startAngle + m_data.m_angleSpan / 2 < 180)
119 painter->rotate(m_data.m_startAngle + m_data.m_angleSpan / 2 - 90);
120 else
121 painter->rotate(m_data.m_startAngle + m_data.m_angleSpan / 2 + 90);
122 painter->drawText(-m_labelTextRect.width() / 2, -m_labelTextRect.height() / 2, m_labelTextRect.width(), m_labelTextRect.height(), Qt::AlignCenter, m_data.m_labelText);
123 break;
124 91 }
125 92
126 93 painter->restore();
127 94 }
128 95 }
129 96
130 97 void PieSliceItem::hoverEnterEvent(QGraphicsSceneHoverEvent * /*event*/)
131 98 {
132 99 m_hovered = true;
133 100 emit hovered(true);
134 101 }
135 102
136 103 void PieSliceItem::hoverLeaveEvent(QGraphicsSceneHoverEvent * /*event*/)
137 104 {
138 105 m_hovered = false;
139 106 emit hovered(false);
140 107 }
141 108
142 109 void PieSliceItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
143 110 {
144 111 emit clicked(event->buttons());
145 112 QGraphicsItem::mousePressEvent(event);
146 113 }
147 114
148 115 void PieSliceItem::setLayout(const PieSliceData &sliceData)
149 116 {
150 117 m_data = sliceData;
151 118 updateGeometry();
152 119 update();
153 120 }
154 121
155 122 void PieSliceItem::updateGeometry()
156 123 {
157 124 if (m_data.m_radius <= 0)
158 125 return;
159 126
160 127 prepareGeometryChange();
161 128
162 129 // slice path
163 130 qreal centerAngle;
164 131 QPointF armStart;
165 132 m_slicePath = slicePath(m_data.m_center, m_data.m_radius, m_data.m_startAngle, m_data.m_angleSpan, &centerAngle, &armStart);
166 133
167 // text rect
168 QFontMetricsF fm(m_data.m_labelFont);
169 m_labelTextRect = QRectF(0, 0, fm.width(m_data.m_labelText), fm.height());
170
171 // label arm path
172 QPointF labelTextStart;
173 m_labelArmPath = labelArmPath(armStart, centerAngle, m_data.m_radius * m_data.m_labelArmLengthFactor, m_labelTextRect.width(), &labelTextStart);
174
175 // text position
176 switch (m_data.m_labelPosition) {
177 case QPieSlice::LabelOutside:
178 m_labelTextRect.moveBottomLeft(labelTextStart);
179 if (m_labelTextRect.left() < 0)
180 m_labelTextRect.setLeft(0);
181 if (m_labelTextRect.right() > parentItem()->boundingRect().right())
182 m_labelTextRect.setRight(parentItem()->boundingRect().right());
183 break;
184 case QPieSlice::LabelInsideHorizontal:
185 case QPieSlice::LabelInsideTangential: {
186 QPointF textCenter;
187 if (m_data.m_holeRadius > 0)
188 textCenter = m_data.m_center + offset(centerAngle, m_data.m_holeRadius + (m_data.m_radius - m_data.m_holeRadius) / 2);
189 else
190 textCenter = m_data.m_center + offset(centerAngle, m_data.m_radius / 2);
191 m_labelTextRect.moveCenter(textCenter);
192 break;
193 }
194 case QPieSlice::LabelInsideNormal: {
195 QPointF textCenter;
196 if (m_data.m_holeRadius > 0)
197 textCenter = m_data.m_center + offset(centerAngle, m_data.m_holeRadius + (m_data.m_radius - m_data.m_holeRadius) / 2);
198 else
199 textCenter = m_data.m_center + offset(centerAngle, m_data.m_radius / 2);
200 m_labelTextRect.moveCenter(textCenter);
201 break;
202 }
134 if (m_data.m_isLabelVisible) {
135 // text rect
136 QFontMetricsF fm(m_data.m_labelFont);
137 m_labelTextRect = ChartPresenter::textBoundingRect(m_data.m_labelFont,
138 m_data.m_labelText,
139 0);
140
141 QString label(m_data.m_labelText);
142 m_labelItem->setVisible(m_data.m_isLabelVisible);
143 m_labelItem->setDefaultTextColor(m_data.m_labelBrush.color());
144 m_labelItem->setFont(m_data.m_labelFont);
145
146 // text position
147 if (m_data.m_labelPosition == QPieSlice::LabelOutside) {
148 setFlag(QGraphicsItem::ItemClipsChildrenToShape, false);
149
150 // label arm path
151 QPointF labelTextStart;
152 m_labelArmPath = labelArmPath(armStart, centerAngle,
153 m_data.m_radius * m_data.m_labelArmLengthFactor,
154 m_labelTextRect.width(), &labelTextStart);
155
156 m_labelTextRect.moveBottomLeft(labelTextStart);
157 if (m_labelTextRect.left() < 0)
158 m_labelTextRect.setLeft(0);
159 else if (m_labelTextRect.left() < parentItem()->boundingRect().left())
160 m_labelTextRect.setLeft(parentItem()->boundingRect().left());
161 if (m_labelTextRect.right() > parentItem()->boundingRect().right())
162 m_labelTextRect.setRight(parentItem()->boundingRect().right());
163
164 if (fm.width(m_data.m_labelText) > m_labelTextRect.width()) {
165 label = ChartPresenter::truncatedText(m_data.m_labelFont, m_data.m_labelText,
166 qreal(0.0), m_labelTextRect.width(),
167 m_labelTextRect.height(), m_labelTextRect);
168 m_labelArmPath = labelArmPath(armStart, centerAngle,
169 m_data.m_radius * m_data.m_labelArmLengthFactor,
170 m_labelTextRect.width(), &labelTextStart);
171
172 m_labelTextRect.moveBottomLeft(labelTextStart);
173 }
174 m_labelItem->setTextWidth(m_labelTextRect.width()
175 + m_labelItem->document()->documentMargin());
176 m_labelItem->setHtml(label);
177 m_labelItem->setPos(m_labelTextRect.x(), m_labelTextRect.y() + 1.0);
178 } else {
179 // label inside
180 setFlag(QGraphicsItem::ItemClipsChildrenToShape);
181 m_labelItem->setTextWidth(m_labelTextRect.width()
182 + m_labelItem->document()->documentMargin());
183 m_labelItem->setHtml(label);
184
185 QPointF textCenter;
186 if (m_data.m_holeRadius > 0) {
187 textCenter = m_data.m_center + offset(centerAngle, m_data.m_holeRadius
188 + (m_data.m_radius
189 - m_data.m_holeRadius) / 2);
190 } else {
191 textCenter = m_data.m_center + offset(centerAngle, m_data.m_radius / 2);
192 }
193 m_labelItem->setPos(textCenter.x() - m_labelItem->boundingRect().width() / 2,
194 textCenter.y() - fm.height() / 2);
195
196 QPointF labelCenter = m_labelItem->boundingRect().center();
197 m_labelItem->setTransformOriginPoint(labelCenter);
198
199 if (m_data.m_labelPosition == QPieSlice::LabelInsideTangential) {
200 m_labelItem->setRotation(m_data.m_startAngle + m_data.m_angleSpan / 2);
201 } else if (m_data.m_labelPosition == QPieSlice::LabelInsideNormal) {
202 if (m_data.m_startAngle + m_data.m_angleSpan / 2 < 180)
203 m_labelItem->setRotation(m_data.m_startAngle + m_data.m_angleSpan / 2 - 90);
204 else
205 m_labelItem->setRotation(m_data.m_startAngle + m_data.m_angleSpan / 2 + 90);
206 }
207 }
203 208 }
204 209
205 210 // bounding rect
206 211 if (m_data.m_isLabelVisible)
207 212 m_boundingRect = m_slicePath.boundingRect().united(m_labelArmPath.boundingRect()).united(m_labelTextRect);
208 213 else
209 214 m_boundingRect = m_slicePath.boundingRect();
210 215
211 216 // Inflate bounding rect by 2/3 pen width to make sure it encompasses whole slice also for thick pens
212 217 // and miter joins.
213 218 int penWidth = (m_data.m_slicePen.width() * 2) / 3;
214 219 m_boundingRect = m_boundingRect.adjusted(-penWidth, -penWidth, penWidth, penWidth);
215 220 }
216 221
217 222 QPointF PieSliceItem::sliceCenter(QPointF point, qreal radius, QPieSlice *slice)
218 223 {
219 224 if (slice->isExploded()) {
220 225 qreal centerAngle = slice->startAngle() + (slice->angleSpan() / 2);
221 226 qreal len = radius * slice->explodeDistanceFactor();
222 227 point += offset(centerAngle, len);
223 228 }
224 229 return point;
225 230 }
226 231
227 232 QPainterPath PieSliceItem::slicePath(QPointF center, qreal radius, qreal startAngle, qreal angleSpan, qreal *centerAngle, QPointF *armStart)
228 233 {
229 234 // calculate center angle
230 235 *centerAngle = startAngle + (angleSpan / 2);
231 236
232 237 // calculate slice rectangle
233 238 QRectF rect(center.x() - radius, center.y() - radius, radius * 2, radius * 2);
234 239
235 240 // slice path
236 241 QPainterPath path;
237 242 if (m_data.m_holeRadius > 0) {
238 243 QRectF insideRect(center.x() - m_data.m_holeRadius, center.y() - m_data.m_holeRadius, m_data.m_holeRadius * 2, m_data.m_holeRadius * 2);
239 244 path.arcMoveTo(rect, -startAngle + 90);
240 245 path.arcTo(rect, -startAngle + 90, -angleSpan);
241 246 path.arcTo(insideRect, -startAngle + 90 - angleSpan, angleSpan);
242 247 path.closeSubpath();
243 248 } else {
244 249 path.moveTo(rect.center());
245 250 path.arcTo(rect, -startAngle + 90, -angleSpan);
246 251 path.closeSubpath();
247 252 }
248 253
249 254 // calculate label arm start point
250 255 *armStart = center;
251 256 *armStart += offset(*centerAngle, radius + PIESLICE_LABEL_GAP);
252 257
253 258 return path;
254 259 }
255 260
256 261 QPainterPath PieSliceItem::labelArmPath(QPointF start, qreal angle, qreal length, qreal textWidth, QPointF *textStart)
257 262 {
258 263 // Normalize the angle to 0-360 range
259 264 // NOTE: We are using int here on purpose. Depenging on platform and hardware
260 265 // qreal can be a double, float or something the user gives to the Qt configure
261 266 // (QT_COORD_TYPE). Compilers do not seem to support modulo for double or float
262 267 // but there are fmod() and fmodf() functions for that. So instead of some #ifdef
263 268 // that might break we just use int. Precision for this is just fine for our needs.
264 269 int normalized = angle * 10.0;
265 270 normalized = normalized % 3600;
266 271 if (normalized < 0)
267 272 normalized += 3600;
268 273 angle = (qreal) normalized / 10.0;
269 274
270 275 // prevent label arm pointing straight down because it will look bad
271 276 if (angle < 180 && angle > 170)
272 277 angle = 170;
273 278 if (angle > 180 && angle < 190)
274 279 angle = 190;
275 280
276 281 // line from slice to label
277 282 QPointF parm1 = start + offset(angle, length);
278 283
279 284 // line to underline the label
280 285 QPointF parm2 = parm1;
281 286 if (angle < 180) { // arm swings the other way on the left side
282 287 parm2 += QPointF(textWidth, 0);
283 288 *textStart = parm1;
284 289 } else {
285 290 parm2 += QPointF(-textWidth, 0);
286 291 *textStart = parm2;
287 292 }
288 293
289 294 QPainterPath path;
290 295 path.moveTo(start);
291 296 path.lineTo(parm1);
292 297 path.lineTo(parm2);
293 298
294 299 return path;
295 300 }
296 301
297 302 #include "moc_piesliceitem_p.cpp"
298 303
299 304 QTCOMMERCIALCHART_END_NAMESPACE
300 305
@@ -1,90 +1,91
1 1 /****************************************************************************
2 2 **
3 3 ** Copyright (C) 2013 Digia Plc
4 4 ** All rights reserved.
5 5 ** For any questions to Digia, please use contact form at http://qt.digia.com
6 6 **
7 7 ** This file is part of the Qt Enterprise Charts Add-on.
8 8 **
9 9 ** $QT_BEGIN_LICENSE$
10 10 ** Licensees holding valid Qt Enterprise licenses may use this file in
11 11 ** accordance with the Qt Enterprise License Agreement provided with the
12 12 ** Software or, alternatively, in accordance with the terms contained in
13 13 ** a written agreement between you and Digia.
14 14 **
15 15 ** If you have questions regarding the use of this file, please use
16 16 ** contact form at http://qt.digia.com
17 17 ** $QT_END_LICENSE$
18 18 **
19 19 ****************************************************************************/
20 20
21 21 // W A R N I N G
22 22 // -------------
23 23 //
24 24 // This file is not part of the Qt Enterprise Chart API. It exists purely as an
25 25 // implementation detail. This header file may change from version to
26 26 // version without notice, or even be removed.
27 27 //
28 28 // We mean it.
29 29
30 30 #ifndef PIESLICEITEM_H
31 31 #define PIESLICEITEM_H
32 32
33 33 #include "qchartglobal.h"
34 34 #include "charttheme_p.h"
35 35 #include "qpieseries.h"
36 36 #include "pieslicedata_p.h"
37 37 #include <QGraphicsItem>
38 38 #include <QRectF>
39 39 #include <QColor>
40 40 #include <QPen>
41 41
42 42 #define PIESLICE_LABEL_GAP 5
43 43
44 44 QTCOMMERCIALCHART_BEGIN_NAMESPACE
45 45 class PieChartItem;
46 46 class PieSliceLabel;
47 47 class QPieSlice;
48 48
49 49 class PieSliceItem : public QGraphicsObject
50 50 {
51 51 Q_OBJECT
52 52
53 53 public:
54 54 PieSliceItem(QGraphicsItem *parent = 0);
55 55 ~PieSliceItem();
56 56
57 57 // from QGraphicsItem
58 58 QRectF boundingRect() const;
59 59 QPainterPath shape() const;
60 60 void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
61 61 void hoverEnterEvent(QGraphicsSceneHoverEvent *event);
62 62 void hoverLeaveEvent(QGraphicsSceneHoverEvent *event);
63 63 void mousePressEvent(QGraphicsSceneMouseEvent *event);
64 64
65 65 void setLayout(const PieSliceData &sliceData);
66 66 static QPointF sliceCenter(QPointF point, qreal radius, QPieSlice *slice);
67 67
68 68 Q_SIGNALS:
69 69 void clicked(Qt::MouseButtons buttons);
70 70 void hovered(bool state);
71 71
72 72 private:
73 73 void updateGeometry();
74 74 QPainterPath slicePath(QPointF center, qreal radius, qreal startAngle, qreal angleSpan, qreal *centerAngle, QPointF *armStart);
75 75 QPainterPath labelArmPath(QPointF start, qreal angle, qreal length, qreal textWidth, QPointF *textStart);
76 76
77 77 private:
78 78 PieSliceData m_data;
79 79 QRectF m_boundingRect;
80 80 QPainterPath m_slicePath;
81 81 QPainterPath m_labelArmPath;
82 82 QRectF m_labelTextRect;
83 83 bool m_hovered;
84 QGraphicsTextItem *m_labelItem;
84 85
85 86 friend class PieSliceAnimation;
86 87 };
87 88
88 89 QTCOMMERCIALCHART_END_NAMESPACE
89 90
90 91 #endif // PIESLICEITEM_H
General Comments 0
You need to be logged in to leave comments. Login now