##// END OF EJS Templates
Fix pie slice label visibility...
Titta Heikkala -
r2655:5086492877e0
parent child
Show More
@@ -1,306 +1,313
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 30 #include <QTextDocument>
31 31 #include <QDebug>
32 32
33 33 QTCOMMERCIALCHART_BEGIN_NAMESPACE
34 34
35 35 QPointF offset(qreal angle, qreal length)
36 36 {
37 37 qreal dx = qSin(angle * (M_PI / 180)) * length;
38 38 qreal dy = qCos(angle * (M_PI / 180)) * length;
39 39 return QPointF(dx, -dy);
40 40 }
41 41
42 42 PieSliceItem::PieSliceItem(QGraphicsItem *parent)
43 43 : QGraphicsObject(parent),
44 44 m_hovered(false)
45 45 {
46 46 setAcceptHoverEvents(true);
47 47 setAcceptedMouseButtons(Qt::MouseButtonMask);
48 48 setZValue(ChartPresenter::PieSeriesZValue);
49 49 m_labelItem = new QGraphicsTextItem(this);
50 50 m_labelItem->document()->setDocumentMargin(1.0);
51 51 }
52 52
53 53 PieSliceItem::~PieSliceItem()
54 54 {
55 55 // If user is hovering over the slice and it gets destroyed we do
56 56 // not get a hover leave event. So we must emit the signal here.
57 57 if (m_hovered)
58 58 emit hovered(false);
59 59 }
60 60
61 61 QRectF PieSliceItem::boundingRect() const
62 62 {
63 63 return m_boundingRect;
64 64 }
65 65
66 66 QPainterPath PieSliceItem::shape() const
67 67 {
68 68 // Don't include the label and label arm.
69 69 // This is used to detect a mouse clicks. We do not want clicks from label.
70 70 return m_slicePath;
71 71 }
72 72
73 73 void PieSliceItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
74 74 {
75 75 painter->save();
76 76 painter->setClipRect(parentItem()->boundingRect());
77 77 painter->setPen(m_data.m_slicePen);
78 78 painter->setBrush(m_data.m_sliceBrush);
79 79 painter->drawPath(m_slicePath);
80 80 painter->restore();
81 81
82 82 if (m_data.m_isLabelVisible) {
83 83 painter->save();
84 84
85 85 // Pen for label arm not defined in the QPieSeries api, let's use brush's color instead
86 86 painter->setBrush(m_data.m_labelBrush);
87 87
88 88 if (m_data.m_labelPosition == QPieSlice::LabelOutside) {
89 89 painter->setClipRect(parentItem()->boundingRect());
90 90 painter->strokePath(m_labelArmPath, m_data.m_labelBrush.color());
91 91 }
92 92
93 93 painter->restore();
94 94 }
95 95 }
96 96
97 97 void PieSliceItem::hoverEnterEvent(QGraphicsSceneHoverEvent * /*event*/)
98 98 {
99 99 m_hovered = true;
100 100 emit hovered(true);
101 101 }
102 102
103 103 void PieSliceItem::hoverLeaveEvent(QGraphicsSceneHoverEvent * /*event*/)
104 104 {
105 105 m_hovered = false;
106 106 emit hovered(false);
107 107 }
108 108
109 109 void PieSliceItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
110 110 {
111 111 emit clicked(event->buttons());
112 112 QGraphicsItem::mousePressEvent(event);
113 113 }
114 114
115 115 void PieSliceItem::setLayout(const PieSliceData &sliceData)
116 116 {
117 117 m_data = sliceData;
118 118 updateGeometry();
119 119 update();
120 120 }
121 121
122 122 void PieSliceItem::updateGeometry()
123 123 {
124 124 if (m_data.m_radius <= 0)
125 125 return;
126 126
127 127 prepareGeometryChange();
128 128
129 129 // slice path
130 130 qreal centerAngle;
131 131 QPointF armStart;
132 132 m_slicePath = slicePath(m_data.m_center, m_data.m_radius, m_data.m_startAngle, m_data.m_angleSpan, &centerAngle, &armStart);
133 133
134 134 m_labelItem->setVisible(m_data.m_isLabelVisible);
135 135
136 136 if (m_data.m_isLabelVisible) {
137 137 // text rect
138 138 m_labelTextRect = ChartPresenter::textBoundingRect(m_data.m_labelFont,
139 139 m_data.m_labelText,
140 140 0);
141 141
142 142 QString label(m_data.m_labelText);
143 143 m_labelItem->setDefaultTextColor(m_data.m_labelBrush.color());
144 144 m_labelItem->setFont(m_data.m_labelFont);
145 145
146 146 // text position
147 147 if (m_data.m_labelPosition == QPieSlice::LabelOutside) {
148 148 setFlag(QGraphicsItem::ItemClipsChildrenToShape, false);
149 149
150 150 // label arm path
151 151 QPointF labelTextStart;
152 152 m_labelArmPath = labelArmPath(armStart, centerAngle,
153 153 m_data.m_radius * m_data.m_labelArmLengthFactor,
154 154 m_labelTextRect.width(), &labelTextStart);
155 155
156 156 m_labelTextRect.moveBottomLeft(labelTextStart);
157 157 if (m_labelTextRect.left() < 0)
158 158 m_labelTextRect.setLeft(0);
159 159 else if (m_labelTextRect.left() < parentItem()->boundingRect().left())
160 160 m_labelTextRect.setLeft(parentItem()->boundingRect().left());
161 161 if (m_labelTextRect.right() > parentItem()->boundingRect().right())
162 162 m_labelTextRect.setRight(parentItem()->boundingRect().right());
163 163
164 164 label = ChartPresenter::truncatedText(m_data.m_labelFont, m_data.m_labelText,
165 165 qreal(0.0), m_labelTextRect.width(),
166 166 m_labelTextRect.height(), m_labelTextRect);
167 167 m_labelArmPath = labelArmPath(armStart, centerAngle,
168 168 m_data.m_radius * m_data.m_labelArmLengthFactor,
169 169 m_labelTextRect.width(), &labelTextStart);
170 170 m_labelTextRect.moveBottomLeft(labelTextStart);
171 171
172 172 m_labelItem->setTextWidth(m_labelTextRect.width()
173 173 + m_labelItem->document()->documentMargin());
174 174 m_labelItem->setHtml(label);
175 175 m_labelItem->setRotation(0);
176 176 m_labelItem->setPos(m_labelTextRect.x(), m_labelTextRect.y() + 1.0);
177 177 } else {
178 178 // label inside
179 179 setFlag(QGraphicsItem::ItemClipsChildrenToShape);
180 180 m_labelItem->setTextWidth(m_labelTextRect.width()
181 181 + m_labelItem->document()->documentMargin());
182 182 m_labelItem->setHtml(label);
183 183
184 184 QPointF textCenter;
185 185 if (m_data.m_holeRadius > 0) {
186 186 textCenter = m_data.m_center + offset(centerAngle, m_data.m_holeRadius
187 187 + (m_data.m_radius
188 188 - m_data.m_holeRadius) / 2);
189 189 } else {
190 190 textCenter = m_data.m_center + offset(centerAngle, m_data.m_radius / 2);
191 191 }
192 192 m_labelItem->setPos(textCenter.x() - m_labelItem->boundingRect().width() / 2,
193 193 textCenter.y() - m_labelTextRect.height() / 2);
194 194
195 195 QPointF labelCenter = m_labelItem->boundingRect().center();
196 196 m_labelItem->setTransformOriginPoint(labelCenter);
197 197
198 198 if (m_data.m_labelPosition == QPieSlice::LabelInsideTangential) {
199 199 m_labelItem->setRotation(m_data.m_startAngle + m_data.m_angleSpan / 2);
200 200 } else if (m_data.m_labelPosition == QPieSlice::LabelInsideNormal) {
201 201 if (m_data.m_startAngle + m_data.m_angleSpan / 2 < 180)
202 202 m_labelItem->setRotation(m_data.m_startAngle + m_data.m_angleSpan / 2 - 90);
203 203 else
204 204 m_labelItem->setRotation(m_data.m_startAngle + m_data.m_angleSpan / 2 + 90);
205 205 } else {
206 206 m_labelItem->setRotation(0);
207 207 }
208 208 }
209 // Hide label if it's outside the bounding rect of parent item
210 QRectF labelRect(m_labelItem->boundingRect());
211 labelRect.moveTopLeft(m_labelItem->pos());
212 if (parentItem()->boundingRect().contains(labelRect))
213 m_labelItem->show();
214 else
215 m_labelItem->hide();
209 216 }
210 217
211 218 // bounding rect
212 219 if (m_data.m_isLabelVisible)
213 220 m_boundingRect = m_slicePath.boundingRect().united(m_labelArmPath.boundingRect()).united(m_labelTextRect);
214 221 else
215 222 m_boundingRect = m_slicePath.boundingRect();
216 223
217 224 // Inflate bounding rect by 2/3 pen width to make sure it encompasses whole slice also for thick pens
218 225 // and miter joins.
219 226 int penWidth = (m_data.m_slicePen.width() * 2) / 3;
220 227 m_boundingRect = m_boundingRect.adjusted(-penWidth, -penWidth, penWidth, penWidth);
221 228 }
222 229
223 230 QPointF PieSliceItem::sliceCenter(QPointF point, qreal radius, QPieSlice *slice)
224 231 {
225 232 if (slice->isExploded()) {
226 233 qreal centerAngle = slice->startAngle() + (slice->angleSpan() / 2);
227 234 qreal len = radius * slice->explodeDistanceFactor();
228 235 point += offset(centerAngle, len);
229 236 }
230 237 return point;
231 238 }
232 239
233 240 QPainterPath PieSliceItem::slicePath(QPointF center, qreal radius, qreal startAngle, qreal angleSpan, qreal *centerAngle, QPointF *armStart)
234 241 {
235 242 // calculate center angle
236 243 *centerAngle = startAngle + (angleSpan / 2);
237 244
238 245 // calculate slice rectangle
239 246 QRectF rect(center.x() - radius, center.y() - radius, radius * 2, radius * 2);
240 247
241 248 // slice path
242 249 QPainterPath path;
243 250 if (m_data.m_holeRadius > 0) {
244 251 QRectF insideRect(center.x() - m_data.m_holeRadius, center.y() - m_data.m_holeRadius, m_data.m_holeRadius * 2, m_data.m_holeRadius * 2);
245 252 path.arcMoveTo(rect, -startAngle + 90);
246 253 path.arcTo(rect, -startAngle + 90, -angleSpan);
247 254 path.arcTo(insideRect, -startAngle + 90 - angleSpan, angleSpan);
248 255 path.closeSubpath();
249 256 } else {
250 257 path.moveTo(rect.center());
251 258 path.arcTo(rect, -startAngle + 90, -angleSpan);
252 259 path.closeSubpath();
253 260 }
254 261
255 262 // calculate label arm start point
256 263 *armStart = center;
257 264 *armStart += offset(*centerAngle, radius + PIESLICE_LABEL_GAP);
258 265
259 266 return path;
260 267 }
261 268
262 269 QPainterPath PieSliceItem::labelArmPath(QPointF start, qreal angle, qreal length, qreal textWidth, QPointF *textStart)
263 270 {
264 271 // Normalize the angle to 0-360 range
265 272 // NOTE: We are using int here on purpose. Depenging on platform and hardware
266 273 // qreal can be a double, float or something the user gives to the Qt configure
267 274 // (QT_COORD_TYPE). Compilers do not seem to support modulo for double or float
268 275 // but there are fmod() and fmodf() functions for that. So instead of some #ifdef
269 276 // that might break we just use int. Precision for this is just fine for our needs.
270 277 int normalized = angle * 10.0;
271 278 normalized = normalized % 3600;
272 279 if (normalized < 0)
273 280 normalized += 3600;
274 281 angle = (qreal) normalized / 10.0;
275 282
276 283 // prevent label arm pointing straight down because it will look bad
277 284 if (angle < 180 && angle > 170)
278 285 angle = 170;
279 286 if (angle > 180 && angle < 190)
280 287 angle = 190;
281 288
282 289 // line from slice to label
283 290 QPointF parm1 = start + offset(angle, length);
284 291
285 292 // line to underline the label
286 293 QPointF parm2 = parm1;
287 294 if (angle < 180) { // arm swings the other way on the left side
288 295 parm2 += QPointF(textWidth, 0);
289 296 *textStart = parm1;
290 297 } else {
291 298 parm2 += QPointF(-textWidth, 0);
292 299 *textStart = parm2;
293 300 }
294 301
295 302 QPainterPath path;
296 303 path.moveTo(start);
297 304 path.lineTo(parm1);
298 305 path.lineTo(parm2);
299 306
300 307 return path;
301 308 }
302 309
303 310 #include "moc_piesliceitem_p.cpp"
304 311
305 312 QTCOMMERCIALCHART_END_NAMESPACE
306 313
General Comments 0
You need to be logged in to leave comments. Login now