##// END OF EJS Templates
Fix long labels visibility for QBarChart...
Titta Heikkala -
r2604:776457a845dd
parent child
Show More
@@ -1,223 +1,219
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 "horizontalaxis_p.h"
22 22 #include "qabstractaxis_p.h"
23 23 #include "chartpresenter_p.h"
24 24 #include <qmath.h>
25 25 #include <QDebug>
26 26
27 27 QTCOMMERCIALCHART_BEGIN_NAMESPACE
28 28
29 29 HorizontalAxis::HorizontalAxis(QAbstractAxis *axis, QGraphicsItem *item, bool intervalAxis)
30 30 : CartesianChartAxis(axis, item, intervalAxis)
31 31 {
32 32 }
33 33
34 34 HorizontalAxis::~HorizontalAxis()
35 35 {
36 36 }
37 37
38 38 void HorizontalAxis::updateGeometry()
39 39 {
40 40 const QVector<qreal> &layout = ChartAxisElement::layout();
41 41
42 42 if (layout.isEmpty())
43 43 return;
44 44
45 45 QStringList labelList = labels();
46 46
47 47 QList<QGraphicsItem *> lines = gridItems();
48 48 QList<QGraphicsItem *> labels = labelItems();
49 49 QList<QGraphicsItem *> shades = shadeItems();
50 50 QList<QGraphicsItem *> arrow = arrowItems();
51 51 QGraphicsTextItem *title = titleItem();
52 52
53 53 Q_ASSERT(labels.size() == labelList.size());
54 54 Q_ASSERT(layout.size() == labelList.size());
55 55
56 56 const QRectF &axisRect = axisGeometry();
57 57 const QRectF &gridRect = gridGeometry();
58 58
59 59 //arrow
60 60 QGraphicsLineItem *arrowItem = static_cast<QGraphicsLineItem *>(arrow.at(0));
61 61
62 62 if (axis()->alignment() == Qt::AlignTop)
63 63 arrowItem->setLine(gridRect.left(), axisRect.bottom(), gridRect.right(), axisRect.bottom());
64 64 else if (axis()->alignment() == Qt::AlignBottom)
65 65 arrowItem->setLine(gridRect.left(), axisRect.top(), gridRect.right(), axisRect.top());
66 66
67 67 qreal width = 0;
68 68
69 69 //title
70 70 QRectF titleBoundingRect;
71 71 QString titleText = axis()->titleText();
72 72 qreal availableSpace = axisRect.height() - labelPadding();
73 73 if (!titleText.isEmpty() && titleItem()->isVisible()) {
74 74 availableSpace -= titlePadding() * 2.0;
75 75 qreal minimumLabelHeight = ChartPresenter::textBoundingRect(axis()->labelsFont(), "...").height();
76 QString truncatedTitle = ChartPresenter::truncatedText(axis()->titleFont(), titleText, qreal(0.0),
77 gridRect.width(), Qt::Horizontal, titleBoundingRect);
78 76 qreal titleSpace = availableSpace - minimumLabelHeight;
79 if (titleSpace < titleBoundingRect.height()) {
80 // Need to also truncate title vertically (multiline title)
81 bool skip = false;
82 if (truncatedTitle.endsWith("...")) {
83 if (truncatedTitle.size() == 3)
84 skip = true; // Already truncated to minimum
85 else
86 truncatedTitle.chop(3);
87 }
88 if (!skip)
89 truncatedTitle = ChartPresenter::truncatedText(axis()->titleFont(), truncatedTitle, qreal(0.0),
90 titleSpace, Qt::Vertical, titleBoundingRect);
91 }
92 title->setHtml(truncatedTitle);
77 title->setHtml(ChartPresenter::truncatedText(axis()->titleFont(), titleText, qreal(0.0),
78 gridRect.width(), titleSpace,
79 titleBoundingRect));
93 80
94 81 titleBoundingRect = title->boundingRect();
95 82
96 83 QPointF center = gridRect.center() - titleBoundingRect.center();
97 84 if (axis()->alignment() == Qt::AlignTop)
98 85 title->setPos(center.x(), axisRect.top() + titlePadding());
99 86 else if (axis()->alignment() == Qt::AlignBottom)
100 87 title->setPos(center.x(), axisRect.bottom() - titleBoundingRect.height() - titlePadding());
101 88
102 89 availableSpace -= titleBoundingRect.height();
103 90 }
104 91
105 92 for (int i = 0; i < layout.size(); ++i) {
106 93 //items
107 94 QGraphicsLineItem *gridItem = static_cast<QGraphicsLineItem*>(lines.at(i));
108 95 QGraphicsLineItem *tickItem = static_cast<QGraphicsLineItem*>(arrow.at(i + 1));
109 96 QGraphicsTextItem *labelItem = static_cast<QGraphicsTextItem *>(labels.at(i));
110 97
111 98 //grid line
112 99 gridItem->setLine(layout[i], gridRect.top(), layout[i], gridRect.bottom());
113 100
114 101 //label text wrapping
115 102 QString text = labelList.at(i);
116 103 QRectF boundingRect;
117 labelItem->setHtml(ChartPresenter::truncatedText(axis()->labelsFont(), text, axis()->labelsAngle(),
118 availableSpace, Qt::Vertical, boundingRect));
104 // don't truncate empty labels
105 if (text.isEmpty()) {
106 labelItem->setHtml(text);
107 } else {
108 qreal labelWidth = axisRect.width() / layout.count() - (2 * labelPadding());
109 labelItem->setHtml(ChartPresenter::truncatedText(axis()->labelsFont(), text,
110 axis()->labelsAngle(), labelWidth,
111 availableSpace, boundingRect));
112 }
119 113
120 114 //label transformation origin point
121 115 const QRectF& rect = labelItem->boundingRect();
122 116 QPointF center = rect.center();
123 117 labelItem->setTransformOriginPoint(center.x(), center.y());
124 118 qreal heightDiff = rect.height() - boundingRect.height();
125 119 qreal widthDiff = rect.width() - boundingRect.width();
126 120
127 121 //ticks and label position
128 122 if (axis()->alignment() == Qt::AlignTop) {
129 123 labelItem->setPos(layout[i] - center.x(), axisRect.bottom() - rect.height() + (heightDiff / 2.0) - labelPadding());
130 124 tickItem->setLine(layout[i], axisRect.bottom(), layout[i], axisRect.bottom() - labelPadding());
131 125 } else if (axis()->alignment() == Qt::AlignBottom) {
132 126 labelItem->setPos(layout[i] - center.x(), axisRect.top() - (heightDiff / 2.0) + labelPadding());
133 127 tickItem->setLine(layout[i], axisRect.top(), layout[i], axisRect.top() + labelPadding());
134 128 }
135 129
136 130 //label in between
137 131 bool forceHide = false;
138 132 if (intervalAxis() && (i + 1) != layout.size()) {
139 133 qreal leftBound = qMax(layout[i], gridRect.left());
140 134 qreal rightBound = qMin(layout[i + 1], gridRect.right());
141 135 const qreal delta = rightBound - leftBound;
142 136 // Hide label in case visible part of the category at the grid edge is too narrow
143 137 if (delta < boundingRect.width()
144 && (leftBound == gridRect.left() || rightBound == gridRect.right())) {
138 && (leftBound == gridRect.left() || rightBound == gridRect.right())
139 && !intervalAxis()) {
145 140 forceHide = true;
146 141 } else {
147 142 labelItem->setPos(leftBound + (delta / 2.0) - center.x(), labelItem->pos().y());
148 143 }
149 144 }
150 145 //label overlap detection - compensate one pixel for rounding errors
151 if (labelItem->pos().x() < width || forceHide ||
146 if ((labelItem->pos().x() < width || forceHide ||
152 147 (labelItem->pos().x() + (widthDiff / 2.0)) < (axisRect.left() - 1.0) ||
153 (labelItem->pos().x() + (widthDiff / 2.0) - 1.0) > axisRect.right()){
148 (labelItem->pos().x() + (widthDiff / 2.0) - 1.0) > axisRect.right())
149 && !intervalAxis()) {
154 150 labelItem->setVisible(false);
155 151 } else {
156 152 labelItem->setVisible(true);
157 153 width = boundingRect.width() + labelItem->pos().x();
158 154 }
159 155
160 156 //shades
161 157 if ((i + 1) % 2 && i > 1) {
162 158 QGraphicsRectItem *rectItem = static_cast<QGraphicsRectItem *>(shades.at(i / 2 - 1));
163 159 qreal leftBound = qMax(layout[i - 1], gridRect.left());
164 160 qreal rightBound = qMin(layout[i], gridRect.right());
165 161 rectItem->setRect(leftBound, gridRect.top(), rightBound - leftBound, gridRect.height());
166 162 if (rectItem->rect().width() <= 0.0)
167 163 rectItem->setVisible(false);
168 164 else
169 165 rectItem->setVisible(true);
170 166 }
171 167
172 168 // check if the grid line and the axis tick should be shown
173 169 qreal x = gridItem->line().p1().x();
174 170 if (x < gridRect.left() || x > gridRect.right()) {
175 171 gridItem->setVisible(false);
176 172 tickItem->setVisible(false);
177 173 } else {
178 174 gridItem->setVisible(true);
179 175 tickItem->setVisible(true);
180 176 }
181 177
182 178 }
183 179
184 180 //begin/end grid line in case labels between
185 181 if (intervalAxis()) {
186 182 QGraphicsLineItem *gridLine;
187 183 gridLine = static_cast<QGraphicsLineItem *>(lines.at(layout.size()));
188 184 gridLine->setLine(gridRect.right(), gridRect.top(), gridRect.right(), gridRect.bottom());
189 185 gridLine->setVisible(true);
190 186 gridLine = static_cast<QGraphicsLineItem*>(lines.at(layout.size()+1));
191 187 gridLine->setLine(gridRect.left(), gridRect.top(), gridRect.left(), gridRect.bottom());
192 188 gridLine->setVisible(true);
193 189 }
194 190 }
195 191
196 192 QSizeF HorizontalAxis::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const
197 193 {
198 194 Q_UNUSED(constraint);
199 195 QSizeF sh(0,0);
200 196
201 197 if (axis()->titleText().isEmpty() || !titleItem()->isVisible())
202 198 return sh;
203 199
204 200 switch (which) {
205 201 case Qt::MinimumSize: {
206 202 QRectF titleRect = ChartPresenter::textBoundingRect(axis()->titleFont(), "...");
207 203 sh = QSizeF(titleRect.width(), titleRect.height() + (titlePadding() * 2.0));
208 204 break;
209 205 }
210 206 case Qt::MaximumSize:
211 207 case Qt::PreferredSize: {
212 208 QRectF titleRect = ChartPresenter::textBoundingRect(axis()->titleFont(), axis()->titleText());
213 209 sh = QSizeF(titleRect.width(), titleRect.height() + (titlePadding() * 2.0));
214 210 break;
215 211 }
216 212 default:
217 213 break;
218 214 }
219 215
220 216 return sh;
221 217 }
222 218
223 219 QTCOMMERCIALCHART_END_NAMESPACE
@@ -1,422 +1,427
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 "polarchartaxisangular_p.h"
22 22 #include "chartpresenter_p.h"
23 23 #include "abstractchartlayout_p.h"
24 24 #include "qabstractaxis.h"
25 25 #include "qabstractaxis_p.h"
26 26 #include <QDebug>
27 27 #include <qmath.h>
28 28 #include <QTextDocument>
29 29
30 30 QTCOMMERCIALCHART_BEGIN_NAMESPACE
31 31
32 32 PolarChartAxisAngular::PolarChartAxisAngular(QAbstractAxis *axis, QGraphicsItem *item, bool intervalAxis)
33 33 : PolarChartAxis(axis, item, intervalAxis)
34 34 {
35 35 }
36 36
37 37 PolarChartAxisAngular::~PolarChartAxisAngular()
38 38 {
39 39 }
40 40
41 41 void PolarChartAxisAngular::updateGeometry()
42 42 {
43 43 QGraphicsLayoutItem::updateGeometry();
44 44
45 45 const QVector<qreal> &layout = this->layout();
46 46 if (layout.isEmpty())
47 47 return;
48 48
49 49 createAxisLabels(layout);
50 50 QStringList labelList = labels();
51 51 QPointF center = axisGeometry().center();
52 52 QList<QGraphicsItem *> arrowItemList = arrowItems();
53 53 QList<QGraphicsItem *> gridItemList = gridItems();
54 54 QList<QGraphicsItem *> labelItemList = labelItems();
55 55 QList<QGraphicsItem *> shadeItemList = shadeItems();
56 56 QGraphicsTextItem *title = titleItem();
57 57
58 58 QGraphicsEllipseItem *axisLine = static_cast<QGraphicsEllipseItem *>(arrowItemList.at(0));
59 59 axisLine->setRect(axisGeometry());
60 60
61 61 qreal radius = axisGeometry().height() / 2.0;
62 62
63 63 QRectF previousLabelRect;
64 64 QRectF firstLabelRect;
65 65
66 66 qreal labelHeight = 0;
67 67
68 68 bool firstShade = true;
69 69 bool nextTickVisible = false;
70 70 if (layout.size())
71 71 nextTickVisible = !(layout.at(0) < 0.0 || layout.at(0) > 360.0);
72 72
73 73 for (int i = 0; i < layout.size(); ++i) {
74 74 qreal angularCoordinate = layout.at(i);
75 75
76 76 QGraphicsLineItem *gridLineItem = static_cast<QGraphicsLineItem *>(gridItemList.at(i));
77 77 QGraphicsLineItem *tickItem = static_cast<QGraphicsLineItem *>(arrowItemList.at(i + 1));
78 78 QGraphicsTextItem *labelItem = static_cast<QGraphicsTextItem *>(labelItemList.at(i));
79 79 QGraphicsPathItem *shadeItem = 0;
80 80 if (i == 0)
81 81 shadeItem = static_cast<QGraphicsPathItem *>(shadeItemList.at(0));
82 82 else if (i % 2)
83 83 shadeItem = static_cast<QGraphicsPathItem *>(shadeItemList.at((i / 2) + 1));
84 84
85 85 // Ignore ticks outside valid range
86 86 bool currentTickVisible = nextTickVisible;
87 87 if ((i == layout.size() - 1)
88 88 || layout.at(i + 1) < 0.0
89 89 || layout.at(i + 1) > 360.0) {
90 90 nextTickVisible = false;
91 91 } else {
92 92 nextTickVisible = true;
93 93 }
94 94
95 95 qreal labelCoordinate = angularCoordinate;
96 96 qreal labelVisible = currentTickVisible;
97 97 if (intervalAxis()) {
98 98 qreal farEdge;
99 99 if (i == (layout.size() - 1))
100 100 farEdge = 360.0;
101 101 else
102 102 farEdge = qMin(qreal(360.0), layout.at(i + 1));
103 103
104 104 // Adjust the labelCoordinate to show it if next tick is visible
105 105 if (nextTickVisible)
106 106 labelCoordinate = qMax(qreal(0.0), labelCoordinate);
107 107
108 108 labelCoordinate = (labelCoordinate + farEdge) / 2.0;
109 109 // Don't display label once the category gets too small near the axis
110 110 if (labelCoordinate < 5.0 || labelCoordinate > 355.0)
111 111 labelVisible = false;
112 112 else
113 113 labelVisible = true;
114 114 }
115 115
116 116 // Need this also in label calculations, so determine it first
117 117 QLineF tickLine(QLineF::fromPolar(radius - tickWidth(), 90.0 - angularCoordinate).p2(),
118 118 QLineF::fromPolar(radius + tickWidth(), 90.0 - angularCoordinate).p2());
119 119 tickLine.translate(center);
120 120
121 121 // Angular axis label
122 122 if (axis()->labelsVisible() && labelVisible) {
123 123 labelItem->setHtml(labelList.at(i));
124 124 const QRectF &rect = labelItem->boundingRect();
125 125 QPointF labelCenter = rect.center();
126 126 labelItem->setTransformOriginPoint(labelCenter.x(), labelCenter.y());
127 127 QRectF boundingRect = ChartPresenter::textBoundingRect(axis()->labelsFont(), labelList.at(i), axis()->labelsAngle());
128 128 boundingRect.moveCenter(labelCenter);
129 129 QPointF positionDiff(rect.topLeft() - boundingRect.topLeft());
130 130
131 131 QPointF labelPoint;
132 132 if (intervalAxis()) {
133 133 QLineF labelLine = QLineF::fromPolar(radius + tickWidth(), 90.0 - labelCoordinate);
134 134 labelLine.translate(center);
135 135 labelPoint = labelLine.p2();
136 136 } else {
137 137 labelPoint = tickLine.p2();
138 138 }
139 139
140 140 QRectF labelRect = moveLabelToPosition(labelCoordinate, labelPoint, boundingRect);
141 141 labelItem->setPos(labelRect.topLeft() + positionDiff);
142 142
143 143 // Store height for title calculations
144 144 qreal labelClearance = axisGeometry().top() - labelRect.top();
145 145 labelHeight = qMax(labelHeight, labelClearance);
146 146
147 147 // Label overlap detection
148 148 if (i && (previousLabelRect.intersects(labelRect) || firstLabelRect.intersects(labelRect))) {
149 149 labelVisible = false;
150 150 } else {
151 151 // Store labelRect for future comparison. Some area is deducted to make things look
152 152 // little nicer, as usually intersection happens at label corner with angular labels.
153 153 labelRect.adjust(-2.0, -4.0, -2.0, -4.0);
154 154 if (firstLabelRect.isEmpty())
155 155 firstLabelRect = labelRect;
156 156
157 157 previousLabelRect = labelRect;
158 158 labelVisible = true;
159 159 }
160 160 }
161 161
162 162 labelItem->setVisible(labelVisible);
163 163 if (!currentTickVisible) {
164 164 gridLineItem->setVisible(false);
165 165 tickItem->setVisible(false);
166 166 if (shadeItem)
167 167 shadeItem->setVisible(false);
168 168 continue;
169 169 }
170 170
171 171 // Angular grid line
172 172 QLineF gridLine = QLineF::fromPolar(radius, 90.0 - angularCoordinate);
173 173 gridLine.translate(center);
174 174 gridLineItem->setLine(gridLine);
175 175 gridLineItem->setVisible(true);
176 176
177 177 // Tick
178 178 tickItem->setLine(tickLine);
179 179 tickItem->setVisible(true);
180 180
181 181 // Shades
182 182 if (i % 2 || (i == 0 && !nextTickVisible)) {
183 183 QPainterPath path;
184 184 path.moveTo(center);
185 185 if (i == 0) {
186 186 // If first tick is also the last, we need to custom fill the first partial arc
187 187 // or it won't get filled.
188 188 path.arcTo(axisGeometry(), 90.0 - layout.at(0), layout.at(0));
189 189 path.closeSubpath();
190 190 } else {
191 191 qreal nextCoordinate;
192 192 if (!nextTickVisible) // Last visible tick
193 193 nextCoordinate = 360.0;
194 194 else
195 195 nextCoordinate = layout.at(i + 1);
196 196 qreal arcSpan = angularCoordinate - nextCoordinate;
197 197 path.arcTo(axisGeometry(), 90.0 - angularCoordinate, arcSpan);
198 198 path.closeSubpath();
199 199
200 200 // Add additional arc for first shade item if there is a partial arc to be filled
201 201 if (firstShade) {
202 202 QGraphicsPathItem *specialShadeItem = static_cast<QGraphicsPathItem *>(shadeItemList.at(0));
203 203 if (layout.at(i - 1) > 0.0) {
204 204 QPainterPath specialPath;
205 205 specialPath.moveTo(center);
206 206 specialPath.arcTo(axisGeometry(), 90.0 - layout.at(i - 1), layout.at(i - 1));
207 207 specialPath.closeSubpath();
208 208 specialShadeItem->setPath(specialPath);
209 209 specialShadeItem->setVisible(true);
210 210 } else {
211 211 specialShadeItem->setVisible(false);
212 212 }
213 213 }
214 214 }
215 215 shadeItem->setPath(path);
216 216 shadeItem->setVisible(true);
217 217 firstShade = false;
218 218 }
219 219 }
220 220
221 221 // Title, centered above the chart
222 222 QString titleText = axis()->titleText();
223 223 if (!titleText.isEmpty() && axis()->isTitleVisible()) {
224 224 QRectF dummyRect;
225 title->setHtml(ChartPresenter::truncatedText(axis()->titleFont(), titleText, qreal(0.0), axisGeometry().width(), Qt::Horizontal, dummyRect));
225 qreal availableTitleHeight = axisGeometry().height() - labelPadding() - titlePadding() * 2.0;
226 qreal minimumLabelHeight = ChartPresenter::textBoundingRect(axis()->labelsFont(), "...").height();
227 availableTitleHeight -= minimumLabelHeight;
228 title->setHtml(ChartPresenter::truncatedText(axis()->titleFont(), titleText, qreal(0.0),
229 axisGeometry().width(), availableTitleHeight,
230 dummyRect));
226 231
227 232 QRectF titleBoundingRect = title->boundingRect();
228 233 QPointF titleCenter = center - titleBoundingRect.center();
229 234 title->setPos(titleCenter.x(), axisGeometry().top() - titlePadding() * 2.0 - titleBoundingRect.height() - labelHeight);
230 235 }
231 236 }
232 237
233 238 Qt::Orientation PolarChartAxisAngular::orientation() const
234 239 {
235 240 return Qt::Horizontal;
236 241 }
237 242
238 243 void PolarChartAxisAngular::createItems(int count)
239 244 {
240 245 if (arrowItems().count() == 0) {
241 246 // angular axis line
242 247 QGraphicsEllipseItem *arrow = new QGraphicsEllipseItem(presenter()->rootItem());
243 248 arrow->setPen(axis()->linePen());
244 249 arrowGroup()->addToGroup(arrow);
245 250 }
246 251
247 252 for (int i = 0; i < count; ++i) {
248 253 QGraphicsLineItem *arrow = new QGraphicsLineItem(presenter()->rootItem());
249 254 QGraphicsLineItem *grid = new QGraphicsLineItem(presenter()->rootItem());
250 255 QGraphicsTextItem *label = new QGraphicsTextItem(presenter()->rootItem());
251 256 label->document()->setDocumentMargin(ChartPresenter::textMargin());
252 257 QGraphicsTextItem *title = titleItem();
253 258 arrow->setPen(axis()->linePen());
254 259 grid->setPen(axis()->gridLinePen());
255 260 label->setFont(axis()->labelsFont());
256 261 label->setDefaultTextColor(axis()->labelsBrush().color());
257 262 label->setRotation(axis()->labelsAngle());
258 263 title->setFont(axis()->titleFont());
259 264 title->setDefaultTextColor(axis()->titleBrush().color());
260 265 title->setHtml(axis()->titleText());
261 266 arrowGroup()->addToGroup(arrow);
262 267 gridGroup()->addToGroup(grid);
263 268 labelGroup()->addToGroup(label);
264 269 if (gridItems().size() == 1 || (((gridItems().size() + 1) % 2) && gridItems().size() > 0)) {
265 270 QGraphicsPathItem *shade = new QGraphicsPathItem(presenter()->rootItem());
266 271 shade->setPen(axis()->shadesPen());
267 272 shade->setBrush(axis()->shadesBrush());
268 273 shadeGroup()->addToGroup(shade);
269 274 }
270 275 }
271 276 }
272 277
273 278 void PolarChartAxisAngular::handleArrowPenChanged(const QPen &pen)
274 279 {
275 280 bool first = true;
276 281 foreach (QGraphicsItem *item, arrowItems()) {
277 282 if (first) {
278 283 first = false;
279 284 // First arrow item is the outer circle of axis
280 285 static_cast<QGraphicsEllipseItem *>(item)->setPen(pen);
281 286 } else {
282 287 static_cast<QGraphicsLineItem *>(item)->setPen(pen);
283 288 }
284 289 }
285 290 }
286 291
287 292 void PolarChartAxisAngular::handleGridPenChanged(const QPen &pen)
288 293 {
289 294 foreach (QGraphicsItem *item, gridItems())
290 295 static_cast<QGraphicsLineItem *>(item)->setPen(pen);
291 296 }
292 297
293 298 QSizeF PolarChartAxisAngular::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const
294 299 {
295 300 Q_UNUSED(which);
296 301 Q_UNUSED(constraint);
297 302 return QSizeF(-1, -1);
298 303 }
299 304
300 305 qreal PolarChartAxisAngular::preferredAxisRadius(const QSizeF &maxSize)
301 306 {
302 307 qreal radius = maxSize.height() / 2.0;
303 308 if (maxSize.width() < maxSize.height())
304 309 radius = maxSize.width() / 2.0;
305 310
306 311 if (axis()->labelsVisible()) {
307 312 QVector<qreal> layout = calculateLayout();
308 313 if (layout.isEmpty())
309 314 return radius;
310 315
311 316 createAxisLabels(layout);
312 317 QStringList labelList = labels();
313 318 QFont font = axis()->labelsFont();
314 319
315 320 QRectF maxRect;
316 321 maxRect.setSize(maxSize);
317 322 maxRect.moveCenter(QPointF(0.0, 0.0));
318 323
319 324 // This is a horrible way to find out the maximum radius for angular axis and its labels.
320 325 // It just increments the radius down until everyhing fits the constraint size.
321 326 // Proper way would be to actually calculate it but this seems to work reasonably fast as it is.
322 327 bool nextTickVisible = false;
323 328 for (int i = 0; i < layout.size(); ) {
324 329 if ((i == layout.size() - 1)
325 330 || layout.at(i + 1) < 0.0
326 331 || layout.at(i + 1) > 360.0) {
327 332 nextTickVisible = false;
328 333 } else {
329 334 nextTickVisible = true;
330 335 }
331 336
332 337 qreal labelCoordinate = layout.at(i);
333 338 qreal labelVisible;
334 339
335 340 if (intervalAxis()) {
336 341 qreal farEdge;
337 342 if (i == (layout.size() - 1))
338 343 farEdge = 360.0;
339 344 else
340 345 farEdge = qMin(qreal(360.0), layout.at(i + 1));
341 346
342 347 // Adjust the labelCoordinate to show it if next tick is visible
343 348 if (nextTickVisible)
344 349 labelCoordinate = qMax(qreal(0.0), labelCoordinate);
345 350
346 351 labelCoordinate = (labelCoordinate + farEdge) / 2.0;
347 352 }
348 353
349 354 if (labelCoordinate < 0.0 || labelCoordinate > 360.0)
350 355 labelVisible = false;
351 356 else
352 357 labelVisible = true;
353 358
354 359 if (!labelVisible) {
355 360 i++;
356 361 continue;
357 362 }
358 363
359 364 QRectF boundingRect = ChartPresenter::textBoundingRect(axis()->labelsFont(), labelList.at(i), axis()->labelsAngle());
360 365 QPointF labelPoint = QLineF::fromPolar(radius + tickWidth(), 90.0 - labelCoordinate).p2();
361 366
362 367 boundingRect = moveLabelToPosition(labelCoordinate, labelPoint, boundingRect);
363 368 QRectF intersectRect = maxRect.intersected(boundingRect);
364 369 if (boundingRect.isEmpty() || intersectRect == boundingRect) {
365 370 i++;
366 371 } else {
367 372 qreal reduction(0.0);
368 373 // If there is no intersection, reduce by smallest dimension of label rect to be on the safe side
369 374 if (intersectRect.isEmpty()) {
370 375 reduction = qMin(boundingRect.height(), boundingRect.width());
371 376 } else {
372 377 // Approximate needed radius reduction is the amount label rect exceeds max rect in either dimension.
373 378 // Could be further optimized by figuring out the proper math how to calculate exact needed reduction.
374 379 reduction = qMax(boundingRect.height() - intersectRect.height(),
375 380 boundingRect.width() - intersectRect.width());
376 381 }
377 382 // Typically the approximated reduction is little low, so add one
378 383 radius -= (reduction + 1.0);
379 384
380 385 if (radius < 1.0) // safeguard
381 386 return 1.0;
382 387 }
383 388 }
384 389 }
385 390
386 391 if (!axis()->titleText().isEmpty() && axis()->isTitleVisible()) {
387 392 QRectF titleRect = ChartPresenter::textBoundingRect(axis()->titleFont(), axis()->titleText());
388 393
389 394 radius -= titlePadding() + (titleRect.height() / 2.0);
390 395 if (radius < 1.0) // safeguard
391 396 return 1.0;
392 397 }
393 398
394 399 return radius;
395 400 }
396 401
397 402 QRectF PolarChartAxisAngular::moveLabelToPosition(qreal angularCoordinate, QPointF labelPoint, QRectF labelRect) const
398 403 {
399 404 if (angularCoordinate == 0.0)
400 405 labelRect.moveCenter(labelPoint + QPointF(0, -labelRect.height() / 2.0));
401 406 else if (angularCoordinate < 90.0)
402 407 labelRect.moveBottomLeft(labelPoint);
403 408 else if (angularCoordinate == 90.0)
404 409 labelRect.moveCenter(labelPoint + QPointF(labelRect.width() / 2.0 + 2.0, 0)); // +2 so that it does not hit the radial axis
405 410 else if (angularCoordinate < 180.0)
406 411 labelRect.moveTopLeft(labelPoint);
407 412 else if (angularCoordinate == 180.0)
408 413 labelRect.moveCenter(labelPoint + QPointF(0, labelRect.height() / 2.0));
409 414 else if (angularCoordinate < 270.0)
410 415 labelRect.moveTopRight(labelPoint);
411 416 else if (angularCoordinate == 270.0)
412 417 labelRect.moveCenter(labelPoint + QPointF(-labelRect.width() / 2.0 - 2.0, 0)); // -2 so that it does not hit the radial axis
413 418 else if (angularCoordinate < 360.0)
414 419 labelRect.moveBottomRight(labelPoint);
415 420 else
416 421 labelRect.moveCenter(labelPoint + QPointF(0, -labelRect.height() / 2.0));
417 422 return labelRect;
418 423 }
419 424
420 425 #include "moc_polarchartaxisangular_p.cpp"
421 426
422 427 QTCOMMERCIALCHART_END_NAMESPACE
@@ -1,291 +1,292
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 "polarchartaxisradial_p.h"
22 22 #include "chartpresenter_p.h"
23 23 #include "abstractchartlayout_p.h"
24 24 #include "qabstractaxis_p.h"
25 25 #include "linearrowitem_p.h"
26 26 #include <QTextDocument>
27 27
28 28 QTCOMMERCIALCHART_BEGIN_NAMESPACE
29 29
30 30 PolarChartAxisRadial::PolarChartAxisRadial(QAbstractAxis *axis, QGraphicsItem *item, bool intervalAxis)
31 31 : PolarChartAxis(axis, item, intervalAxis)
32 32 {
33 33 }
34 34
35 35 PolarChartAxisRadial::~PolarChartAxisRadial()
36 36 {
37 37 }
38 38
39 39 void PolarChartAxisRadial::updateGeometry()
40 40 {
41 41 const QVector<qreal> &layout = this->layout();
42 42 if (layout.isEmpty())
43 43 return;
44 44
45 45 createAxisLabels(layout);
46 46 QStringList labelList = labels();
47 47 QPointF center = axisGeometry().center();
48 48 QList<QGraphicsItem *> arrowItemList = arrowItems();
49 49 QList<QGraphicsItem *> gridItemList = gridItems();
50 50 QList<QGraphicsItem *> labelItemList = labelItems();
51 51 QList<QGraphicsItem *> shadeItemList = shadeItems();
52 52 QGraphicsTextItem* title = titleItem();
53 53 qreal radius = axisGeometry().height() / 2.0;
54 54
55 55 QLineF line(center, center + QPointF(0, -radius));
56 56 QGraphicsLineItem *axisLine = static_cast<QGraphicsLineItem *>(arrowItemList.at(0));
57 57 axisLine->setLine(line);
58 58
59 59 QRectF previousLabelRect;
60 60 bool firstShade = true;
61 61 bool nextTickVisible = false;
62 62 if (layout.size())
63 63 nextTickVisible = !(layout.at(0) < 0.0 || layout.at(0) > radius);
64 64
65 65 for (int i = 0; i < layout.size(); ++i) {
66 66 qreal radialCoordinate = layout.at(i);
67 67
68 68 QGraphicsEllipseItem *gridItem = static_cast<QGraphicsEllipseItem *>(gridItemList.at(i));
69 69 QGraphicsLineItem *tickItem = static_cast<QGraphicsLineItem *>(arrowItemList.at(i + 1));
70 70 QGraphicsTextItem *labelItem = static_cast<QGraphicsTextItem *>(labelItemList.at(i));
71 71 QGraphicsPathItem *shadeItem = 0;
72 72 if (i == 0)
73 73 shadeItem = static_cast<QGraphicsPathItem *>(shadeItemList.at(0));
74 74 else if (i % 2)
75 75 shadeItem = static_cast<QGraphicsPathItem *>(shadeItemList.at((i / 2) + 1));
76 76
77 77 // Ignore ticks outside valid range
78 78 bool currentTickVisible = nextTickVisible;
79 79 if ((i == layout.size() - 1)
80 80 || layout.at(i + 1) < 0.0
81 81 || layout.at(i + 1) > radius) {
82 82 nextTickVisible = false;
83 83 } else {
84 84 nextTickVisible = true;
85 85 }
86 86
87 87 qreal labelCoordinate = radialCoordinate;
88 88 qreal labelVisible = currentTickVisible;
89 89 qreal labelPad = labelPadding() / 2.0;
90 90 if (intervalAxis()) {
91 91 qreal farEdge;
92 92 if (i == (layout.size() - 1))
93 93 farEdge = radius;
94 94 else
95 95 farEdge = qMin(radius, layout.at(i + 1));
96 96
97 97 // Adjust the labelCoordinate to show it if next tick is visible
98 98 if (nextTickVisible)
99 99 labelCoordinate = qMax(qreal(0.0), labelCoordinate);
100 100
101 101 labelCoordinate = (labelCoordinate + farEdge) / 2.0;
102 102 if (labelCoordinate > 0.0 && labelCoordinate < radius)
103 103 labelVisible = true;
104 104 else
105 105 labelVisible = false;
106 106 }
107 107
108 108 // Radial axis label
109 109 if (axis()->labelsVisible() && labelVisible) {
110 110 labelItem->setHtml(labelList.at(i));
111 111 QRectF labelRect = labelItem->boundingRect();
112 112 QPointF labelCenter = labelRect.center();
113 113 labelItem->setTransformOriginPoint(labelCenter.x(), labelCenter.y());
114 114 QRectF boundingRect = ChartPresenter::textBoundingRect(axis()->labelsFont(), labelList.at(i), axis()->labelsAngle());
115 115 boundingRect.moveCenter(labelCenter);
116 116 QPointF positionDiff(labelRect.topLeft() - boundingRect.topLeft());
117 117 QPointF labelPoint = center;
118 118 if (intervalAxis())
119 119 labelPoint += QPointF(labelPad, -labelCoordinate - (boundingRect.height() / 2.0));
120 120 else
121 121 labelPoint += QPointF(labelPad, labelPad - labelCoordinate);
122 122 labelRect.moveTopLeft(labelPoint);
123 123 labelItem->setPos(labelRect.topLeft() + positionDiff);
124 124
125 125 // Label overlap detection
126 126 labelRect.setSize(boundingRect.size());
127 127 if ((i && previousLabelRect.intersects(labelRect))
128 128 || !axisGeometry().contains(labelRect)) {
129 129 labelVisible = false;
130 130 } else {
131 131 previousLabelRect = labelRect;
132 132 labelVisible = true;
133 133 }
134 134 }
135 135
136 136 labelItem->setVisible(labelVisible);
137 137 if (!currentTickVisible) {
138 138 gridItem->setVisible(false);
139 139 tickItem->setVisible(false);
140 140 if (shadeItem)
141 141 shadeItem->setVisible(false);
142 142 continue;
143 143 }
144 144
145 145 // Radial grid line
146 146 QRectF gridRect;
147 147 gridRect.setWidth(radialCoordinate * 2.0);
148 148 gridRect.setHeight(radialCoordinate * 2.0);
149 149 gridRect.moveCenter(center);
150 150
151 151 gridItem->setRect(gridRect);
152 152 gridItem->setVisible(true);
153 153
154 154 // Tick
155 155 QLineF tickLine(-tickWidth(), 0.0, tickWidth(), 0.0);
156 156 tickLine.translate(center.rx(), gridRect.top());
157 157 tickItem->setLine(tickLine);
158 158 tickItem->setVisible(true);
159 159
160 160 // Shades
161 161 if (i % 2 || (i == 0 && !nextTickVisible)) {
162 162 QPainterPath path;
163 163 if (i == 0) {
164 164 // If first tick is also the last, we need to custom fill the inner circle
165 165 // or it won't get filled.
166 166 QRectF innerCircle(0.0, 0.0, layout.at(0) * 2.0, layout.at(0) * 2.0);
167 167 innerCircle.moveCenter(center);
168 168 path.addEllipse(innerCircle);
169 169 } else {
170 170 QRectF otherGridRect;
171 171 if (!nextTickVisible) { // Last visible tick
172 172 otherGridRect = axisGeometry();
173 173 } else {
174 174 qreal otherGridRectDimension = layout.at(i + 1) * 2.0;
175 175 otherGridRect.setWidth(otherGridRectDimension);
176 176 otherGridRect.setHeight(otherGridRectDimension);
177 177 otherGridRect.moveCenter(center);
178 178 }
179 179 path.addEllipse(gridRect);
180 180 path.addEllipse(otherGridRect);
181 181
182 182 // Add additional shading in first visible shade item if there is a partial tick
183 183 // to be filled at the center (log & category axes)
184 184 if (firstShade) {
185 185 QGraphicsPathItem *specialShadeItem = static_cast<QGraphicsPathItem *>(shadeItemList.at(0));
186 186 if (layout.at(i - 1) > 0.0) {
187 187 QRectF innerCircle(0.0, 0.0, layout.at(i - 1) * 2.0, layout.at(i - 1) * 2.0);
188 188 innerCircle.moveCenter(center);
189 189 QPainterPath specialPath;
190 190 specialPath.addEllipse(innerCircle);
191 191 specialShadeItem->setPath(specialPath);
192 192 specialShadeItem->setVisible(true);
193 193 } else {
194 194 specialShadeItem->setVisible(false);
195 195 }
196 196 }
197 197 }
198 198 shadeItem->setPath(path);
199 199 shadeItem->setVisible(true);
200 200 firstShade = false;
201 201 }
202 202 }
203 203
204 204 // Title, along the 0 axis
205 205 QString titleText = axis()->titleText();
206 206 if (!titleText.isEmpty() && axis()->isTitleVisible()) {
207 207 QRectF dummyRect;
208 title->setHtml(ChartPresenter::truncatedText(axis()->titleFont(), titleText, qreal(0.0), radius, Qt::Horizontal, dummyRect));
208 title->setHtml(ChartPresenter::truncatedText(axis()->titleFont(), titleText, qreal(0.0),
209 radius, radius, dummyRect));
209 210
210 211 QRectF titleBoundingRect = title->boundingRect();
211 212 QPointF titleCenter = titleBoundingRect.center();
212 213 QPointF arrowCenter = axisLine->boundingRect().center();
213 214 QPointF titleCenterDiff = arrowCenter - titleCenter;
214 215 title->setPos(titleCenterDiff.x() - titlePadding() - (titleBoundingRect.height() / 2.0), titleCenterDiff.y());
215 216 title->setTransformOriginPoint(titleCenter);
216 217 title->setRotation(270.0);
217 218 }
218 219
219 220 QGraphicsLayoutItem::updateGeometry();
220 221 }
221 222
222 223 Qt::Orientation PolarChartAxisRadial::orientation() const
223 224 {
224 225 return Qt::Vertical;
225 226 }
226 227
227 228 void PolarChartAxisRadial::createItems(int count)
228 229 {
229 230 if (arrowItems().count() == 0) {
230 231 // radial axis center line
231 232 QGraphicsLineItem *arrow = new LineArrowItem(this, presenter()->rootItem());
232 233 arrow->setPen(axis()->linePen());
233 234 arrowGroup()->addToGroup(arrow);
234 235 }
235 236
236 237 for (int i = 0; i < count; ++i) {
237 238 QGraphicsLineItem *arrow = new QGraphicsLineItem(presenter()->rootItem());
238 239 QGraphicsEllipseItem *grid = new QGraphicsEllipseItem(presenter()->rootItem());
239 240 QGraphicsTextItem *label = new QGraphicsTextItem(presenter()->rootItem());
240 241 label->document()->setDocumentMargin(ChartPresenter::textMargin());
241 242 QGraphicsTextItem *title = titleItem();
242 243 arrow->setPen(axis()->linePen());
243 244 grid->setPen(axis()->gridLinePen());
244 245 label->setFont(axis()->labelsFont());
245 246 label->setDefaultTextColor(axis()->labelsBrush().color());
246 247 label->setRotation(axis()->labelsAngle());
247 248 title->setFont(axis()->titleFont());
248 249 title->setDefaultTextColor(axis()->titleBrush().color());
249 250 title->setHtml(axis()->titleText());
250 251 arrowGroup()->addToGroup(arrow);
251 252 gridGroup()->addToGroup(grid);
252 253 labelGroup()->addToGroup(label);
253 254 if (gridItems().size() == 1 || (((gridItems().size() + 1) % 2) && gridItems().size() > 0)) {
254 255 QGraphicsPathItem *shade = new QGraphicsPathItem(presenter()->rootItem());
255 256 shade->setPen(axis()->shadesPen());
256 257 shade->setBrush(axis()->shadesBrush());
257 258 shadeGroup()->addToGroup(shade);
258 259 }
259 260 }
260 261 }
261 262
262 263 void PolarChartAxisRadial::handleArrowPenChanged(const QPen &pen)
263 264 {
264 265 foreach (QGraphicsItem *item, arrowItems())
265 266 static_cast<QGraphicsLineItem *>(item)->setPen(pen);
266 267 }
267 268
268 269 void PolarChartAxisRadial::handleGridPenChanged(const QPen &pen)
269 270 {
270 271 foreach (QGraphicsItem *item, gridItems())
271 272 static_cast<QGraphicsEllipseItem *>(item)->setPen(pen);
272 273 }
273 274
274 275 QSizeF PolarChartAxisRadial::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const
275 276 {
276 277 Q_UNUSED(which);
277 278 Q_UNUSED(constraint);
278 279 return QSizeF(-1.0, -1.0);
279 280 }
280 281
281 282 qreal PolarChartAxisRadial::preferredAxisRadius(const QSizeF &maxSize)
282 283 {
283 284 qreal radius = maxSize.height() / 2.0;
284 285 if (maxSize.width() < maxSize.height())
285 286 radius = maxSize.width() / 2.0;
286 287 return radius;
287 288 }
288 289
289 290 #include "moc_polarchartaxisradial_p.cpp"
290 291
291 292 QTCOMMERCIALCHART_END_NAMESPACE
@@ -1,229 +1,225
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 "verticalaxis_p.h"
22 22 #include "qabstractaxis.h"
23 23 #include "chartpresenter_p.h"
24 24 #include <QDebug>
25 25
26 26 QTCOMMERCIALCHART_BEGIN_NAMESPACE
27 27
28 28 VerticalAxis::VerticalAxis(QAbstractAxis *axis, QGraphicsItem *item, bool intervalAxis)
29 29 : CartesianChartAxis(axis, item, intervalAxis)
30 30 {
31 31 }
32 32
33 33 VerticalAxis::~VerticalAxis()
34 34 {
35 35 }
36 36
37 37 void VerticalAxis::updateGeometry()
38 38 {
39 39 const QVector<qreal> &layout = ChartAxisElement::layout();
40 40
41 41 if (layout.isEmpty())
42 42 return;
43 43
44 44 QStringList labelList = labels();
45 45
46 46 QList<QGraphicsItem *> lines = gridItems();
47 47 QList<QGraphicsItem *> labels = labelItems();
48 48 QList<QGraphicsItem *> shades = shadeItems();
49 49 QList<QGraphicsItem *> arrow = arrowItems();
50 50 QGraphicsTextItem *title = titleItem();
51 51
52 52 Q_ASSERT(labels.size() == labelList.size());
53 53 Q_ASSERT(layout.size() == labelList.size());
54 54
55 55 const QRectF &axisRect = axisGeometry();
56 56 const QRectF &gridRect = gridGeometry();
57 57
58 58 qreal height = axisRect.bottom();
59 59
60 60
61 61 //arrow
62 62 QGraphicsLineItem *arrowItem = static_cast<QGraphicsLineItem*>(arrow.at(0));
63 63
64 64 //arrow position
65 65 if (axis()->alignment() == Qt::AlignLeft)
66 66 arrowItem->setLine(axisRect.right(), gridRect.top(), axisRect.right(), gridRect.bottom());
67 67 else if (axis()->alignment() == Qt::AlignRight)
68 68 arrowItem->setLine(axisRect.left(), gridRect.top(), axisRect.left(), gridRect.bottom());
69 69
70 70 //title
71 71 QRectF titleBoundingRect;
72 72 QString titleText = axis()->titleText();
73 73 qreal availableSpace = axisRect.width() - labelPadding();
74 74 if (!titleText.isEmpty() && titleItem()->isVisible()) {
75 75 availableSpace -= titlePadding() * 2.0;
76 76 qreal minimumLabelWidth = ChartPresenter::textBoundingRect(axis()->labelsFont(), "...").width();
77 QString truncatedTitle = ChartPresenter::truncatedText(axis()->titleFont(), titleText, qreal(0.0),
78 gridRect.height(), Qt::Horizontal, titleBoundingRect);
79 77 qreal titleSpace = availableSpace - minimumLabelWidth;
80 if (titleSpace < titleBoundingRect.height()) {
81 // Need to also truncate title vertically (multiline title)
82 bool skip = false;
83 if (truncatedTitle.endsWith("...")) {
84 if (truncatedTitle.size() == 3)
85 skip = true; // Already truncated to minimum
86 else
87 truncatedTitle.chop(3);
88 }
89 if (!skip)
90 truncatedTitle = ChartPresenter::truncatedText(axis()->titleFont(), truncatedTitle, qreal(0.0),
91 titleSpace, Qt::Vertical, titleBoundingRect);
92 }
93 title->setHtml(truncatedTitle);
78 title->setHtml(ChartPresenter::truncatedText(axis()->titleFont(), titleText, qreal(90.0),
79 titleSpace, gridRect.height(),
80 titleBoundingRect));
94 81
95 82 titleBoundingRect = title->boundingRect();
96 83
97 84 QPointF center = gridRect.center() - titleBoundingRect.center();
98 85 if (axis()->alignment() == Qt::AlignLeft)
99 86 title->setPos(axisRect.left() - titleBoundingRect.width() / 2.0 + titleBoundingRect.height() / 2.0 + titlePadding(), center.y());
100 87 else if (axis()->alignment() == Qt::AlignRight)
101 88 title->setPos(axisRect.right() - titleBoundingRect.width() / 2.0 - titleBoundingRect.height() / 2.0 - titlePadding(), center.y());
102 89
103 90 title->setTransformOriginPoint(titleBoundingRect.center());
104 91 title->setRotation(270);
105 92
106 93 availableSpace -= titleBoundingRect.height();
107 94 }
108 95
109 96 for (int i = 0; i < layout.size(); ++i) {
110 97 //items
111 98 QGraphicsLineItem *gridItem = static_cast<QGraphicsLineItem *>(lines.at(i));
112 99 QGraphicsLineItem *tickItem = static_cast<QGraphicsLineItem *>(arrow.at(i + 1));
113 100 QGraphicsTextItem *labelItem = static_cast<QGraphicsTextItem *>(labels.at(i));
114 101
115 102 //grid line
116 103 gridItem->setLine(gridRect.left(), layout[i], gridRect.right(), layout[i]);
117 104
118 105 //label text wrapping
119 106 QString text = labelList.at(i);
120 107 QRectF boundingRect;
121 labelItem->setHtml(ChartPresenter::truncatedText(axis()->labelsFont(), text, axis()->labelsAngle(),
122 availableSpace, Qt::Horizontal, boundingRect));
108 // don't truncate empty labels
109 if (text.isEmpty()) {
110 labelItem->setHtml(text);
111 } else {
112 qreal labelHeight = (axisRect.height() / layout.count()) - (2 * labelPadding());
113 labelItem->setHtml(ChartPresenter::truncatedText(axis()->labelsFont(), text,
114 axis()->labelsAngle(), availableSpace,
115 labelHeight, boundingRect));
116 }
123 117
124 118 //label transformation origin point
125 119 const QRectF &rect = labelItem->boundingRect();
126 120 QPointF center = rect.center();
127 121 labelItem->setTransformOriginPoint(center.x(), center.y());
128 122 qreal widthDiff = rect.width() - boundingRect.width();
129 123 qreal heightDiff = rect.height() - boundingRect.height();
130 124
131 125 //ticks and label position
132 126 if (axis()->alignment() == Qt::AlignLeft) {
133 127 labelItem->setPos(axisRect.right() - rect.width() + (widthDiff / 2.0) - labelPadding(), layout[i] - center.y());
134 128 tickItem->setLine(axisRect.right() - labelPadding(), layout[i], axisRect.right(), layout[i]);
135 129 } else if (axis()->alignment() == Qt::AlignRight) {
136 130 labelItem->setPos(axisRect.left() + labelPadding() - (widthDiff / 2.0), layout[i] - center.y());
137 131 tickItem->setLine(axisRect.left(), layout[i], axisRect.left() + labelPadding(), layout[i]);
138 132 }
139 133
140 134 //label in between
141 135 bool forceHide = false;
142 136 if (intervalAxis() && (i + 1) != layout.size()) {
143 137 qreal lowerBound = qMin(layout[i], gridRect.bottom());
144 138 qreal upperBound = qMax(layout[i + 1], gridRect.top());
145 139 const qreal delta = lowerBound - upperBound;
146 140 // Hide label in case visible part of the category at the grid edge is too narrow
147 141 if (delta < boundingRect.height()
148 && (lowerBound == gridRect.bottom() || upperBound == gridRect.top())) {
142 && (lowerBound == gridRect.bottom() || upperBound == gridRect.top())
143 && !intervalAxis()) {
149 144 forceHide = true;
150 145 } else {
151 146 labelItem->setPos(labelItem->pos().x() , lowerBound - (delta / 2.0) - center.y());
152 147 }
153 148 }
154 149
155 150 //label overlap detection - compensate one pixel for rounding errors
156 if (labelItem->pos().y() + boundingRect.height() > height || forceHide ||
151 if ((labelItem->pos().y() + boundingRect.height() > height || forceHide ||
157 152 (labelItem->pos().y() + (heightDiff / 2.0) - 1.0) > axisRect.bottom() ||
158 labelItem->pos().y() + (heightDiff / 2.0) < (axisRect.top() - 1.0)) {
153 labelItem->pos().y() + (heightDiff / 2.0) < (axisRect.top() - 1.0))
154 && !intervalAxis()) {
159 155 labelItem->setVisible(false);
160 156 }
161 157 else {
162 158 labelItem->setVisible(true);
163 159 height=labelItem->pos().y();
164 160 }
165 161
166 162 //shades
167 163 if ((i + 1) % 2 && i > 1) {
168 164 QGraphicsRectItem *rectItem = static_cast<QGraphicsRectItem *>(shades.at(i / 2 - 1));
169 165 qreal lowerBound = qMin(layout[i - 1], gridRect.bottom());
170 166 qreal upperBound = qMax(layout[i], gridRect.top());
171 167 rectItem->setRect(gridRect.left(), upperBound, gridRect.width(), lowerBound - upperBound);
172 168 if (rectItem->rect().height() <= 0.0)
173 169 rectItem->setVisible(false);
174 170 else
175 171 rectItem->setVisible(true);
176 172 }
177 173
178 174 // check if the grid line and the axis tick should be shown
179 175 qreal y = gridItem->line().p1().y();
180 176 if ((y < gridRect.top() || y > gridRect.bottom()))
181 177 {
182 178 gridItem->setVisible(false);
183 179 tickItem->setVisible(false);
184 180 }else{
185 181 gridItem->setVisible(true);
186 182 tickItem->setVisible(true);
187 183 }
188 184
189 185 }
190 186 //begin/end grid line in case labels between
191 187 if (intervalAxis()) {
192 188 QGraphicsLineItem *gridLine;
193 189 gridLine = static_cast<QGraphicsLineItem *>(lines.at(layout.size()));
194 190 gridLine->setLine(gridRect.left(), gridRect.top(), gridRect.right(), gridRect.top());
195 191 gridLine->setVisible(true);
196 192 gridLine = static_cast<QGraphicsLineItem*>(lines.at(layout.size() + 1));
197 193 gridLine->setLine(gridRect.left(), gridRect.bottom(), gridRect.right(), gridRect.bottom());
198 194 gridLine->setVisible(true);
199 195 }
200 196 }
201 197
202 198 QSizeF VerticalAxis::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const
203 199 {
204 200 Q_UNUSED(constraint);
205 201 QSizeF sh(0, 0);
206 202
207 203 if (axis()->titleText().isEmpty() || !titleItem()->isVisible())
208 204 return sh;
209 205
210 206 switch (which) {
211 207 case Qt::MinimumSize: {
212 208 QRectF titleRect = ChartPresenter::textBoundingRect(axis()->titleFont(), "...");
213 209 sh = QSizeF(titleRect.height() + (titlePadding() * 2.0), titleRect.width());
214 210 break;
215 211 }
216 212 case Qt::MaximumSize:
217 213 case Qt::PreferredSize: {
218 214 QRectF titleRect = ChartPresenter::textBoundingRect(axis()->titleFont(), axis()->titleText());
219 215 sh = QSizeF(titleRect.height() + (titlePadding() * 2.0), titleRect.width());
220 216 break;
221 217 }
222 218 default:
223 219 break;
224 220 }
225 221
226 222 return sh;
227 223 }
228 224
229 225 QTCOMMERCIALCHART_END_NAMESPACE
@@ -1,484 +1,480
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 #include "chartpresenter_p.h"
21 21 #include "qchart.h"
22 22 #include "chartitem_p.h"
23 23 #include "qchart_p.h"
24 24 #include "qabstractaxis.h"
25 25 #include "qabstractaxis_p.h"
26 26 #include "chartdataset_p.h"
27 27 #include "chartanimation_p.h"
28 28 #include "qabstractseries_p.h"
29 29 #include "qareaseries.h"
30 30 #include "chartaxiselement_p.h"
31 31 #include "chartbackground_p.h"
32 32 #include "cartesianchartlayout_p.h"
33 33 #include "polarchartlayout_p.h"
34 34 #include "charttitle_p.h"
35 35 #include <QTimer>
36 36 #include <QTextDocument>
37 37
38 38 QTCOMMERCIALCHART_BEGIN_NAMESPACE
39 39
40 40 ChartPresenter::ChartPresenter(QChart *chart, QChart::ChartType type)
41 41 : QObject(chart),
42 42 m_chart(chart),
43 43 m_options(QChart::NoAnimation),
44 44 m_state(ShowState),
45 45 m_background(0),
46 46 m_plotAreaBackground(0),
47 47 m_title(0)
48 48 {
49 49 if (type == QChart::ChartTypeCartesian)
50 50 m_layout = new CartesianChartLayout(this);
51 51 else if (type == QChart::ChartTypePolar)
52 52 m_layout = new PolarChartLayout(this);
53 53 Q_ASSERT(m_layout);
54 54 }
55 55
56 56 ChartPresenter::~ChartPresenter()
57 57 {
58 58
59 59 }
60 60
61 61 void ChartPresenter::setGeometry(const QRectF rect)
62 62 {
63 63 if (m_rect != rect) {
64 64 m_rect = rect;
65 65 foreach (ChartItem *chart, m_chartItems) {
66 66 chart->domain()->setSize(rect.size());
67 67 chart->setPos(rect.topLeft());
68 68 }
69 69 }
70 70 }
71 71
72 72 QRectF ChartPresenter::geometry() const
73 73 {
74 74 return m_rect;
75 75 }
76 76
77 77 void ChartPresenter::handleAxisAdded(QAbstractAxis *axis)
78 78 {
79 79 axis->d_ptr->initializeGraphics(rootItem());
80 80 axis->d_ptr->initializeAnimations(m_options);
81 81 ChartAxisElement *item = axis->d_ptr->axisItem();
82 82 item->setPresenter(this);
83 83 item->setThemeManager(m_chart->d_ptr->m_themeManager);
84 84 m_axisItems<<item;
85 85 m_axes<<axis;
86 86 m_layout->invalidate();
87 87 }
88 88
89 89 void ChartPresenter::handleAxisRemoved(QAbstractAxis *axis)
90 90 {
91 91 ChartAxisElement *item = axis->d_ptr->m_item.take();
92 92 item->hide();
93 93 item->disconnect();
94 94 item->deleteLater();
95 95 m_axisItems.removeAll(item);
96 96 m_axes.removeAll(axis);
97 97 m_layout->invalidate();
98 98 }
99 99
100 100
101 101 void ChartPresenter::handleSeriesAdded(QAbstractSeries *series)
102 102 {
103 103 series->d_ptr->initializeGraphics(rootItem());
104 104 series->d_ptr->initializeAnimations(m_options);
105 105 ChartItem *chart = series->d_ptr->chartItem();
106 106 chart->setPresenter(this);
107 107 chart->setThemeManager(m_chart->d_ptr->m_themeManager);
108 108 chart->domain()->setSize(m_rect.size());
109 109 chart->setPos(m_rect.topLeft());
110 110 chart->handleDomainUpdated(); //this could be moved to intializeGraphics when animator is refactored
111 111 m_chartItems<<chart;
112 112 m_series<<series;
113 113 m_layout->invalidate();
114 114 }
115 115
116 116 void ChartPresenter::handleSeriesRemoved(QAbstractSeries *series)
117 117 {
118 118 ChartItem *chart = series->d_ptr->m_item.take();
119 119 chart->hide();
120 120 chart->disconnect();
121 121 chart->deleteLater();
122 122 m_chartItems.removeAll(chart);
123 123 m_series.removeAll(series);
124 124 m_layout->invalidate();
125 125 }
126 126
127 127 void ChartPresenter::setAnimationOptions(QChart::AnimationOptions options)
128 128 {
129 129 if (m_options != options) {
130 130 QChart::AnimationOptions oldOptions = m_options;
131 131 m_options = options;
132 132 if (options.testFlag(QChart::SeriesAnimations) != oldOptions.testFlag(QChart::SeriesAnimations)) {
133 133 foreach (QAbstractSeries *series, m_series)
134 134 series->d_ptr->initializeAnimations(m_options);
135 135 }
136 136 if (options.testFlag(QChart::GridAxisAnimations) != oldOptions.testFlag(QChart::GridAxisAnimations)) {
137 137 foreach (QAbstractAxis *axis, m_axes)
138 138 axis->d_ptr->initializeAnimations(m_options);
139 139 }
140 140 m_layout->invalidate(); // So that existing animations don't just stop halfway
141 141 }
142 142 }
143 143
144 144 void ChartPresenter::setState(State state,QPointF point)
145 145 {
146 146 m_state=state;
147 147 m_statePoint=point;
148 148 }
149 149
150 150 QChart::AnimationOptions ChartPresenter::animationOptions() const
151 151 {
152 152 return m_options;
153 153 }
154 154
155 155 void ChartPresenter::createBackgroundItem()
156 156 {
157 157 if (!m_background) {
158 158 m_background = new ChartBackground(rootItem());
159 159 m_background->setPen(Qt::NoPen); // Theme doesn't touch pen so don't use default
160 160 m_background->setBrush(QChartPrivate::defaultBrush());
161 161 m_background->setZValue(ChartPresenter::BackgroundZValue);
162 162 }
163 163 }
164 164
165 165 void ChartPresenter::createPlotAreaBackgroundItem()
166 166 {
167 167 if (!m_plotAreaBackground) {
168 168 if (m_chart->chartType() == QChart::ChartTypeCartesian)
169 169 m_plotAreaBackground = new QGraphicsRectItem(rootItem());
170 170 else
171 171 m_plotAreaBackground = new QGraphicsEllipseItem(rootItem());
172 172 // Use transparent pen instead of Qt::NoPen, as Qt::NoPen causes
173 173 // antialising artifacts with axis lines for some reason.
174 174 m_plotAreaBackground->setPen(QPen(Qt::transparent));
175 175 m_plotAreaBackground->setBrush(Qt::NoBrush);
176 176 m_plotAreaBackground->setZValue(ChartPresenter::PlotAreaZValue);
177 177 m_plotAreaBackground->setVisible(false);
178 178 }
179 179 }
180 180
181 181 void ChartPresenter::createTitleItem()
182 182 {
183 183 if (!m_title) {
184 184 m_title = new ChartTitle(rootItem());
185 185 m_title->setZValue(ChartPresenter::BackgroundZValue);
186 186 }
187 187 }
188 188
189 189 void ChartPresenter::startAnimation(ChartAnimation *animation)
190 190 {
191 191 animation->stop();
192 192 QTimer::singleShot(0, animation, SLOT(startChartAnimation()));
193 193 }
194 194
195 195 void ChartPresenter::setBackgroundBrush(const QBrush &brush)
196 196 {
197 197 createBackgroundItem();
198 198 m_background->setBrush(brush);
199 199 m_layout->invalidate();
200 200 }
201 201
202 202 QBrush ChartPresenter::backgroundBrush() const
203 203 {
204 204 if (!m_background)
205 205 return QBrush();
206 206 return m_background->brush();
207 207 }
208 208
209 209 void ChartPresenter::setBackgroundPen(const QPen &pen)
210 210 {
211 211 createBackgroundItem();
212 212 m_background->setPen(pen);
213 213 m_layout->invalidate();
214 214 }
215 215
216 216 QPen ChartPresenter::backgroundPen() const
217 217 {
218 218 if (!m_background)
219 219 return QPen();
220 220 return m_background->pen();
221 221 }
222 222
223 223 void ChartPresenter::setBackgroundRoundness(qreal diameter)
224 224 {
225 225 createBackgroundItem();
226 226 m_background->setDiameter(diameter);
227 227 m_layout->invalidate();
228 228 }
229 229
230 230 qreal ChartPresenter::backgroundRoundness() const
231 231 {
232 232 if (!m_background)
233 233 return 0;
234 234 return m_background->diameter();
235 235 }
236 236
237 237 void ChartPresenter::setPlotAreaBackgroundBrush(const QBrush &brush)
238 238 {
239 239 createPlotAreaBackgroundItem();
240 240 m_plotAreaBackground->setBrush(brush);
241 241 m_layout->invalidate();
242 242 }
243 243
244 244 QBrush ChartPresenter::plotAreaBackgroundBrush() const
245 245 {
246 246 if (!m_plotAreaBackground)
247 247 return QBrush();
248 248 return m_plotAreaBackground->brush();
249 249 }
250 250
251 251 void ChartPresenter::setPlotAreaBackgroundPen(const QPen &pen)
252 252 {
253 253 createPlotAreaBackgroundItem();
254 254 m_plotAreaBackground->setPen(pen);
255 255 m_layout->invalidate();
256 256 }
257 257
258 258 QPen ChartPresenter::plotAreaBackgroundPen() const
259 259 {
260 260 if (!m_plotAreaBackground)
261 261 return QPen();
262 262 return m_plotAreaBackground->pen();
263 263 }
264 264
265 265 void ChartPresenter::setTitle(const QString &title)
266 266 {
267 267 createTitleItem();
268 268 m_title->setText(title);
269 269 m_layout->invalidate();
270 270 }
271 271
272 272 QString ChartPresenter::title() const
273 273 {
274 274 if (!m_title)
275 275 return QString();
276 276 return m_title->text();
277 277 }
278 278
279 279 void ChartPresenter::setTitleFont(const QFont &font)
280 280 {
281 281 createTitleItem();
282 282 m_title->setFont(font);
283 283 m_layout->invalidate();
284 284 }
285 285
286 286 QFont ChartPresenter::titleFont() const
287 287 {
288 288 if (!m_title)
289 289 return QFont();
290 290 return m_title->font();
291 291 }
292 292
293 293 void ChartPresenter::setTitleBrush(const QBrush &brush)
294 294 {
295 295 createTitleItem();
296 296 m_title->setDefaultTextColor(brush.color());
297 297 m_layout->invalidate();
298 298 }
299 299
300 300 QBrush ChartPresenter::titleBrush() const
301 301 {
302 302 if (!m_title)
303 303 return QBrush();
304 304 return QBrush(m_title->defaultTextColor());
305 305 }
306 306
307 307 void ChartPresenter::setBackgroundVisible(bool visible)
308 308 {
309 309 createBackgroundItem();
310 310 m_background->setVisible(visible);
311 311 }
312 312
313 313
314 314 bool ChartPresenter::isBackgroundVisible() const
315 315 {
316 316 if (!m_background)
317 317 return false;
318 318 return m_background->isVisible();
319 319 }
320 320
321 321 void ChartPresenter::setPlotAreaBackgroundVisible(bool visible)
322 322 {
323 323 createPlotAreaBackgroundItem();
324 324 m_plotAreaBackground->setVisible(visible);
325 325 }
326 326
327 327 bool ChartPresenter::isPlotAreaBackgroundVisible() const
328 328 {
329 329 if (!m_plotAreaBackground)
330 330 return false;
331 331 return m_plotAreaBackground->isVisible();
332 332 }
333 333
334 334 void ChartPresenter::setBackgroundDropShadowEnabled(bool enabled)
335 335 {
336 336 createBackgroundItem();
337 337 m_background->setDropShadowEnabled(enabled);
338 338 }
339 339
340 340 bool ChartPresenter::isBackgroundDropShadowEnabled() const
341 341 {
342 342 if (!m_background)
343 343 return false;
344 344 return m_background->isDropShadowEnabled();
345 345 }
346 346
347 347
348 348 AbstractChartLayout *ChartPresenter::layout()
349 349 {
350 350 return m_layout;
351 351 }
352 352
353 353 QLegend *ChartPresenter::legend()
354 354 {
355 355 return m_chart->legend();
356 356 }
357 357
358 358 void ChartPresenter::setVisible(bool visible)
359 359 {
360 360 m_chart->setVisible(visible);
361 361 }
362 362
363 363 ChartBackground *ChartPresenter::backgroundElement()
364 364 {
365 365 return m_background;
366 366 }
367 367
368 368 QAbstractGraphicsShapeItem *ChartPresenter::plotAreaElement()
369 369 {
370 370 return m_plotAreaBackground;
371 371 }
372 372
373 373 QList<ChartAxisElement *> ChartPresenter::axisItems() const
374 374 {
375 375 return m_axisItems;
376 376 }
377 377
378 378 QList<ChartItem *> ChartPresenter::chartItems() const
379 379 {
380 380 return m_chartItems;
381 381 }
382 382
383 383 ChartTitle *ChartPresenter::titleElement()
384 384 {
385 385 return m_title;
386 386 }
387 387
388 388 QRectF ChartPresenter::textBoundingRect(const QFont &font, const QString &text, qreal angle)
389 389 {
390 390 static QGraphicsTextItem dummyTextItem;
391 391 static bool initMargin = true;
392 392 if (initMargin) {
393 393 dummyTextItem.document()->setDocumentMargin(textMargin());
394 394 initMargin = false;
395 395 }
396 396
397 397 dummyTextItem.setFont(font);
398 398 dummyTextItem.setHtml(text);
399 399 QRectF boundingRect = dummyTextItem.boundingRect();
400 400
401 401 // Take rotation into account
402 402 if (angle) {
403 403 QTransform transform;
404 404 transform.rotate(angle);
405 405 boundingRect = transform.mapRect(boundingRect);
406 406 }
407 407
408 408 return boundingRect;
409 409 }
410 410
411 411 // boundingRect parameter returns the rotated bounding rect of the text
412 412 QString ChartPresenter::truncatedText(const QFont &font, const QString &text, qreal angle,
413 qreal maxSize, Qt::Orientation constraintOrientation,
414 QRectF &boundingRect)
413 qreal maxWidth, qreal maxHeight, QRectF &boundingRect)
415 414 {
416 415 QString truncatedString(text);
417 416 boundingRect = textBoundingRect(font, truncatedString, angle);
418 qreal checkDimension = ((constraintOrientation == Qt::Horizontal)
419 ? boundingRect.width() : boundingRect.height());
420 if (checkDimension > maxSize) {
417 if (boundingRect.width() > maxWidth || boundingRect.height() > maxHeight) {
421 418 // It can be assumed that almost any amount of string manipulation is faster
422 419 // than calculating one bounding rectangle, so first prepare a list of truncated strings
423 420 // to try.
424 421 static const char *truncateMatchString = "&#?[0-9a-zA-Z]*;$";
425 422 static QRegExp truncateMatcher(truncateMatchString);
426 423
427 424 QVector<QString> testStrings(text.length());
428 425 int count(0);
429 426 static QLatin1Char closeTag('>');
430 427 static QLatin1Char openTag('<');
431 428 static QLatin1Char semiColon(';');
432 429 static QLatin1String ellipsis("...");
433 430 while (truncatedString.length() > 1) {
434 431 int chopIndex(-1);
435 432 int chopCount(1);
436 433 QChar lastChar(truncatedString.at(truncatedString.length() - 1));
437 434
438 435 if (lastChar == closeTag)
439 436 chopIndex = truncatedString.lastIndexOf(openTag);
440 437 else if (lastChar == semiColon)
441 438 chopIndex = truncateMatcher.indexIn(truncatedString, 0);
442 439
443 440 if (chopIndex != -1)
444 441 chopCount = truncatedString.length() - chopIndex;
445 442 truncatedString.chop(chopCount);
446 443 testStrings[count] = truncatedString + ellipsis;
447 444 count++;
448 445 }
449 446
450 447 // Binary search for best fit
451 448 int minIndex(0);
452 449 int maxIndex(count - 1);
453 450 int bestIndex(count);
454 451 QRectF checkRect;
452
455 453 while (maxIndex >= minIndex) {
456 454 int mid = (maxIndex + minIndex) / 2;
457 455 checkRect = textBoundingRect(font, testStrings.at(mid), angle);
458 checkDimension = ((constraintOrientation == Qt::Horizontal)
459 ? checkRect.width() : checkRect.height());
460 if (checkDimension > maxSize) {
456 if (checkRect.width() > maxWidth || checkRect.height() > maxHeight) {
461 457 // Checked index too large, all under this are also too large
462 458 minIndex = mid + 1;
463 459 } else {
464 460 // Checked index fits, all over this also fit
465 461 maxIndex = mid - 1;
466 462 bestIndex = mid;
467 463 boundingRect = checkRect;
468 464 }
469 465 }
470 466 // Default to "..." if nothing fits
471 467 if (bestIndex == count) {
472 468 boundingRect = textBoundingRect(font, ellipsis, angle);
473 469 truncatedString = ellipsis;
474 470 } else {
475 471 truncatedString = testStrings.at(bestIndex);
476 472 }
477 473 }
478 474
479 475 return truncatedString;
480 476 }
481 477
482 478 #include "moc_chartpresenter_p.cpp"
483 479
484 480 QTCOMMERCIALCHART_END_NAMESPACE
@@ -1,180 +1,180
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 CHARTPRESENTER_H
31 31 #define CHARTPRESENTER_H
32 32
33 33 #include "qchartglobal.h"
34 34 #include "qchart.h" //because of QChart::ChartThemeId
35 35 #include <QRectF>
36 36 #include <QMargins>
37 37
38 38 QTCOMMERCIALCHART_BEGIN_NAMESPACE
39 39
40 40 class ChartItem;
41 41 class AxisItem;
42 42 class QAbstractSeries;
43 43 class ChartDataSet;
44 44 class AbstractDomain;
45 45 class ChartAxisElement;
46 46 class ChartAnimator;
47 47 class ChartBackground;
48 48 class ChartTitle;
49 49 class ChartAnimation;
50 50 class AbstractChartLayout;
51 51
52 52 class ChartPresenter: public QObject
53 53 {
54 54 Q_OBJECT
55 55 public:
56 56 enum ZValues {
57 57 BackgroundZValue = -1,
58 58 PlotAreaZValue,
59 59 ShadesZValue,
60 60 GridZValue,
61 61 AxisZValue,
62 62 SeriesZValue,
63 63 LineChartZValue = SeriesZValue,
64 64 SplineChartZValue = SeriesZValue,
65 65 BarSeriesZValue = SeriesZValue,
66 66 ScatterSeriesZValue = SeriesZValue,
67 67 PieSeriesZValue = SeriesZValue,
68 68 BoxPlotSeriesZValue = SeriesZValue,
69 69 LegendZValue,
70 70 TopMostZValue
71 71 };
72 72
73 73 enum State {
74 74 ShowState,
75 75 ScrollUpState,
76 76 ScrollDownState,
77 77 ScrollLeftState,
78 78 ScrollRightState,
79 79 ZoomInState,
80 80 ZoomOutState
81 81 };
82 82
83 83 ChartPresenter(QChart *chart, QChart::ChartType type);
84 84 virtual ~ChartPresenter();
85 85
86 86
87 87 void setGeometry(QRectF rect);
88 88 QRectF geometry() const;
89 89
90 90 QGraphicsItem *rootItem(){ return m_chart; }
91 91 ChartBackground *backgroundElement();
92 92 QAbstractGraphicsShapeItem *plotAreaElement();
93 93 ChartTitle *titleElement();
94 94 QList<ChartAxisElement *> axisItems() const;
95 95 QList<ChartItem *> chartItems() const;
96 96
97 97 QLegend *legend();
98 98
99 99 void setBackgroundBrush(const QBrush &brush);
100 100 QBrush backgroundBrush() const;
101 101
102 102 void setBackgroundPen(const QPen &pen);
103 103 QPen backgroundPen() const;
104 104
105 105 void setBackgroundRoundness(qreal diameter);
106 106 qreal backgroundRoundness() const;
107 107
108 108 void setPlotAreaBackgroundBrush(const QBrush &brush);
109 109 QBrush plotAreaBackgroundBrush() const;
110 110
111 111 void setPlotAreaBackgroundPen(const QPen &pen);
112 112 QPen plotAreaBackgroundPen() const;
113 113
114 114 void setTitle(const QString &title);
115 115 QString title() const;
116 116
117 117 void setTitleFont(const QFont &font);
118 118 QFont titleFont() const;
119 119
120 120 void setTitleBrush(const QBrush &brush);
121 121 QBrush titleBrush() const;
122 122
123 123 void setBackgroundVisible(bool visible);
124 124 bool isBackgroundVisible() const;
125 125
126 126 void setPlotAreaBackgroundVisible(bool visible);
127 127 bool isPlotAreaBackgroundVisible() const;
128 128
129 129 void setBackgroundDropShadowEnabled(bool enabled);
130 130 bool isBackgroundDropShadowEnabled() const;
131 131
132 132 void setVisible(bool visible);
133 133
134 134 void setAnimationOptions(QChart::AnimationOptions options);
135 135 QChart::AnimationOptions animationOptions() const;
136 136
137 137 void startAnimation(ChartAnimation *animation);
138 138
139 139 void setState(State state,QPointF point);
140 140 State state() const { return m_state; }
141 141 QPointF statePoint() const { return m_statePoint; }
142 142 AbstractChartLayout *layout();
143 143
144 144 QChart::ChartType chartType() const { return m_chart->chartType(); }
145 145 QChart *chart() { return m_chart; }
146 146
147 147 static QRectF textBoundingRect(const QFont &font, const QString &text, qreal angle = 0.0);
148 static QString truncatedText(const QFont &font, const QString &text, qreal angle, qreal maxSize,
149 Qt::Orientation constraintOrientation, QRectF &boundingRect);
148 static QString truncatedText(const QFont &font, const QString &text, qreal angle,
149 qreal maxWidth, qreal maxHeight, QRectF &boundingRect);
150 150 inline static qreal textMargin() { return qreal(0.5); }
151 151 private:
152 152 void createBackgroundItem();
153 153 void createPlotAreaBackgroundItem();
154 154 void createTitleItem();
155 155
156 156 public Q_SLOTS:
157 157 void handleSeriesAdded(QAbstractSeries *series);
158 158 void handleSeriesRemoved(QAbstractSeries *series);
159 159 void handleAxisAdded(QAbstractAxis *axis);
160 160 void handleAxisRemoved(QAbstractAxis *axis);
161 161
162 162 private:
163 163 QChart *m_chart;
164 164 QList<ChartItem *> m_chartItems;
165 165 QList<ChartAxisElement *> m_axisItems;
166 166 QList<QAbstractSeries *> m_series;
167 167 QList<QAbstractAxis *> m_axes;
168 168 QChart::AnimationOptions m_options;
169 169 State m_state;
170 170 QPointF m_statePoint;
171 171 AbstractChartLayout *m_layout;
172 172 ChartBackground *m_background;
173 173 QAbstractGraphicsShapeItem *m_plotAreaBackground;
174 174 ChartTitle *m_title;
175 175 QRectF m_rect;
176 176 };
177 177
178 178 QTCOMMERCIALCHART_END_NAMESPACE
179 179
180 180 #endif /* CHARTPRESENTER_H */
@@ -1,88 +1,90
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 "charttitle_p.h"
22 22 #include "chartpresenter_p.h"
23 23 #include <QFont>
24 24 #include <QFontMetrics>
25 25 #include <QDebug>
26 26 #include <QTextDocument>
27 27
28 28 QTCOMMERCIALCHART_BEGIN_NAMESPACE
29 29
30 30 ChartTitle::ChartTitle(QGraphicsItem *parent)
31 31 : QGraphicsTextItem(parent)
32 32 {
33 33 document()->setDocumentMargin(ChartPresenter::textMargin());
34 34 }
35 35
36 36 ChartTitle::~ChartTitle()
37 37 {
38 38
39 39 }
40 40
41 41 void ChartTitle::setText(const QString &text)
42 42 {
43 43 m_text = text;
44 44 }
45 45
46 46 QString ChartTitle::text() const
47 47 {
48 48 return m_text;
49 49 }
50 50
51 51 void ChartTitle::setGeometry(const QRectF &rect)
52 52 {
53 53 QRectF dummyRect;
54 QGraphicsTextItem::setHtml(ChartPresenter::truncatedText(font(), m_text, qreal(0.0), rect.width(), Qt::Horizontal, dummyRect));
54 QGraphicsTextItem::setHtml(ChartPresenter::truncatedText(font(), m_text, qreal(0.0),
55 rect.width(), rect.height(),
56 dummyRect));
55 57 setPos(rect.topLeft());
56 58 }
57 59
58 60
59 61 QSizeF ChartTitle::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const
60 62 {
61 63 Q_UNUSED(constraint);
62 64 QSizeF sh;
63 65
64 66 switch (which) {
65 67 case Qt::MinimumSize: {
66 68 QRectF titleRect = ChartPresenter::textBoundingRect(font(), "...");
67 69 sh = QSizeF(titleRect.width(), titleRect.height());
68 70 break;
69 71 }
70 72 case Qt::PreferredSize:
71 73 case Qt::MaximumSize: {
72 74 QRectF titleRect = ChartPresenter::textBoundingRect(font(), m_text);
73 75 sh = QSizeF(titleRect.width(), titleRect.height());
74 76 break;
75 77 }
76 78 case Qt::MinimumDescent: {
77 79 QFontMetrics fn(font());
78 80 sh = QSizeF(0, fn.descent());
79 81 break;
80 82 }
81 83 default:
82 84 break;
83 85 }
84 86
85 87 return sh;
86 88 }
87 89
88 90 QTCOMMERCIALCHART_END_NAMESPACE
@@ -1,189 +1,190
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 <QPainter>
22 22 #include <QGraphicsSceneEvent>
23 23 #include <QGraphicsTextItem>
24 24 #include <QTextDocument>
25 25
26 26 #include "qlegend.h"
27 27 #include "qlegend_p.h"
28 28 #include "qlegendmarker.h"
29 29 #include "qlegendmarker_p.h"
30 30 #include "legendmarkeritem_p.h"
31 31 #include "chartpresenter_p.h"
32 32
33 33 QTCOMMERCIALCHART_BEGIN_NAMESPACE
34 34
35 35 LegendMarkerItem::LegendMarkerItem(QLegendMarkerPrivate *marker, QGraphicsObject *parent) :
36 36 QGraphicsObject(parent),
37 37 m_marker(marker),
38 38 m_markerRect(0,0,10.0,10.0),
39 39 m_boundingRect(0,0,0,0),
40 40 m_textItem(new QGraphicsTextItem(this)),
41 41 m_rectItem(new QGraphicsRectItem(this)),
42 42 m_margin(3),
43 43 m_space(4),
44 44 m_hovering(false),
45 45 m_pressPos(0, 0)
46 46 {
47 47 m_rectItem->setRect(m_markerRect);
48 48 m_textItem->document()->setDocumentMargin(ChartPresenter::textMargin());
49 49 setAcceptHoverEvents(true);
50 50 }
51 51
52 52 LegendMarkerItem::~LegendMarkerItem()
53 53 {
54 54 if (m_hovering) {
55 55 emit m_marker->q_ptr->hovered(false);
56 56 }
57 57 }
58 58
59 59 void LegendMarkerItem::setPen(const QPen &pen)
60 60 {
61 61 m_rectItem->setPen(pen);
62 62 }
63 63
64 64 QPen LegendMarkerItem::pen() const
65 65 {
66 66 return m_rectItem->pen();
67 67 }
68 68
69 69 void LegendMarkerItem::setBrush(const QBrush &brush)
70 70 {
71 71 m_rectItem->setBrush(brush);
72 72 }
73 73
74 74 QBrush LegendMarkerItem::brush() const
75 75 {
76 76 return m_rectItem->brush();
77 77 }
78 78
79 79 void LegendMarkerItem::setFont(const QFont &font)
80 80 {
81 81 m_textItem->setFont(font);
82 82 QFontMetrics fn(font);
83 83 m_markerRect = QRectF(0,0,fn.height()/2,fn.height()/2);
84 84 updateGeometry();
85 85 }
86 86
87 87 QFont LegendMarkerItem::font() const
88 88 {
89 89 return m_textItem->font();
90 90 }
91 91
92 92 void LegendMarkerItem::setLabel(const QString label)
93 93 {
94 94 m_label = label;
95 95 updateGeometry();
96 96 }
97 97
98 98 QString LegendMarkerItem::label() const
99 99 {
100 100 return m_label;
101 101 }
102 102
103 103 void LegendMarkerItem::setLabelBrush(const QBrush &brush)
104 104 {
105 105 m_textItem->setDefaultTextColor(brush.color());
106 106 }
107 107
108 108 QBrush LegendMarkerItem::labelBrush() const
109 109 {
110 110 return QBrush(m_textItem->defaultTextColor());
111 111 }
112 112
113 113 void LegendMarkerItem::setGeometry(const QRectF &rect)
114 114 {
115 115 qreal width = rect.width();
116 116 qreal x = m_margin + m_markerRect.width() + m_space + m_margin;
117 117 QRectF truncatedRect;
118 118
119 m_textItem->setHtml(ChartPresenter::truncatedText(m_textItem->font(), m_label, qreal(0.0), width - x, Qt::Horizontal, truncatedRect));
119 m_textItem->setHtml(ChartPresenter::truncatedText(m_textItem->font(), m_label, qreal(0.0),
120 width - x, rect.height(), truncatedRect));
120 121
121 122 qreal y = qMax(m_markerRect.height() + 2 * m_margin, truncatedRect.height() + 2 * m_margin);
122 123
123 124 const QRectF &textRect = m_textItem->boundingRect();
124 125
125 126 m_textItem->setPos(x - m_margin, y / 2 - textRect.height() / 2);
126 127 m_rectItem->setRect(m_markerRect);
127 128 // The textMargin adjustments to position are done to make default case rects less blurry with anti-aliasing
128 129 m_rectItem->setPos(m_margin - ChartPresenter::textMargin(), y / 2.0 - m_markerRect.height() / 2.0 + ChartPresenter::textMargin());
129 130
130 131 prepareGeometryChange();
131 132 m_boundingRect = QRectF(0, 0, x + textRect.width() + m_margin, y);
132 133 }
133 134
134 135 QRectF LegendMarkerItem::boundingRect() const
135 136 {
136 137 return m_boundingRect;
137 138 }
138 139
139 140 void LegendMarkerItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
140 141 {
141 142 Q_UNUSED(option)
142 143 Q_UNUSED(widget)
143 144 Q_UNUSED(painter)
144 145 }
145 146
146 147 QSizeF LegendMarkerItem::sizeHint(Qt::SizeHint which, const QSizeF& constraint) const
147 148 {
148 149 Q_UNUSED(constraint)
149 150
150 151 QSizeF sh;
151 152
152 153 switch (which) {
153 154 case Qt::MinimumSize: {
154 155 QRectF labelRect = ChartPresenter::textBoundingRect(m_textItem->font(), "...");
155 156 sh = QSizeF(labelRect.width() + (2.0 * m_margin) + m_space + m_markerRect.width(),
156 157 qMax(m_markerRect.height(), labelRect.height()) + (2.0 * m_margin));
157 158 break;
158 159 }
159 160 case Qt::PreferredSize: {
160 161 QRectF labelRect = ChartPresenter::textBoundingRect(m_textItem->font(), m_label);
161 162 sh = QSizeF(labelRect.width() + (2.0 * m_margin) + m_space + m_markerRect.width(),
162 163 qMax(m_markerRect.height(), labelRect.height()) + (2.0 * m_margin));
163 164 break;
164 165 }
165 166 default:
166 167 break;
167 168 }
168 169
169 170 return sh;
170 171 }
171 172
172 173 void LegendMarkerItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
173 174 {
174 175 Q_UNUSED(event)
175 176 m_hovering = true;
176 177 emit m_marker->q_ptr->hovered(true);
177 178 }
178 179
179 180 void LegendMarkerItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
180 181 {
181 182 Q_UNUSED(event)
182 183 m_hovering = false;
183 184 emit m_marker->q_ptr->hovered(false);
184 185 }
185 186
186 187
187 188 #include "moc_legendmarkeritem_p.cpp"
188 189
189 190 QTCOMMERCIALCHART_END_NAMESPACE
@@ -1,298 +1,300
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 <QDebug>
31 31
32 32 QTCOMMERCIALCHART_BEGIN_NAMESPACE
33 33
34 34 QPointF offset(qreal angle, qreal length)
35 35 {
36 36 qreal dx = qSin(angle * (M_PI / 180)) * length;
37 37 qreal dy = qCos(angle * (M_PI / 180)) * length;
38 38 return QPointF(dx, -dy);
39 39 }
40 40
41 41 PieSliceItem::PieSliceItem(QGraphicsItem *parent)
42 42 : QGraphicsObject(parent),
43 43 m_hovered(false)
44 44 {
45 45 setAcceptHoverEvents(true);
46 46 setAcceptedMouseButtons(Qt::MouseButtonMask);
47 47 setZValue(ChartPresenter::PieSeriesZValue);
48 48 }
49 49
50 50 PieSliceItem::~PieSliceItem()
51 51 {
52 52 // If user is hovering over the slice and it gets destroyed we do
53 53 // not get a hover leave event. So we must emit the signal here.
54 54 if (m_hovered)
55 55 emit hovered(false);
56 56 }
57 57
58 58 QRectF PieSliceItem::boundingRect() const
59 59 {
60 60 return m_boundingRect;
61 61 }
62 62
63 63 QPainterPath PieSliceItem::shape() const
64 64 {
65 65 // Don't include the label and label arm.
66 66 // This is used to detect a mouse clicks. We do not want clicks from label.
67 67 return m_slicePath;
68 68 }
69 69
70 70 void PieSliceItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
71 71 {
72 72 painter->save();
73 73 painter->setClipRect(parentItem()->boundingRect());
74 74 painter->setPen(m_data.m_slicePen);
75 75 painter->setBrush(m_data.m_sliceBrush);
76 76 painter->drawPath(m_slicePath);
77 77 painter->restore();
78 78
79 79 if (m_data.m_isLabelVisible) {
80 80 painter->save();
81 81
82 82 // Pen for label arm not defined in the QPieSeries api, let's use brush's color instead
83 83 // Also, the drawText actually uses the pen color for the text color (unlike QGraphicsSimpleTextItem)
84 84 painter->setPen(m_data.m_labelBrush.color());
85 85 painter->setBrush(m_data.m_labelBrush);
86 86 painter->setFont(m_data.m_labelFont);
87 87
88 88 QFontMetricsF fm(m_data.m_labelFont);
89 89 QString label = m_data.m_labelText;
90 90 QRectF labelBoundingRect;
91 91
92 92 switch (m_data.m_labelPosition) {
93 93 case QPieSlice::LabelOutside:
94 94 painter->setClipRect(parentItem()->boundingRect());
95 95 painter->strokePath(m_labelArmPath, m_data.m_labelBrush.color());
96 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.
97 99 label = ChartPresenter::truncatedText(m_data.m_labelFont, m_data.m_labelText,
98 100 qreal(0.0), m_labelTextRect.width(),
99 Qt::Horizontal, labelBoundingRect);
101 fm.height() + 1.0, labelBoundingRect);
100 102 }
101 103 painter->drawText(m_labelTextRect, Qt::AlignCenter, label);
102 104 break;
103 105 case QPieSlice::LabelInsideHorizontal:
104 106 painter->setClipPath(m_slicePath);
105 107 painter->drawText(m_labelTextRect, Qt::AlignCenter, m_data.m_labelText);
106 108 break;
107 109 case QPieSlice::LabelInsideTangential:
108 110 painter->setClipPath(m_slicePath);
109 111 painter->translate(m_labelTextRect.center());
110 112 painter->rotate(m_data.m_startAngle + m_data.m_angleSpan / 2);
111 113 painter->drawText(-m_labelTextRect.width() / 2, -m_labelTextRect.height() / 2, m_labelTextRect.width(), m_labelTextRect.height(), Qt::AlignCenter, m_data.m_labelText);
112 114 break;
113 115 case QPieSlice::LabelInsideNormal:
114 116 painter->setClipPath(m_slicePath);
115 117 painter->translate(m_labelTextRect.center());
116 118 if (m_data.m_startAngle + m_data.m_angleSpan / 2 < 180)
117 119 painter->rotate(m_data.m_startAngle + m_data.m_angleSpan / 2 - 90);
118 120 else
119 121 painter->rotate(m_data.m_startAngle + m_data.m_angleSpan / 2 + 90);
120 122 painter->drawText(-m_labelTextRect.width() / 2, -m_labelTextRect.height() / 2, m_labelTextRect.width(), m_labelTextRect.height(), Qt::AlignCenter, m_data.m_labelText);
121 123 break;
122 124 }
123 125
124 126 painter->restore();
125 127 }
126 128 }
127 129
128 130 void PieSliceItem::hoverEnterEvent(QGraphicsSceneHoverEvent * /*event*/)
129 131 {
130 132 m_hovered = true;
131 133 emit hovered(true);
132 134 }
133 135
134 136 void PieSliceItem::hoverLeaveEvent(QGraphicsSceneHoverEvent * /*event*/)
135 137 {
136 138 m_hovered = false;
137 139 emit hovered(false);
138 140 }
139 141
140 142 void PieSliceItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
141 143 {
142 144 emit clicked(event->buttons());
143 145 QGraphicsItem::mousePressEvent(event);
144 146 }
145 147
146 148 void PieSliceItem::setLayout(const PieSliceData &sliceData)
147 149 {
148 150 m_data = sliceData;
149 151 updateGeometry();
150 152 update();
151 153 }
152 154
153 155 void PieSliceItem::updateGeometry()
154 156 {
155 157 if (m_data.m_radius <= 0)
156 158 return;
157 159
158 160 prepareGeometryChange();
159 161
160 162 // slice path
161 163 qreal centerAngle;
162 164 QPointF armStart;
163 165 m_slicePath = slicePath(m_data.m_center, m_data.m_radius, m_data.m_startAngle, m_data.m_angleSpan, &centerAngle, &armStart);
164 166
165 167 // text rect
166 168 QFontMetricsF fm(m_data.m_labelFont);
167 169 m_labelTextRect = QRectF(0, 0, fm.width(m_data.m_labelText), fm.height());
168 170
169 171 // label arm path
170 172 QPointF labelTextStart;
171 173 m_labelArmPath = labelArmPath(armStart, centerAngle, m_data.m_radius * m_data.m_labelArmLengthFactor, m_labelTextRect.width(), &labelTextStart);
172 174
173 175 // text position
174 176 switch (m_data.m_labelPosition) {
175 177 case QPieSlice::LabelOutside:
176 178 m_labelTextRect.moveBottomLeft(labelTextStart);
177 179 if (m_labelTextRect.left() < 0)
178 180 m_labelTextRect.setLeft(0);
179 181 if (m_labelTextRect.right() > parentItem()->boundingRect().right())
180 182 m_labelTextRect.setRight(parentItem()->boundingRect().right());
181 183 break;
182 184 case QPieSlice::LabelInsideHorizontal:
183 185 case QPieSlice::LabelInsideTangential: {
184 186 QPointF textCenter;
185 187 if (m_data.m_holeRadius > 0)
186 188 textCenter = m_data.m_center + offset(centerAngle, m_data.m_holeRadius + (m_data.m_radius - m_data.m_holeRadius) / 2);
187 189 else
188 190 textCenter = m_data.m_center + offset(centerAngle, m_data.m_radius / 2);
189 191 m_labelTextRect.moveCenter(textCenter);
190 192 break;
191 193 }
192 194 case QPieSlice::LabelInsideNormal: {
193 195 QPointF textCenter;
194 196 if (m_data.m_holeRadius > 0)
195 197 textCenter = m_data.m_center + offset(centerAngle, m_data.m_holeRadius + (m_data.m_radius - m_data.m_holeRadius) / 2);
196 198 else
197 199 textCenter = m_data.m_center + offset(centerAngle, m_data.m_radius / 2);
198 200 m_labelTextRect.moveCenter(textCenter);
199 201 break;
200 202 }
201 203 }
202 204
203 205 // bounding rect
204 206 if (m_data.m_isLabelVisible)
205 207 m_boundingRect = m_slicePath.boundingRect().united(m_labelArmPath.boundingRect()).united(m_labelTextRect);
206 208 else
207 209 m_boundingRect = m_slicePath.boundingRect();
208 210
209 211 // Inflate bounding rect by 2/3 pen width to make sure it encompasses whole slice also for thick pens
210 212 // and miter joins.
211 213 int penWidth = (m_data.m_slicePen.width() * 2) / 3;
212 214 m_boundingRect = m_boundingRect.adjusted(-penWidth, -penWidth, penWidth, penWidth);
213 215 }
214 216
215 217 QPointF PieSliceItem::sliceCenter(QPointF point, qreal radius, QPieSlice *slice)
216 218 {
217 219 if (slice->isExploded()) {
218 220 qreal centerAngle = slice->startAngle() + (slice->angleSpan() / 2);
219 221 qreal len = radius * slice->explodeDistanceFactor();
220 222 point += offset(centerAngle, len);
221 223 }
222 224 return point;
223 225 }
224 226
225 227 QPainterPath PieSliceItem::slicePath(QPointF center, qreal radius, qreal startAngle, qreal angleSpan, qreal *centerAngle, QPointF *armStart)
226 228 {
227 229 // calculate center angle
228 230 *centerAngle = startAngle + (angleSpan / 2);
229 231
230 232 // calculate slice rectangle
231 233 QRectF rect(center.x() - radius, center.y() - radius, radius * 2, radius * 2);
232 234
233 235 // slice path
234 236 QPainterPath path;
235 237 if (m_data.m_holeRadius > 0) {
236 238 QRectF insideRect(center.x() - m_data.m_holeRadius, center.y() - m_data.m_holeRadius, m_data.m_holeRadius * 2, m_data.m_holeRadius * 2);
237 239 path.arcMoveTo(rect, -startAngle + 90);
238 240 path.arcTo(rect, -startAngle + 90, -angleSpan);
239 241 path.arcTo(insideRect, -startAngle + 90 - angleSpan, angleSpan);
240 242 path.closeSubpath();
241 243 } else {
242 244 path.moveTo(rect.center());
243 245 path.arcTo(rect, -startAngle + 90, -angleSpan);
244 246 path.closeSubpath();
245 247 }
246 248
247 249 // calculate label arm start point
248 250 *armStart = center;
249 251 *armStart += offset(*centerAngle, radius + PIESLICE_LABEL_GAP);
250 252
251 253 return path;
252 254 }
253 255
254 256 QPainterPath PieSliceItem::labelArmPath(QPointF start, qreal angle, qreal length, qreal textWidth, QPointF *textStart)
255 257 {
256 258 // Normalize the angle to 0-360 range
257 259 // NOTE: We are using int here on purpose. Depenging on platform and hardware
258 260 // qreal can be a double, float or something the user gives to the Qt configure
259 261 // (QT_COORD_TYPE). Compilers do not seem to support modulo for double or float
260 262 // but there are fmod() and fmodf() functions for that. So instead of some #ifdef
261 263 // that might break we just use int. Precision for this is just fine for our needs.
262 264 int normalized = angle * 10.0;
263 265 normalized = normalized % 3600;
264 266 if (normalized < 0)
265 267 normalized += 3600;
266 268 angle = (qreal) normalized / 10.0;
267 269
268 270 // prevent label arm pointing straight down because it will look bad
269 271 if (angle < 180 && angle > 170)
270 272 angle = 170;
271 273 if (angle > 180 && angle < 190)
272 274 angle = 190;
273 275
274 276 // line from slice to label
275 277 QPointF parm1 = start + offset(angle, length);
276 278
277 279 // line to underline the label
278 280 QPointF parm2 = parm1;
279 281 if (angle < 180) { // arm swings the other way on the left side
280 282 parm2 += QPointF(textWidth, 0);
281 283 *textStart = parm1;
282 284 } else {
283 285 parm2 += QPointF(-textWidth, 0);
284 286 *textStart = parm2;
285 287 }
286 288
287 289 QPainterPath path;
288 290 path.moveTo(start);
289 291 path.lineTo(parm1);
290 292 path.lineTo(parm2);
291 293
292 294 return path;
293 295 }
294 296
295 297 #include "moc_piesliceitem_p.cpp"
296 298
297 299 QTCOMMERCIALCHART_END_NAMESPACE
298 300
General Comments 0
You need to be logged in to leave comments. Login now