##// END OF EJS Templates
Fix crash when adding/removing points during animation...
Miikka Heikkinen -
r2489:c4f9629c130d
parent child
Show More
@@ -1,149 +1,155
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 Commercial Charts Add-on.
8 8 **
9 9 ** $QT_BEGIN_LICENSE$
10 10 ** Licensees holding valid Qt Commercial licenses may use this file in
11 11 ** accordance with the Qt Commercial 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 "xyanimation_p.h"
22 22 #include "xychart_p.h"
23 23 #include <QDebug>
24 24
25 25 Q_DECLARE_METATYPE(QVector<QPointF>)
26 26
27 27 QTCOMMERCIALCHART_BEGIN_NAMESPACE
28 28
29 29 XYAnimation::XYAnimation(XYChart *item)
30 30 : ChartAnimation(item),
31 31 m_type(NewAnimation),
32 32 m_dirty(false),
33 33 m_index(-1),
34 34 m_item(item)
35 35 {
36 36 setDuration(ChartAnimationDuration);
37 37 setEasingCurve(QEasingCurve::OutQuart);
38 38 }
39 39
40 40 XYAnimation::~XYAnimation()
41 41 {
42 42 }
43 43
44 44 void XYAnimation::setup(const QVector<QPointF> &oldPoints, const QVector<QPointF> &newPoints, int index)
45 45 {
46 46 m_type = NewAnimation;
47 47
48 48 if (state() != QAbstractAnimation::Stopped) {
49 49 stop();
50 50 m_dirty = false;
51 51 }
52 52
53 53 if (!m_dirty) {
54 54 m_dirty = true;
55 55 m_oldPoints = oldPoints;
56 56 }
57 57
58 58 m_newPoints = newPoints;
59 59
60 60 int x = m_oldPoints.count();
61 61 int y = m_newPoints.count();
62
63 if (x - y == 1 && index >= 0 && y > 0) {
62 int diff = x - y;
63 int requestedDiff = oldPoints.count() - y;
64
65 // m_oldPoints can be whatever between 0 and actual points count if new animation setup
66 // interrupts a previous animation, so only do remove and add animations if both
67 // stored diff and requested diff indicate add or remove. Also ensure that index is not
68 // invalid.
69 if (diff == 1 && requestedDiff == 1 && index >= 0 && y > 0 && index <= y) {
64 70 //remove point
65 71 m_newPoints.insert(index, index > 0 ? newPoints[index - 1] : newPoints[index]);
66 72 m_index = index;
67 73 m_type = RemovePointAnimation;
68 74 }
69 75
70 if (x - y == -1 && index >= 0) {
76 if (diff == -1 && requestedDiff == -1 && index >= 0 && index <= x) {
71 77 //add point
72 78 m_oldPoints.insert(index, index > 0 ? newPoints[index - 1] : newPoints[index]);
73 79 m_index = index;
74 80 m_type = AddPointAnimation;
75 81 }
76 82
77 83 x = m_oldPoints.count();
78 84 y = m_newPoints.count();
79 85
80 86 if (x != y)
81 87 m_type = NewAnimation;
82 88 else if (m_type == NewAnimation)
83 89 m_type = ReplacePointAnimation;
84 90
85 91 setKeyValueAt(0.0, qVariantFromValue(m_oldPoints));
86 92 setKeyValueAt(1.0, qVariantFromValue(m_newPoints));
87 93 }
88 94
89 95 QVariant XYAnimation::interpolated(const QVariant &start, const QVariant &end, qreal progress) const
90 96 {
91 97 QVector<QPointF> startVector = qvariant_cast<QVector<QPointF> >(start);
92 98 QVector<QPointF> endVector = qvariant_cast<QVector<QPointF> >(end);
93 99 QVector<QPointF> result;
94 100
95 101 switch (m_type) {
96 102
97 103 case ReplacePointAnimation:
98 104 case AddPointAnimation:
99 105 case RemovePointAnimation: {
100 106 if (startVector.count() != endVector.count())
101 107 break;
102 108
103 109 for (int i = 0; i < startVector.count(); i++) {
104 110 qreal x = startVector[i].x() + ((endVector[i].x() - startVector[i].x()) * progress);
105 111 qreal y = startVector[i].y() + ((endVector[i].y() - startVector[i].y()) * progress);
106 112 result << QPointF(x, y);
107 113 }
108 114
109 115 }
110 116 break;
111 117 case NewAnimation: {
112 118 for (int i = 0; i < endVector.count() * qBound(qreal(0), progress, qreal(1)); i++)
113 119 result << endVector[i];
114 120 }
115 121 break;
116 122 default:
117 123 qWarning() << "Unknown type of animation";
118 124 break;
119 125 }
120 126
121 127 return qVariantFromValue(result);
122 128 }
123 129
124 130 void XYAnimation::updateCurrentValue(const QVariant &value)
125 131 {
126 132 if (state() != QAbstractAnimation::Stopped) { //workaround
127 133
128 134 QVector<QPointF> vector = qvariant_cast<QVector<QPointF> >(value);
129 135 m_item->setGeometryPoints(vector);
130 136 m_item->updateGeometry();
131 137 m_item->setDirty(true);
132 138 m_dirty = false;
133 139
134 140 }
135 141 }
136 142
137 143 void XYAnimation::updateState(QAbstractAnimation::State newState, QAbstractAnimation::State oldState)
138 144 {
139 145 if (oldState == QAbstractAnimation::Running && newState == QAbstractAnimation::Stopped) {
140 146 if (m_item->isDirty() && m_type == RemovePointAnimation) {
141 147 if (!m_newPoints.isEmpty())
142 148 m_newPoints.remove(m_index);
143 149 m_item->setGeometryPoints(m_newPoints);
144 150 }
145 151 }
146 152 }
147 153
148 154 #include "moc_chartanimation_p.cpp"
149 155 QTCOMMERCIALCHART_END_NAMESPACE
@@ -1,369 +1,372
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 Commercial Charts Add-on.
8 8 **
9 9 ** $QT_BEGIN_LICENSE$
10 10 ** Licensees holding valid Qt Commercial licenses may use this file in
11 11 ** accordance with the Qt Commercial 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 "linechartitem_p.h"
22 22 #include "qlineseries.h"
23 23 #include "qlineseries_p.h"
24 24 #include "chartpresenter_p.h"
25 25 #include "polardomain_p.h"
26 26 #include <QPainter>
27 27 #include <QGraphicsSceneMouseEvent>
28 28
29 29 QTCOMMERCIALCHART_BEGIN_NAMESPACE
30 30
31 31 const qreal mouseEventMinWidth(12);
32 32
33 33 LineChartItem::LineChartItem(QLineSeries *series, QGraphicsItem *item)
34 34 : XYChart(series,item),
35 35 m_series(series),
36 36 m_pointsVisible(false),
37 37 m_chartType(QChart::ChartTypeUndefined)
38 38 {
39 39 setAcceptHoverEvents(true);
40 40 setZValue(ChartPresenter::LineChartZValue);
41 41 QObject::connect(series->d_func(), SIGNAL(updated()), this, SLOT(handleUpdated()));
42 42 QObject::connect(series, SIGNAL(visibleChanged()), this, SLOT(handleUpdated()));
43 43 QObject::connect(series, SIGNAL(opacityChanged()), this, SLOT(handleUpdated()));
44 44 handleUpdated();
45 45 }
46 46
47 47 QRectF LineChartItem::boundingRect() const
48 48 {
49 49 return m_rect;
50 50 }
51 51
52 52 QPainterPath LineChartItem::shape() const
53 53 {
54 54 return m_shapePath;
55 55 }
56 56
57 57 void LineChartItem::updateGeometry()
58 58 {
59 59 m_points = geometryPoints();
60 60 const QVector<QPointF> &points = m_points;
61 61
62 62 if (points.size() == 0) {
63 63 prepareGeometryChange();
64 64 m_fullPath = QPainterPath();
65 65 m_linePath = QPainterPath();
66 66 m_rect = QRect();
67 67 return;
68 68 }
69 69
70 70 QPainterPath linePath;
71 71 QPainterPath fullPath;
72 72 // Use worst case scenario to determine required margin.
73 73 qreal margin = m_linePen.width() * 1.42;
74 74
75 75 // Area series use component line series that aren't necessarily added to the chart themselves,
76 76 // so check if chart type is forced before trying to obtain it from the chart.
77 77 QChart::ChartType chartType = m_chartType;
78 78 if (chartType == QChart::ChartTypeUndefined)
79 79 chartType = m_series->chart()->chartType();
80 80
81 81 // For polar charts, we need special handling for angular (horizontal)
82 82 // points that are off-grid.
83 83 if (chartType == QChart::ChartTypePolar) {
84 84 QPainterPath linePathLeft;
85 85 QPainterPath linePathRight;
86 86 QPainterPath *currentSegmentPath = 0;
87 87 QPainterPath *previousSegmentPath = 0;
88 88 qreal minX = domain()->minX();
89 89 qreal maxX = domain()->maxX();
90 90 qreal minY = domain()->minY();
91 91 QPointF currentSeriesPoint = m_series->pointAt(0);
92 92 QPointF currentGeometryPoint = points.at(0);
93 93 QPointF previousGeometryPoint = points.at(0);
94 94 int size = m_linePen.width();
95 95 bool pointOffGrid = false;
96 96 bool previousPointWasOffGrid = (currentSeriesPoint.x() < minX || currentSeriesPoint.x() > maxX);
97 97
98 98 qreal domainRadius = domain()->size().height() / 2.0;
99 99 const QPointF centerPoint(domainRadius, domainRadius);
100 100
101 101 if (!previousPointWasOffGrid) {
102 102 fullPath.moveTo(points.at(0));
103 103 if (m_pointsVisible && currentSeriesPoint.y() >= minY) {
104 104 // Do not draw ellipses for points below minimum Y.
105 105 linePath.addEllipse(points.at(0), size, size);
106 106 fullPath.addEllipse(points.at(0), size, size);
107 107 linePath.moveTo(points.at(0));
108 108 fullPath.moveTo(points.at(0));
109 109 }
110 110 }
111 111
112 112 qreal leftMarginLine = centerPoint.x() - margin;
113 113 qreal rightMarginLine = centerPoint.x() + margin;
114 114 qreal horizontal = centerPoint.y();
115 115
116 // See ScatterChartItem::updateGeometry() for explanation why seriesLastIndex is needed
117 const int seriesLastIndex = m_series->count() - 1;
118
116 119 for (int i = 1; i < points.size(); i++) {
117 120 // Interpolating line fragments would be ugly when thick pen is used,
118 121 // so we work around it by utilizing three separate
119 122 // paths for line segments and clip those with custom regions at paint time.
120 123 // "Right" path contains segments that cross the axis line with visible point on the
121 124 // right side of the axis line, as well as segments that have one point within the margin
122 125 // on the right side of the axis line and another point on the right side of the chart.
123 126 // "Left" path contains points with similarly on the left side.
124 127 // "Full" path contains rest of the points.
125 128 // This doesn't yield perfect results always. E.g. when segment covers more than 90
126 129 // degrees and both of the points are within the margin, one in the top half and one in the
127 130 // bottom half of the chart, the bottom one gets clipped incorrectly.
128 131 // However, this should be rare occurrence in any sensible chart.
129 currentSeriesPoint = m_series->pointAt(i);
132 currentSeriesPoint = m_series->pointAt(qMin(seriesLastIndex, i));
130 133 currentGeometryPoint = points.at(i);
131 134 pointOffGrid = (currentSeriesPoint.x() < minX || currentSeriesPoint.x() > maxX);
132 135
133 136 // Draw something unless both off-grid
134 137 if (!pointOffGrid || !previousPointWasOffGrid) {
135 138 QPointF intersectionPoint;
136 139 qreal y;
137 140 if (pointOffGrid != previousPointWasOffGrid) {
138 141 if (currentGeometryPoint.x() == previousGeometryPoint.x()) {
139 142 y = currentGeometryPoint.y() + (currentGeometryPoint.y() - previousGeometryPoint.y()) / 2.0;
140 143 } else {
141 144 qreal ratio = (centerPoint.x() - currentGeometryPoint.x()) / (currentGeometryPoint.x() - previousGeometryPoint.x());
142 145 y = currentGeometryPoint.y() + (currentGeometryPoint.y() - previousGeometryPoint.y()) * ratio;
143 146 }
144 147 intersectionPoint = QPointF(centerPoint.x(), y);
145 148 }
146 149
147 150 bool dummyOk; // We know points are ok, but this is needed
148 151 qreal currentAngle = static_cast<PolarDomain *>(domain())->toAngularCoordinate(currentSeriesPoint.x(), dummyOk);
149 152 qreal previousAngle = static_cast<PolarDomain *>(domain())->toAngularCoordinate(m_series->pointAt(i - 1).x(), dummyOk);
150 153
151 154 if ((qAbs(currentAngle - previousAngle) > 180.0)) {
152 155 // If the angle between two points is over 180 degrees (half X range),
153 156 // any direct segment between them becomes meaningless.
154 157 // In this case two line segments are drawn instead, from previous
155 158 // point to the center and from center to current point.
156 159 if ((previousAngle < 0.0 || (previousAngle <= 180.0 && previousGeometryPoint.x() < rightMarginLine))
157 160 && previousGeometryPoint.y() < horizontal) {
158 161 currentSegmentPath = &linePathRight;
159 162 } else if ((previousAngle > 360.0 || (previousAngle > 180.0 && previousGeometryPoint.x() > leftMarginLine))
160 163 && previousGeometryPoint.y() < horizontal) {
161 164 currentSegmentPath = &linePathLeft;
162 165 } else if (previousAngle > 0.0 && previousAngle < 360.0) {
163 166 currentSegmentPath = &linePath;
164 167 } else {
165 168 currentSegmentPath = 0;
166 169 }
167 170
168 171 if (currentSegmentPath) {
169 172 if (previousSegmentPath != currentSegmentPath)
170 173 currentSegmentPath->moveTo(previousGeometryPoint);
171 174 if (previousPointWasOffGrid)
172 175 fullPath.moveTo(intersectionPoint);
173 176
174 177 currentSegmentPath->lineTo(centerPoint);
175 178 fullPath.lineTo(centerPoint);
176 179 }
177 180
178 181 previousSegmentPath = currentSegmentPath;
179 182
180 183 if ((currentAngle < 0.0 || (currentAngle <= 180.0 && currentGeometryPoint.x() < rightMarginLine))
181 184 && currentGeometryPoint.y() < horizontal) {
182 185 currentSegmentPath = &linePathRight;
183 186 } else if ((currentAngle > 360.0 || (currentAngle > 180.0 &&currentGeometryPoint.x() > leftMarginLine))
184 187 && currentGeometryPoint.y() < horizontal) {
185 188 currentSegmentPath = &linePathLeft;
186 189 } else if (currentAngle > 0.0 && currentAngle < 360.0) {
187 190 currentSegmentPath = &linePath;
188 191 } else {
189 192 currentSegmentPath = 0;
190 193 }
191 194
192 195 if (currentSegmentPath) {
193 196 if (previousSegmentPath != currentSegmentPath)
194 197 currentSegmentPath->moveTo(centerPoint);
195 198 if (!previousSegmentPath)
196 199 fullPath.moveTo(centerPoint);
197 200
198 201 currentSegmentPath->lineTo(currentGeometryPoint);
199 202 if (pointOffGrid)
200 203 fullPath.lineTo(intersectionPoint);
201 204 else
202 205 fullPath.lineTo(currentGeometryPoint);
203 206 }
204 207 } else {
205 208 if (previousAngle < 0.0 || currentAngle < 0.0
206 209 || ((previousAngle <= 180.0 && currentAngle <= 180.0)
207 210 && ((previousGeometryPoint.x() < rightMarginLine && previousGeometryPoint.y() < horizontal)
208 211 || (currentGeometryPoint.x() < rightMarginLine && currentGeometryPoint.y() < horizontal)))) {
209 212 currentSegmentPath = &linePathRight;
210 213 } else if (previousAngle > 360.0 || currentAngle > 360.0
211 214 || ((previousAngle > 180.0 && currentAngle > 180.0)
212 215 && ((previousGeometryPoint.x() > leftMarginLine && previousGeometryPoint.y() < horizontal)
213 216 || (currentGeometryPoint.x() > leftMarginLine && currentGeometryPoint.y() < horizontal)))) {
214 217 currentSegmentPath = &linePathLeft;
215 218 } else {
216 219 currentSegmentPath = &linePath;
217 220 }
218 221
219 222 if (currentSegmentPath != previousSegmentPath)
220 223 currentSegmentPath->moveTo(previousGeometryPoint);
221 224 if (previousPointWasOffGrid)
222 225 fullPath.moveTo(intersectionPoint);
223 226
224 227 if (pointOffGrid)
225 228 fullPath.lineTo(intersectionPoint);
226 229 else
227 230 fullPath.lineTo(currentGeometryPoint);
228 231 currentSegmentPath->lineTo(currentGeometryPoint);
229 232 }
230 233 } else {
231 234 currentSegmentPath = 0;
232 235 }
233 236
234 237 previousPointWasOffGrid = pointOffGrid;
235 238 if (m_pointsVisible && !pointOffGrid && currentSeriesPoint.y() >= minY) {
236 239 linePath.addEllipse(points.at(i), size, size);
237 240 fullPath.addEllipse(points.at(i), size, size);
238 241 linePath.moveTo(points.at(i));
239 242 fullPath.moveTo(points.at(i));
240 243 }
241 244 previousSegmentPath = currentSegmentPath;
242 245 previousGeometryPoint = currentGeometryPoint;
243 246 }
244 247 m_linePathPolarRight = linePathRight;
245 248 m_linePathPolarLeft = linePathLeft;
246 249 // Note: This construction of m_fullpath is not perfect. The partial segments that are
247 250 // outside left/right clip regions at axis boundary still generate hover/click events,
248 251 // because shape doesn't get clipped. It doesn't seem possible to do sensibly.
249 252 } else { // not polar
250 253 linePath.moveTo(points.at(0));
251 254 if (m_pointsVisible) {
252 255 int size = m_linePen.width();
253 256 linePath.addEllipse(points.at(0), size, size);
254 257 linePath.moveTo(points.at(0));
255 258 for (int i = 1; i < points.size(); i++) {
256 259 linePath.lineTo(points.at(i));
257 260 linePath.addEllipse(points.at(i), size, size);
258 261 linePath.moveTo(points.at(i));
259 262 }
260 263 } else {
261 264 for (int i = 1; i < points.size(); i++)
262 265 linePath.lineTo(points.at(i));
263 266 }
264 267 fullPath = linePath;
265 268 }
266 269
267 270 QPainterPathStroker stroker;
268 271 // QPainter::drawLine does not respect join styles, for example BevelJoin becomes MiterJoin.
269 272 // This is why we are prepared for the "worst case" scenario, i.e. use always MiterJoin and
270 273 // multiply line width with square root of two when defining shape and bounding rectangle.
271 274 stroker.setWidth(margin);
272 275 stroker.setJoinStyle(Qt::MiterJoin);
273 276 stroker.setCapStyle(Qt::SquareCap);
274 277 stroker.setMiterLimit(m_linePen.miterLimit());
275 278
276 279 prepareGeometryChange();
277 280
278 281 m_linePath = linePath;
279 282 m_fullPath = fullPath;
280 283 m_shapePath = stroker.createStroke(fullPath);
281 284
282 285 m_rect = m_shapePath.boundingRect();
283 286 }
284 287
285 288 void LineChartItem::handleUpdated()
286 289 {
287 290 // If points visiblity has changed, a geometry update is needed.
288 291 // Also, if pen changes when points are visible, geometry update is needed.
289 292 bool doGeometryUpdate =
290 293 (m_pointsVisible != m_series->pointsVisible())
291 294 || (m_series->pointsVisible() && (m_linePen != m_series->pen()));
292 295 setVisible(m_series->isVisible());
293 296 setOpacity(m_series->opacity());
294 297 m_pointsVisible = m_series->pointsVisible();
295 298 m_linePen = m_series->pen();
296 299 if (doGeometryUpdate)
297 300 updateGeometry();
298 301 update();
299 302 }
300 303
301 304 void LineChartItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
302 305 {
303 306 Q_UNUSED(widget)
304 307 Q_UNUSED(option)
305 308
306 309 QRectF clipRect = QRectF(QPointF(0, 0), domain()->size());
307 310
308 311 painter->save();
309 312 painter->setPen(m_linePen);
310 313 bool alwaysUsePath = false;
311 314
312 315 if (m_series->chart()->chartType() == QChart::ChartTypePolar) {
313 316 qreal halfWidth = domain()->size().width() / 2.0;
314 317 QRectF clipRectLeft = QRectF(0, 0, halfWidth, domain()->size().height());
315 318 QRectF clipRectRight = QRectF(halfWidth, 0, halfWidth, domain()->size().height());
316 319 QRegion fullPolarClipRegion(clipRect.toRect(), QRegion::Ellipse);
317 320 QRegion clipRegionLeft(fullPolarClipRegion.intersected(clipRectLeft.toRect()));
318 321 QRegion clipRegionRight(fullPolarClipRegion.intersected(clipRectRight.toRect()));
319 322 painter->setClipRegion(clipRegionLeft);
320 323 painter->drawPath(m_linePathPolarLeft);
321 324 painter->setClipRegion(clipRegionRight);
322 325 painter->drawPath(m_linePathPolarRight);
323 326 painter->setClipRegion(fullPolarClipRegion);
324 327 alwaysUsePath = true; // required for proper clipping
325 328 } else {
326 329 painter->setClipRect(clipRect);
327 330 }
328 331
329 332 if (m_pointsVisible) {
330 333 painter->setBrush(m_linePen.color());
331 334 painter->drawPath(m_linePath);
332 335 } else {
333 336 painter->setBrush(QBrush(Qt::NoBrush));
334 337 if (m_linePen.style() != Qt::SolidLine || alwaysUsePath) {
335 338 // If pen style is not solid line, always fall back to path painting
336 339 // to ensure proper continuity of the pattern
337 340 painter->drawPath(m_linePath);
338 341 } else {
339 342 for (int i(1); i < m_points.size(); i++)
340 343 painter->drawLine(m_points.at(i - 1), m_points.at(i));
341 344 }
342 345 }
343 346
344 347 painter->restore();
345 348 }
346 349
347 350 void LineChartItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
348 351 {
349 352 emit XYChart::clicked(domain()->calculateDomainPoint(event->pos()));
350 353 QGraphicsItem::mousePressEvent(event);
351 354 }
352 355
353 356 void LineChartItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
354 357 {
355 358 emit XYChart::hovered(domain()->calculateDomainPoint(event->pos()), true);
356 359 // event->accept();
357 360 QGraphicsItem::hoverEnterEvent(event);
358 361 }
359 362
360 363 void LineChartItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
361 364 {
362 365 emit XYChart::hovered(domain()->calculateDomainPoint(event->pos()), false);
363 366 // event->accept();
364 367 QGraphicsItem::hoverEnterEvent(event);
365 368 }
366 369
367 370 #include "moc_linechartitem_p.cpp"
368 371
369 372 QTCOMMERCIALCHART_END_NAMESPACE
@@ -1,199 +1,207
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 Commercial Charts Add-on.
8 8 **
9 9 ** $QT_BEGIN_LICENSE$
10 10 ** Licensees holding valid Qt Commercial licenses may use this file in
11 11 ** accordance with the Qt Commercial 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 "scatterchartitem_p.h"
22 22 #include "qscatterseries.h"
23 23 #include "qscatterseries_p.h"
24 24 #include "chartpresenter_p.h"
25 25 #include "abstractdomain_p.h"
26 26 #include "qchart.h"
27 27 #include <QPainter>
28 28 #include <QGraphicsScene>
29 29 #include <QDebug>
30 30 #include <QGraphicsSceneMouseEvent>
31 31
32 32 QTCOMMERCIALCHART_BEGIN_NAMESPACE
33 33
34 34 ScatterChartItem::ScatterChartItem(QScatterSeries *series, QGraphicsItem *item)
35 35 : XYChart(series,item),
36 36 m_series(series),
37 37 m_items(this),
38 38 m_visible(true),
39 39 m_shape(QScatterSeries::MarkerShapeRectangle),
40 40 m_size(15)
41 41 {
42 42 QObject::connect(m_series->d_func(), SIGNAL(updated()), this, SLOT(handleUpdated()));
43 43 QObject::connect(m_series, SIGNAL(visibleChanged()), this, SLOT(handleUpdated()));
44 44 QObject::connect(m_series, SIGNAL(opacityChanged()), this, SLOT(handleUpdated()));
45 45
46 46 setZValue(ChartPresenter::ScatterSeriesZValue);
47 47 setFlags(QGraphicsItem::ItemClipsChildrenToShape);
48 48
49 49 handleUpdated();
50 50
51 51 m_items.setHandlesChildEvents(false);
52 52 }
53 53
54 54 QRectF ScatterChartItem::boundingRect() const
55 55 {
56 56 return m_rect;
57 57 }
58 58
59 59 void ScatterChartItem::createPoints(int count)
60 60 {
61 61 for (int i = 0; i < count; ++i) {
62 62
63 63 QGraphicsItem *item = 0;
64 64
65 65 switch (m_shape) {
66 66 case QScatterSeries::MarkerShapeCircle: {
67 67 item = new CircleMarker(0, 0, m_size, m_size, this);
68 68 const QRectF &rect = item->boundingRect();
69 69 item->setPos(-rect.width() / 2, -rect.height() / 2);
70 70 break;
71 71 }
72 72 case QScatterSeries::MarkerShapeRectangle:
73 73 item = new RectangleMarker(0, 0, m_size, m_size, this);
74 74 item->setPos(-m_size / 2, -m_size / 2);
75 75 break;
76 76 default:
77 77 qWarning() << "Unsupported marker type";
78 78 break;
79 79 }
80 80 m_items.addToGroup(item);
81 81 }
82 82 }
83 83
84 84 void ScatterChartItem::deletePoints(int count)
85 85 {
86 86 QList<QGraphicsItem *> items = m_items.childItems();
87 87
88 88 for (int i = 0; i < count; ++i) {
89 89 QGraphicsItem *item = items.takeLast();
90 90 m_markerMap.remove(item);
91 91 delete(item);
92 92 }
93 93 }
94 94
95 95 void ScatterChartItem::markerSelected(QGraphicsItem *marker)
96 96 {
97 97 emit XYChart::clicked(m_markerMap[marker]);
98 98 }
99 99
100 100 void ScatterChartItem::markerHovered(QGraphicsItem *marker, bool state)
101 101 {
102 102 emit XYChart::hovered(m_markerMap[marker], state);
103 103 }
104 104
105 105 void ScatterChartItem::updateGeometry()
106 106 {
107 107
108 108 const QVector<QPointF>& points = geometryPoints();
109 109
110 110 if (points.size() == 0) {
111 111 deletePoints(m_items.childItems().count());
112 112 return;
113 113 }
114 114
115 115 int diff = m_items.childItems().size() - points.size();
116 116
117 117 if (diff > 0)
118 118 deletePoints(diff);
119 119 else if (diff < 0)
120 120 createPoints(-diff);
121 121
122 122 if (diff != 0)
123 123 handleUpdated();
124 124
125 125 QList<QGraphicsItem *> items = m_items.childItems();
126 126
127 127 QRectF clipRect(QPointF(0,0),domain()->size());
128 128
129 129 QVector<bool> offGridStatus = offGridStatusVector();
130 const int seriesLastIndex = m_series->count() - 1;
130 131
131 132 for (int i = 0; i < points.size(); i++) {
132 133 QGraphicsItem *item = items.at(i);
133 134 const QPointF &point = points.at(i);
134 135 const QRectF &rect = item->boundingRect();
135 m_markerMap[item] = m_series->pointAt(i);
136 // During remove/append animation series may have different number of points,
137 // so ensure we don't go over the index. Animation handling itself ensures that
138 // if there is actually no points in the series, then it won't generate a fake point,
139 // so we can be assured there is always at least one point in m_series here.
140 // Note that marker map values can be technically incorrect during the animation,
141 // if it was caused by an insert, but this shouldn't be a problem as the points are
142 // fake anyway.
143 m_markerMap[item] = m_series->pointAt(qMin(seriesLastIndex, i));
136 144 item->setPos(point.x() - rect.width() / 2, point.y() - rect.height() / 2);
137 145
138 146 if (!m_visible || offGridStatus.at(i))
139 147 item->setVisible(false);
140 148 else
141 149 item->setVisible(true);
142 150 }
143 151
144 152 prepareGeometryChange();
145 153 m_rect = clipRect;
146 154 }
147 155
148 156 void ScatterChartItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
149 157 {
150 158 Q_UNUSED(painter)
151 159 Q_UNUSED(option)
152 160 Q_UNUSED(widget)
153 161 }
154 162
155 163 void ScatterChartItem::setPen(const QPen &pen)
156 164 {
157 165 foreach (QGraphicsItem *item , m_items.childItems())
158 166 static_cast<QAbstractGraphicsShapeItem*>(item)->setPen(pen);
159 167 }
160 168
161 169 void ScatterChartItem::setBrush(const QBrush &brush)
162 170 {
163 171 foreach (QGraphicsItem *item , m_items.childItems())
164 172 static_cast<QAbstractGraphicsShapeItem*>(item)->setBrush(brush);
165 173 }
166 174
167 175 void ScatterChartItem::handleUpdated()
168 176 {
169 177 int count = m_items.childItems().count();
170 178
171 179 if (count == 0)
172 180 return;
173 181
174 182 bool recreate = m_visible != m_series->isVisible()
175 183 || m_size != m_series->markerSize()
176 184 || m_shape != m_series->markerShape();
177 185
178 186 m_visible = m_series->isVisible();
179 187 m_size = m_series->markerSize();
180 188 m_shape = m_series->markerShape();
181 189 setOpacity(m_series->opacity());
182 190
183 191 if (recreate) {
184 192 // TODO: optimize handleUpdate to recreate points only in case shape changed
185 193 deletePoints(count);
186 194 createPoints(count);
187 195
188 196 // Updating geometry is now safe, because it won't call handleUpdated unless it creates/deletes points
189 197 updateGeometry();
190 198 }
191 199
192 200 setPen(m_series->pen());
193 201 setBrush(m_series->brush());
194 202 update();
195 203 }
196 204
197 205 #include "moc_scatterchartitem_p.cpp"
198 206
199 207 QTCOMMERCIALCHART_END_NAMESPACE
@@ -1,456 +1,459
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 Commercial Charts Add-on.
8 8 **
9 9 ** $QT_BEGIN_LICENSE$
10 10 ** Licensees holding valid Qt Commercial licenses may use this file in
11 11 ** accordance with the Qt Commercial 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 "splinechartitem_p.h"
22 22 #include "qsplineseries_p.h"
23 23 #include "chartpresenter_p.h"
24 24 #include "splineanimation_p.h"
25 25 #include "polardomain_p.h"
26 26 #include <QPainter>
27 27 #include <QGraphicsSceneMouseEvent>
28 28
29 29 QTCOMMERCIALCHART_BEGIN_NAMESPACE
30 30
31 31 SplineChartItem::SplineChartItem(QSplineSeries *series, QGraphicsItem *item)
32 32 : XYChart(series,item),
33 33 m_series(series),
34 34 m_pointsVisible(false),
35 35 m_animation(0)
36 36 {
37 37 setAcceptHoverEvents(true);
38 38 setZValue(ChartPresenter::SplineChartZValue);
39 39 QObject::connect(m_series->d_func(), SIGNAL(updated()), this, SLOT(handleUpdated()));
40 40 QObject::connect(series, SIGNAL(visibleChanged()), this, SLOT(handleUpdated()));
41 41 QObject::connect(series, SIGNAL(opacityChanged()), this, SLOT(handleUpdated()));
42 42 handleUpdated();
43 43 }
44 44
45 45 QRectF SplineChartItem::boundingRect() const
46 46 {
47 47 return m_rect;
48 48 }
49 49
50 50 QPainterPath SplineChartItem::shape() const
51 51 {
52 52 return m_fullPath;
53 53 }
54 54
55 55 void SplineChartItem::setAnimation(SplineAnimation *animation)
56 56 {
57 57 m_animation = animation;
58 58 XYChart::setAnimation(animation);
59 59 }
60 60
61 61 ChartAnimation *SplineChartItem::animation() const
62 62 {
63 63 return m_animation;
64 64 }
65 65
66 66 void SplineChartItem::setControlGeometryPoints(QVector<QPointF>& points)
67 67 {
68 68 m_controlPoints = points;
69 69 }
70 70
71 71 QVector<QPointF> SplineChartItem::controlGeometryPoints() const
72 72 {
73 73 return m_controlPoints;
74 74 }
75 75
76 76 void SplineChartItem::updateChart(QVector<QPointF> &oldPoints, QVector<QPointF> &newPoints, int index)
77 77 {
78 78 QVector<QPointF> controlPoints;
79 79 if (newPoints.count() >= 2)
80 80 controlPoints = calculateControlPoints(newPoints);
81 81
82 82 if (m_animation)
83 83 m_animation->setup(oldPoints, newPoints, m_controlPoints, controlPoints, index);
84 84
85 85 m_points = newPoints;
86 86 m_controlPoints = controlPoints;
87 87 setDirty(false);
88 88
89 89 if (m_animation)
90 90 presenter()->startAnimation(m_animation);
91 91 else
92 92 updateGeometry();
93 93 }
94 94
95 95 void SplineChartItem::updateGeometry()
96 96 {
97 97 const QVector<QPointF> &points = m_points;
98 98 const QVector<QPointF> &controlPoints = m_controlPoints;
99 99
100 100 if ((points.size() < 2) || (controlPoints.size() < 2)) {
101 101 prepareGeometryChange();
102 102 m_path = QPainterPath();
103 103 m_rect = QRect();
104 104 return;
105 105 }
106 106
107 107 Q_ASSERT(points.count() * 2 - 2 == controlPoints.count());
108 108
109 109 QPainterPath splinePath;
110 110 QPainterPath fullPath;
111 111 // Use worst case scenario to determine required margin.
112 112 qreal margin = m_linePen.width() * 1.42;
113 113
114 114 if (m_series->chart()->chartType() == QChart::ChartTypePolar) {
115 115 QPainterPath splinePathLeft;
116 116 QPainterPath splinePathRight;
117 117 QPainterPath *currentSegmentPath = 0;
118 118 QPainterPath *previousSegmentPath = 0;
119 119 qreal minX = domain()->minX();
120 120 qreal maxX = domain()->maxX();
121 121 qreal minY = domain()->minY();
122 122 QPointF currentSeriesPoint = m_series->pointAt(0);
123 123 QPointF currentGeometryPoint = points.at(0);
124 124 QPointF previousGeometryPoint = points.at(0);
125 125 bool pointOffGrid = false;
126 126 bool previousPointWasOffGrid = (currentSeriesPoint.x() < minX || currentSeriesPoint.x() > maxX);
127 127 m_visiblePoints.clear();
128 128 m_visiblePoints.reserve(points.size());
129 129
130 130 qreal domainRadius = domain()->size().height() / 2.0;
131 131 const QPointF centerPoint(domainRadius, domainRadius);
132 132
133 133 if (!previousPointWasOffGrid) {
134 134 fullPath.moveTo(points.at(0));
135 135 // Do not draw points for points below minimum Y.
136 136 if (m_pointsVisible && currentSeriesPoint.y() >= minY)
137 137 m_visiblePoints.append(currentGeometryPoint);
138 138 }
139 139
140 140 qreal leftMarginLine = centerPoint.x() - margin;
141 141 qreal rightMarginLine = centerPoint.x() + margin;
142 142 qreal horizontal = centerPoint.y();
143 143
144 // See ScatterChartItem::updateGeometry() for explanation why seriesLastIndex is needed
145 const int seriesLastIndex = m_series->count() - 1;
146
144 147 for (int i = 1; i < points.size(); i++) {
145 148 // Interpolating spline fragments accurately is not trivial, and would anyway be ugly
146 149 // when thick pen is used, so we work around it by utilizing three separate
147 150 // paths for spline segments and clip those with custom regions at paint time.
148 151 // "Right" path contains segments that cross the axis line with visible point on the
149 152 // right side of the axis line, as well as segments that have one point within the margin
150 153 // on the right side of the axis line and another point on the right side of the chart.
151 154 // "Left" path contains points with similarly on the left side.
152 155 // "Full" path contains rest of the points.
153 156 // This doesn't yield perfect results always. E.g. when segment covers more than 90
154 157 // degrees and both of the points are within the margin, one in the top half and one in the
155 158 // bottom half of the chart, the bottom one gets clipped incorrectly.
156 159 // However, this should be rare occurrence in any sensible chart.
157 currentSeriesPoint = m_series->pointAt(i);
160 currentSeriesPoint = m_series->pointAt(qMin(seriesLastIndex, i));
158 161 currentGeometryPoint = points.at(i);
159 162 pointOffGrid = (currentSeriesPoint.x() < minX || currentSeriesPoint.x() > maxX);
160 163
161 164 // Draw something unless both off-grid
162 165 if (!pointOffGrid || !previousPointWasOffGrid) {
163 166 bool dummyOk; // We know points are ok, but this is needed
164 167 qreal currentAngle = static_cast<PolarDomain *>(domain())->toAngularCoordinate(currentSeriesPoint.x(), dummyOk);
165 168 qreal previousAngle = static_cast<PolarDomain *>(domain())->toAngularCoordinate(m_series->pointAt(i - 1).x(), dummyOk);
166 169
167 170 if ((qAbs(currentAngle - previousAngle) > 180.0)) {
168 171 // If the angle between two points is over 180 degrees (half X range),
169 172 // any direct segment between them becomes meaningless.
170 173 // In this case two line segments are drawn instead, from previous
171 174 // point to the center and from center to current point.
172 175 if ((previousAngle < 0.0 || (previousAngle <= 180.0 && previousGeometryPoint.x() < rightMarginLine))
173 176 && previousGeometryPoint.y() < horizontal) {
174 177 currentSegmentPath = &splinePathRight;
175 178 } else if ((previousAngle > 360.0 || (previousAngle > 180.0 && previousGeometryPoint.x() > leftMarginLine))
176 179 && previousGeometryPoint.y() < horizontal) {
177 180 currentSegmentPath = &splinePathLeft;
178 181 } else if (previousAngle > 0.0 && previousAngle < 360.0) {
179 182 currentSegmentPath = &splinePath;
180 183 } else {
181 184 currentSegmentPath = 0;
182 185 }
183 186
184 187 if (currentSegmentPath) {
185 188 if (previousSegmentPath != currentSegmentPath)
186 189 currentSegmentPath->moveTo(previousGeometryPoint);
187 190 if (!previousSegmentPath)
188 191 fullPath.moveTo(previousGeometryPoint);
189 192
190 193 currentSegmentPath->lineTo(centerPoint);
191 194 fullPath.lineTo(centerPoint);
192 195 }
193 196
194 197 previousSegmentPath = currentSegmentPath;
195 198
196 199 if ((currentAngle < 0.0 || (currentAngle <= 180.0 && currentGeometryPoint.x() < rightMarginLine))
197 200 && currentGeometryPoint.y() < horizontal) {
198 201 currentSegmentPath = &splinePathRight;
199 202 } else if ((currentAngle > 360.0 || (currentAngle > 180.0 &&currentGeometryPoint.x() > leftMarginLine))
200 203 && currentGeometryPoint.y() < horizontal) {
201 204 currentSegmentPath = &splinePathLeft;
202 205 } else if (currentAngle > 0.0 && currentAngle < 360.0) {
203 206 currentSegmentPath = &splinePath;
204 207 } else {
205 208 currentSegmentPath = 0;
206 209 }
207 210
208 211 if (currentSegmentPath) {
209 212 if (previousSegmentPath != currentSegmentPath)
210 213 currentSegmentPath->moveTo(centerPoint);
211 214 if (!previousSegmentPath)
212 215 fullPath.moveTo(centerPoint);
213 216
214 217 currentSegmentPath->lineTo(currentGeometryPoint);
215 218 fullPath.lineTo(currentGeometryPoint);
216 219 }
217 220 } else {
218 221 QPointF cp1 = controlPoints[2 * (i - 1)];
219 222 QPointF cp2 = controlPoints[(2 * i) - 1];
220 223
221 224 if (previousAngle < 0.0 || currentAngle < 0.0
222 225 || ((previousAngle <= 180.0 && currentAngle <= 180.0)
223 226 && ((previousGeometryPoint.x() < rightMarginLine && previousGeometryPoint.y() < horizontal)
224 227 || (currentGeometryPoint.x() < rightMarginLine && currentGeometryPoint.y() < horizontal)))) {
225 228 currentSegmentPath = &splinePathRight;
226 229 } else if (previousAngle > 360.0 || currentAngle > 360.0
227 230 || ((previousAngle > 180.0 && currentAngle > 180.0)
228 231 && ((previousGeometryPoint.x() > leftMarginLine && previousGeometryPoint.y() < horizontal)
229 232 || (currentGeometryPoint.x() > leftMarginLine && currentGeometryPoint.y() < horizontal)))) {
230 233 currentSegmentPath = &splinePathLeft;
231 234 } else {
232 235 currentSegmentPath = &splinePath;
233 236 }
234 237
235 238 if (currentSegmentPath != previousSegmentPath)
236 239 currentSegmentPath->moveTo(previousGeometryPoint);
237 240 if (!previousSegmentPath)
238 241 fullPath.moveTo(previousGeometryPoint);
239 242
240 243 fullPath.cubicTo(cp1, cp2, currentGeometryPoint);
241 244 currentSegmentPath->cubicTo(cp1, cp2, currentGeometryPoint);
242 245 }
243 246 } else {
244 247 currentSegmentPath = 0;
245 248 }
246 249
247 250 previousPointWasOffGrid = pointOffGrid;
248 251 if (!pointOffGrid && m_pointsVisible && currentSeriesPoint.y() >= minY)
249 252 m_visiblePoints.append(currentGeometryPoint);
250 253 previousSegmentPath = currentSegmentPath;
251 254 previousGeometryPoint = currentGeometryPoint;
252 255 }
253 256
254 257 m_pathPolarRight = splinePathRight;
255 258 m_pathPolarLeft = splinePathLeft;
256 259 // Note: This construction of m_fullpath is not perfect. The partial segments that are
257 260 // outside left/right clip regions at axis boundary still generate hover/click events,
258 261 // because shape doesn't get clipped. It doesn't seem possible to do sensibly.
259 262 } else { // not polar
260 263 splinePath.moveTo(points.at(0));
261 264 for (int i = 0; i < points.size() - 1; i++) {
262 265 const QPointF &point = points.at(i + 1);
263 266 splinePath.cubicTo(controlPoints[2 * i], controlPoints[2 * i + 1], point);
264 267 }
265 268 fullPath = splinePath;
266 269 }
267 270 m_path = splinePath;
268 271
269 272 QPainterPathStroker stroker;
270 273 // The full path is comprised of three separate paths.
271 274 // This is why we are prepared for the "worst case" scenario, i.e. use always MiterJoin and
272 275 // multiply line width with square root of two when defining shape and bounding rectangle.
273 276 stroker.setWidth(margin);
274 277 stroker.setJoinStyle(Qt::MiterJoin);
275 278 stroker.setCapStyle(Qt::SquareCap);
276 279 stroker.setMiterLimit(m_linePen.miterLimit());
277 280
278 281 prepareGeometryChange();
279 282
280 283 m_fullPath = stroker.createStroke(fullPath);
281 284 m_rect = m_fullPath.boundingRect();
282 285 }
283 286
284 287 /*!
285 288 Calculates control points which are needed by QPainterPath.cubicTo function to draw the cubic Bezier cureve between two points.
286 289 */
287 290 QVector<QPointF> SplineChartItem::calculateControlPoints(const QVector<QPointF> &points)
288 291 {
289 292 QVector<QPointF> controlPoints;
290 293 controlPoints.resize(points.count() * 2 - 2);
291 294
292 295 int n = points.count() - 1;
293 296
294 297 if (n == 1) {
295 298 //for n==1
296 299 controlPoints[0].setX((2 * points[0].x() + points[1].x()) / 3);
297 300 controlPoints[0].setY((2 * points[0].y() + points[1].y()) / 3);
298 301 controlPoints[1].setX(2 * controlPoints[0].x() - points[0].x());
299 302 controlPoints[1].setY(2 * controlPoints[0].y() - points[0].y());
300 303 return controlPoints;
301 304 }
302 305
303 306 // Calculate first Bezier control points
304 307 // Set of equations for P0 to Pn points.
305 308 //
306 309 // | 2 1 0 0 ... 0 0 0 ... 0 0 0 | | P1_1 | | P0 + 2 * P1 |
307 310 // | 1 4 1 0 ... 0 0 0 ... 0 0 0 | | P1_2 | | 4 * P1 + 2 * P2 |
308 311 // | 0 1 4 1 ... 0 0 0 ... 0 0 0 | | P1_3 | | 4 * P2 + 2 * P3 |
309 312 // | . . . . . . . . . . . . | | ... | | ... |
310 313 // | 0 0 0 0 ... 1 4 1 ... 0 0 0 | * | P1_i | = | 4 * P(i-1) + 2 * Pi |
311 314 // | . . . . . . . . . . . . | | ... | | ... |
312 315 // | 0 0 0 0 0 0 0 0 ... 1 4 1 | | P1_(n-1)| | 4 * P(n-2) + 2 * P(n-1) |
313 316 // | 0 0 0 0 0 0 0 0 ... 0 2 7 | | P1_n | | 8 * P(n-1) + Pn |
314 317 //
315 318 QVector<qreal> vector;
316 319 vector.resize(n);
317 320
318 321 vector[0] = points[0].x() + 2 * points[1].x();
319 322
320 323
321 324 for (int i = 1; i < n - 1; ++i)
322 325 vector[i] = 4 * points[i].x() + 2 * points[i + 1].x();
323 326
324 327 vector[n - 1] = (8 * points[n - 1].x() + points[n].x()) / 2.0;
325 328
326 329 QVector<qreal> xControl = firstControlPoints(vector);
327 330
328 331 vector[0] = points[0].y() + 2 * points[1].y();
329 332
330 333 for (int i = 1; i < n - 1; ++i)
331 334 vector[i] = 4 * points[i].y() + 2 * points[i + 1].y();
332 335
333 336 vector[n - 1] = (8 * points[n - 1].y() + points[n].y()) / 2.0;
334 337
335 338 QVector<qreal> yControl = firstControlPoints(vector);
336 339
337 340 for (int i = 0, j = 0; i < n; ++i, ++j) {
338 341
339 342 controlPoints[j].setX(xControl[i]);
340 343 controlPoints[j].setY(yControl[i]);
341 344
342 345 j++;
343 346
344 347 if (i < n - 1) {
345 348 controlPoints[j].setX(2 * points[i + 1].x() - xControl[i + 1]);
346 349 controlPoints[j].setY(2 * points[i + 1].y() - yControl[i + 1]);
347 350 } else {
348 351 controlPoints[j].setX((points[n].x() + xControl[n - 1]) / 2);
349 352 controlPoints[j].setY((points[n].y() + yControl[n - 1]) / 2);
350 353 }
351 354 }
352 355 return controlPoints;
353 356 }
354 357
355 358 QVector<qreal> SplineChartItem::firstControlPoints(const QVector<qreal>& vector)
356 359 {
357 360 QVector<qreal> result;
358 361
359 362 int count = vector.count();
360 363 result.resize(count);
361 364 result[0] = vector[0] / 2.0;
362 365
363 366 QVector<qreal> temp;
364 367 temp.resize(count);
365 368 temp[0] = 0;
366 369
367 370 qreal b = 2.0;
368 371
369 372 for (int i = 1; i < count; i++) {
370 373 temp[i] = 1 / b;
371 374 b = (i < count - 1 ? 4.0 : 3.5) - temp[i];
372 375 result[i] = (vector[i] - result[i - 1]) / b;
373 376 }
374 377
375 378 for (int i = 1; i < count; i++)
376 379 result[count - i - 1] -= temp[count - i] * result[count - i];
377 380
378 381 return result;
379 382 }
380 383
381 384 //handlers
382 385
383 386 void SplineChartItem::handleUpdated()
384 387 {
385 388 setVisible(m_series->isVisible());
386 389 setOpacity(m_series->opacity());
387 390 m_pointsVisible = m_series->pointsVisible();
388 391 m_linePen = m_series->pen();
389 392 m_pointPen = m_series->pen();
390 393 m_pointPen.setWidthF(2 * m_pointPen.width());
391 394 update();
392 395 }
393 396
394 397 //painter
395 398
396 399 void SplineChartItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
397 400 {
398 401 Q_UNUSED(widget)
399 402 Q_UNUSED(option)
400 403
401 404 QRectF clipRect = QRectF(QPointF(0, 0), domain()->size());
402 405
403 406 painter->save();
404 407 painter->setPen(m_linePen);
405 408 painter->setBrush(Qt::NoBrush);
406 409
407 410 if (m_series->chart()->chartType() == QChart::ChartTypePolar) {
408 411 qreal halfWidth = domain()->size().width() / 2.0;
409 412 QRectF clipRectLeft = QRectF(0, 0, halfWidth, domain()->size().height());
410 413 QRectF clipRectRight = QRectF(halfWidth, 0, halfWidth, domain()->size().height());
411 414 QRegion fullPolarClipRegion(clipRect.toRect(), QRegion::Ellipse);
412 415 QRegion clipRegionLeft(fullPolarClipRegion.intersected(clipRectLeft.toRect()));
413 416 QRegion clipRegionRight(fullPolarClipRegion.intersected(clipRectRight.toRect()));
414 417 painter->setClipRegion(clipRegionLeft);
415 418 painter->drawPath(m_pathPolarLeft);
416 419 painter->setClipRegion(clipRegionRight);
417 420 painter->drawPath(m_pathPolarRight);
418 421 painter->setClipRegion(fullPolarClipRegion);
419 422 } else {
420 423 painter->setClipRect(clipRect);
421 424 }
422 425
423 426 painter->drawPath(m_path);
424 427
425 428 if (m_pointsVisible) {
426 429 painter->setPen(m_pointPen);
427 430 if (m_series->chart()->chartType() == QChart::ChartTypePolar)
428 431 painter->drawPoints(m_visiblePoints);
429 432 else
430 433 painter->drawPoints(geometryPoints());
431 434 }
432 435
433 436 painter->restore();
434 437 }
435 438
436 439 void SplineChartItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
437 440 {
438 441 emit XYChart::clicked(domain()->calculateDomainPoint(event->pos()));
439 442 QGraphicsItem::mousePressEvent(event);
440 443 }
441 444
442 445 void SplineChartItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
443 446 {
444 447 emit XYChart::hovered(domain()->calculateDomainPoint(event->pos()), true);
445 448 QGraphicsItem::hoverEnterEvent(event);
446 449 }
447 450
448 451 void SplineChartItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
449 452 {
450 453 emit XYChart::hovered(domain()->calculateDomainPoint(event->pos()), false);
451 454 QGraphicsItem::hoverLeaveEvent(event);
452 455 }
453 456
454 457 #include "moc_splinechartitem_p.cpp"
455 458
456 459 QTCOMMERCIALCHART_END_NAMESPACE
@@ -1,187 +1,191
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 Commercial Charts Add-on.
8 8 **
9 9 ** $QT_BEGIN_LICENSE$
10 10 ** Licensees holding valid Qt Commercial licenses may use this file in
11 11 ** accordance with the Qt Commercial 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 "xychart_p.h"
22 22 #include "qxyseries.h"
23 23 #include "qxyseries_p.h"
24 24 #include "chartpresenter_p.h"
25 25 #include "abstractdomain_p.h"
26 26 #include "qxymodelmapper.h"
27 27 #include "qabstractaxis_p.h"
28 28 #include <QPainter>
29 29 #include <QAbstractItemModel>
30 30
31 31
32 32 QTCOMMERCIALCHART_BEGIN_NAMESPACE
33 33
34 34 //TODO: optimize : remove points which are not visible
35 35
36 36 XYChart::XYChart(QXYSeries *series, QGraphicsItem *item):
37 37 ChartItem(series->d_func(),item),
38 38 m_series(series),
39 39 m_animation(0),
40 40 m_dirty(true)
41 41 {
42 42 QObject::connect(series, SIGNAL(pointReplaced(int)), this, SLOT(handlePointReplaced(int)));
43 43 QObject::connect(series, SIGNAL(pointsReplaced()), this, SLOT(handlePointsReplaced()));
44 44 QObject::connect(series, SIGNAL(pointAdded(int)), this, SLOT(handlePointAdded(int)));
45 45 QObject::connect(series, SIGNAL(pointRemoved(int)), this, SLOT(handlePointRemoved(int)));
46 46 QObject::connect(this, SIGNAL(clicked(QPointF)), series, SIGNAL(clicked(QPointF)));
47 47 QObject::connect(this, SIGNAL(hovered(QPointF,bool)), series, SIGNAL(hovered(QPointF,bool)));
48 48 }
49 49
50 50 void XYChart::setGeometryPoints(const QVector<QPointF> &points)
51 51 {
52 52 m_points = points;
53 53 }
54 54
55 55 void XYChart::setAnimation(XYAnimation *animation)
56 56 {
57 57 m_animation = animation;
58 58 }
59 59
60 60 void XYChart::setDirty(bool dirty)
61 61 {
62 62 m_dirty = dirty;
63 63 }
64 64
65 65 // Returns a vector with same size as geometryPoints vector, indicating
66 66 // the off grid status of points.
67 67 QVector<bool> XYChart::offGridStatusVector()
68 68 {
69 69 qreal minX = domain()->minX();
70 70 qreal maxX = domain()->maxX();
71 71 qreal minY = domain()->minY();
72 72 qreal maxY = domain()->maxY();
73 73
74 74 QVector<bool> returnVector;
75 75 returnVector.resize(m_points.size());
76 // During remove/append animation series may have different number of points,
77 // so ensure we don't go over the index. No need to check for zero points, this
78 // will not be called in such a situation.
79 const int seriesLastIndex = m_series->count() - 1;
76 80
77 81 for (int i = 0; i < m_points.size(); i++) {
78 const QPointF &seriesPoint = m_series->pointAt(i);
82 const QPointF &seriesPoint = m_series->pointAt(qMin(seriesLastIndex, i));
79 83 if (seriesPoint.x() < minX
80 84 || seriesPoint.x() > maxX
81 85 || seriesPoint.y() < minY
82 86 || seriesPoint.y() > maxY) {
83 87 returnVector[i] = true;
84 88 } else {
85 89 returnVector[i] = false;
86 90 }
87 91 }
88 92 return returnVector;
89 93 }
90 94
91 95 void XYChart::updateChart(QVector<QPointF> &oldPoints, QVector<QPointF> &newPoints, int index)
92 96 {
93 97
94 98 if (m_animation) {
95 99 m_animation->setup(oldPoints, newPoints, index);
96 100 m_points = newPoints;
97 101 setDirty(false);
98 102 presenter()->startAnimation(m_animation);
99 103 } else {
100 104 m_points = newPoints;
101 105 updateGeometry();
102 106 }
103 107 }
104 108
105 109 //handlers
106 110
107 111 void XYChart::handlePointAdded(int index)
108 112 {
109 113 Q_ASSERT(index < m_series->count());
110 114 Q_ASSERT(index >= 0);
111 115
112 116 QVector<QPointF> points;
113 117
114 118 if (m_dirty || m_points.isEmpty()) {
115 119 points = domain()->calculateGeometryPoints(m_series->points());
116 120 } else {
117 121 points = m_points;
118 122 QPointF point = domain()->calculateGeometryPoint(m_series->points()[index], m_validData);
119 123 if (!m_validData)
120 124 m_points.clear();
121 125 else
122 126 points.insert(index, point);
123 127 }
124 128
125 129 updateChart(m_points, points, index);
126 130 }
127 131
128 132 void XYChart::handlePointRemoved(int index)
129 133 {
130 134 Q_ASSERT(index <= m_series->count());
131 135 Q_ASSERT(index >= 0);
132 136
133 137 QVector<QPointF> points;
134 138
135 139 if (m_dirty || m_points.isEmpty()) {
136 140 points = domain()->calculateGeometryPoints(m_series->points());
137 141 } else {
138 142 points = m_points;
139 143 points.remove(index);
140 144 }
141 145
142 146 updateChart(m_points, points, index);
143 147 }
144 148
145 149 void XYChart::handlePointReplaced(int index)
146 150 {
147 151 Q_ASSERT(index < m_series->count());
148 152 Q_ASSERT(index >= 0);
149 153
150 154 QVector<QPointF> points;
151 155
152 156 if (m_dirty || m_points.isEmpty()) {
153 157 points = domain()->calculateGeometryPoints(m_series->points());
154 158 } else {
155 159 QPointF point = domain()->calculateGeometryPoint(m_series->points()[index], m_validData);
156 160 if (!m_validData)
157 161 m_points.clear();
158 162 points = m_points;
159 163 if (m_validData)
160 164 points.replace(index, point);
161 165 }
162 166
163 167 updateChart(m_points, points, index);
164 168 }
165 169
166 170 void XYChart::handlePointsReplaced()
167 171 {
168 172 // All the points were replaced -> recalculate
169 173 QVector<QPointF> points = domain()->calculateGeometryPoints(m_series->points());
170 174 updateChart(m_points, points, -1);
171 175 }
172 176
173 177 void XYChart::handleDomainUpdated()
174 178 {
175 179 if (isEmpty()) return;
176 180 QVector<QPointF> points = domain()->calculateGeometryPoints(m_series->points());
177 181 updateChart(m_points, points);
178 182 }
179 183
180 184 bool XYChart::isEmpty()
181 185 {
182 186 return domain()->isEmpty() || m_series->points().isEmpty();
183 187 }
184 188
185 189 #include "moc_xychart_p.cpp"
186 190
187 191 QTCOMMERCIALCHART_END_NAMESPACE
@@ -1,457 +1,520
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 Commercial Charts Add-on.
8 8 **
9 9 ** $QT_BEGIN_LICENSE$
10 10 ** Licensees holding valid Qt Commercial licenses may use this file in
11 11 ** accordance with the Qt Commercial 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 "tst_qxyseries.h"
22 22
23 23 Q_DECLARE_METATYPE(QList<QPointF>)
24 24
25 25 void tst_QXYSeries::initTestCase()
26 26 {
27 27 }
28 28
29 29 void tst_QXYSeries::cleanupTestCase()
30 30 {
31 31 }
32 32
33 33 void tst_QXYSeries::init()
34 34 {
35 35 m_view = new QChartView(newQChartOrQPolarChart());
36 36 m_chart = m_view->chart();
37 37 }
38 38
39 39 void tst_QXYSeries::cleanup()
40 40 {
41 41 delete m_view;
42 42 m_view = 0;
43 43 m_chart = 0;
44 44 m_series = 0;
45 45 }
46 46
47 47 void tst_QXYSeries::seriesName()
48 48 {
49 49 QSignalSpy nameSpy(m_series, SIGNAL(nameChanged()));
50 50 QCOMPARE(m_series->name(), QString());
51 51 m_series->setName("seriesname");
52 52 QCOMPARE(m_series->name(), QString("seriesname"));
53 53 TRY_COMPARE(nameSpy.count(), 1);
54 54 }
55 55
56 56 void tst_QXYSeries::seriesVisible()
57 57 {
58 58 QSignalSpy visibleSpy(m_series, SIGNAL(visibleChanged()));
59 59 QCOMPARE(m_series->isVisible(), true);
60 60 m_series->setVisible(false);
61 61 QCOMPARE(m_series->isVisible(), false);
62 62 m_series->setVisible(true);
63 63 TRY_COMPARE(visibleSpy.count(), 2);
64 64 }
65 65
66 66 void tst_QXYSeries::seriesOpacity()
67 67 {
68 68 QSignalSpy opacitySpy(m_series, SIGNAL(opacityChanged()));
69 69 QCOMPARE(m_series->opacity(), 1.0);
70 70
71 71 m_series->setOpacity(0.5);
72 72 QCOMPARE(m_series->opacity(), 0.5);
73 73 QCOMPARE(opacitySpy.count(), 1);
74 74
75 75 m_series->setOpacity(0.0);
76 76 QCOMPARE(m_series->opacity(), 0.0);
77 77 QCOMPARE(opacitySpy.count(), 2);
78 78
79 79 m_series->setOpacity(1.0);
80 80 QCOMPARE(m_series->opacity(), 1.0);
81 81 QCOMPARE(opacitySpy.count(), 3);
82 82 }
83 83
84 84 void tst_QXYSeries::append_data()
85 85 {
86 86 QTest::addColumn< QList<QPointF> >("points");
87 QTest::newRow("0,0 1,1 2,2 3,3") << (QList<QPointF>() << QPointF(0,0) << QPointF(1,1) << QPointF(2,2) << QPointF(3,3));
88 QTest::newRow("0,0 -1,-1 -2,-2 -3,-3") << (QList<QPointF>() << QPointF(0,0) << QPointF(-1,-1) << QPointF(-2,-2) << QPointF(-3,-3));
87 QTest::addColumn< QList<QPointF> >("otherPoints");
88 QTest::newRow("0,0 1,1 2,2 3,3")
89 << (QList<QPointF>() << QPointF(0,0) << QPointF(1,1) << QPointF(2,2) << QPointF(3,3))
90 << (QList<QPointF>() << QPointF(4,4) << QPointF(5,5) << QPointF(6,6) << QPointF(7,7));
91 QTest::newRow("0,0 -1,-1 -2,-2 -3,-3")
92 << (QList<QPointF>() << QPointF(0,0) << QPointF(-1,-1) << QPointF(-2,-2) << QPointF(-3,-3))
93 << (QList<QPointF>() << QPointF(-4,-4) << QPointF(-5,-5) << QPointF(-6,-6) << QPointF(-7,-7));
89 94 }
90 95
91 96
92 97 void tst_QXYSeries::append_raw_data()
93 98 {
94 99 append_data();
95 100 }
96 101
97 102 void tst_QXYSeries::append_raw()
98 103 {
99 104 QFETCH(QList<QPointF>, points);
105 QFETCH(QList<QPointF>, otherPoints);
100 106 QSignalSpy spy0(m_series, SIGNAL(clicked(QPointF)));
101 107 QSignalSpy addedSpy(m_series, SIGNAL(pointAdded(int)));
102 108 m_series->append(points);
103 109 TRY_COMPARE(spy0.count(), 0);
104 110 TRY_COMPARE(addedSpy.count(), points.count());
105 111 QCOMPARE(m_series->points(), points);
112
113 // Process events between appends
114 foreach (const QPointF &point, otherPoints) {
115 m_series->append(point);
116 QApplication::processEvents();
117 }
106 118 }
107 119
108 120 void tst_QXYSeries::chart_append_data()
109 121 {
110 122 append_data();
111 123 }
112 124
113 125 void tst_QXYSeries::chart_append()
114 126 {
115 127 append_raw();
116 128 m_chart->addSeries(m_series);
117 129 m_view->show();
118 130 QTest::qWaitForWindowShown(m_view);
119 131 }
120 132
121 133 void tst_QXYSeries::append_chart_data()
122 134 {
123 135 append_data();
124 136 }
125 137
126 138 void tst_QXYSeries::append_chart()
127 139 {
128 140 m_view->show();
129 141 m_chart->addSeries(m_series);
130 142 QTest::qWaitForWindowShown(m_view);
131 143 append_raw();
132 144
133 145 }
134 146
135 147 void tst_QXYSeries::append_chart_animation_data()
136 148 {
137 149 append_data();
138 150 }
139 151
140 152 void tst_QXYSeries::append_chart_animation()
141 153 {
142 154 m_chart->setAnimationOptions(QChart::AllAnimations);
143 155 append_chart();
144 156 }
145 157
146 158 void tst_QXYSeries::count_data()
147 159 {
148 160 QTest::addColumn<int>("count");
149 161 QTest::newRow("0") << 0;
150 162 QTest::newRow("5") << 5;
151 163 QTest::newRow("10") << 5;
152 164 }
153 165
154 166 void tst_QXYSeries::count_raw_data()
155 167 {
156 168 count_data();
157 169 }
158 170
159 171 void tst_QXYSeries::count_raw()
160 172 {
161 173 QFETCH(int, count);
162 174
163 175 QSignalSpy spy0(m_series, SIGNAL(clicked(QPointF)));
164 176
165 177 for(int i=0 ; i< count; ++i)
166 178 m_series->append(i,i);
167 179
168 180 TRY_COMPARE(spy0.count(), 0);
169 181 QCOMPARE(m_series->count(), count);
170 182 }
171 183
172 184 void tst_QXYSeries::remove_raw_data()
173 185 {
174 186 append_data();
175 187 }
176 188
177 189 void tst_QXYSeries::remove_raw()
178 190 {
179 191 QFETCH(QList<QPointF>, points);
180 192 QSignalSpy spy0(m_series, SIGNAL(clicked(QPointF)));
181 193 m_series->append(points);
182 194 TRY_COMPARE(spy0.count(), 0);
183 195 QCOMPARE(m_series->points(), points);
184 196
185 197 foreach (const QPointF& point,points)
186 198 m_series->remove(point);
187 199
188 200 QCOMPARE(m_series->points().count(), 0);
189 201 TRY_COMPARE(spy0.count(), 0);
190 202
191 203 m_series->append(points);
192 204 QCOMPARE(m_series->points(), points);
193 205
194 206 //reverse order
195 207 for(int i = points.count()-1 ; i>=0; i--){
196 208 m_series->remove(points[i]);
197 209 }
198 210 QCOMPARE(m_series->points().count(), 0);
211
212 QApplication::processEvents();
213
214 // Process events between removes
215 m_series->append(points);
216 QCOMPARE(m_series->points(), points);
217 foreach (const QPointF &point, points) {
218 m_series->remove(point);
219 QApplication::processEvents();
220 }
221
222 // Actual meaningful delay between removes, but still shorter than animation duration
223 // (simulate e.g. spamming a hypothetical "remove last point"-button)
224 QList<QPointF> bunchOfPoints;
225 for (int i = 0; i < 10; i++)
226 bunchOfPoints.append(QPointF(i, (qreal) rand() / (qreal) RAND_MAX));
227 m_series->replace(bunchOfPoints);
228 QCOMPARE(m_series->points(), bunchOfPoints);
229 QTest::qWait(1500); // Wait for append animations to be over
230 for (int i = bunchOfPoints.count() - 1; i >= 0; i--) {
231 m_series->remove(bunchOfPoints.at(i));
232 QTest::qWait(50);
233 }
199 234 }
200 235
201 236 void tst_QXYSeries::remove_chart_data()
202 237 {
203 238 append_data();
204 239 }
205 240
206 241 void tst_QXYSeries::remove_chart()
207 242 {
208 243 m_view->show();
209 244 m_chart->addSeries(m_series);
210 245 QTest::qWaitForWindowShown(m_view);
211 246 remove_raw();
212 247 }
213 248
214 249 void tst_QXYSeries::remove_chart_animation_data()
215 250 {
216 251 append_data();
217 252 }
218 253
219 254 void tst_QXYSeries::remove_chart_animation()
220 255 {
221 256 m_chart->setAnimationOptions(QChart::AllAnimations);
222 257 remove_chart();
223 258 }
224 259
225 260
226 261 void tst_QXYSeries::clear_raw_data()
227 262 {
228 263 append_data();
229 264 }
230 265
231 266 void tst_QXYSeries::clear_raw()
232 267 {
233 268 QFETCH(QList<QPointF>, points);
234 269 QSignalSpy spy0(m_series, SIGNAL(clicked(QPointF)));
235 270 m_series->append(points);
236 271 TRY_COMPARE(spy0.count(), 0);
237 272 QCOMPARE(m_series->points(), points);
238 273 m_series->clear();
239 274 TRY_COMPARE(spy0.count(), 0);
240 275 QCOMPARE(m_series->points().count(), 0);
276
277 QApplication::processEvents();
241 278 }
242 279
243 280 void tst_QXYSeries::clear_chart_data()
244 281 {
245 282 append_data();
246 283 }
247 284
248 285 void tst_QXYSeries::clear_chart()
249 286 {
250 287 m_view->show();
251 288 m_chart->addSeries(m_series);
252 289 QTest::qWaitForWindowShown(m_view);
253 290 clear_raw();
254 291 }
255 292
256 293 void tst_QXYSeries::clear_chart_animation_data()
257 294 {
258 295 append_data();
259 296 }
260 297
261 298 void tst_QXYSeries::clear_chart_animation()
262 299 {
263 300 m_chart->setAnimationOptions(QChart::AllAnimations);
264 301 clear_chart();
265 302 }
266 303
267 304 void tst_QXYSeries::replace_raw_data()
268 305 {
269 306 append_data();
270 307 }
271 308
272 309 void tst_QXYSeries::replace_raw()
273 310 {
274 311 QFETCH(QList<QPointF>, points);
312 QFETCH(QList<QPointF>, otherPoints);
275 313 QSignalSpy pointReplacedSpy(m_series, SIGNAL(pointReplaced(int)));
276 314 QSignalSpy pointsReplacedSpy(m_series, SIGNAL(pointsReplaced()));
277 315 m_series->append(points);
278 316 TRY_COMPARE(pointReplacedSpy.count(), 0);
279 317 TRY_COMPARE(pointsReplacedSpy.count(), 0);
280 318 QCOMPARE(m_series->points(), points);
281 319
282 320 foreach (const QPointF& point, points)
283 321 m_series->replace(point.x(),point.y(),point.x(),0);
284 322 TRY_COMPARE(pointReplacedSpy.count(), points.count());
285 323 TRY_COMPARE(pointsReplacedSpy.count(), 0);
286 324
287 325 // Replace a point that does not exist
288 326 m_series->replace(-123, 999, 0, 0);
289 327 TRY_COMPARE(pointReplacedSpy.count(), points.count());
290 328 TRY_COMPARE(pointsReplacedSpy.count(), 0);
291 329
292 330 QList<QPointF> newPoints = m_series->points();
293 331 QCOMPARE(newPoints.count(), points.count());
294 332 for(int i =0 ; i<points.count() ; ++i) {
295 333 QCOMPARE(points[i].x(), newPoints[i].x());
296 334 QCOMPARE(newPoints[i].y(), 0.0);
297 335 }
298 336
299 337 // Replace all points
300 338 QList<QPointF> allPoints;
301 339 for (int i = 0; i < 10; i++)
302 340 allPoints.append(QPointF(i, (qreal) rand() / (qreal) RAND_MAX));
303 341 m_series->replace(allPoints);
304 342 TRY_COMPARE(pointReplacedSpy.count(), points.count());
305 343 TRY_COMPARE(pointsReplacedSpy.count(), 1);
344
345 m_series->replace(points);
346 QApplication::processEvents();
347
348 // Process events between replaces
349 for (int i = 0; i < points.count(); ++i) {
350 m_series->replace(points.at(i), otherPoints.at(i));
351 QApplication::processEvents();
352 }
353
354 newPoints = m_series->points();
355 QCOMPARE(newPoints.count(), points.count());
356 for (int i = 0; i < points.count(); ++i) {
357 QCOMPARE(otherPoints.at(i).x(), newPoints.at(i).x());
358 QCOMPARE(otherPoints.at(i).y(), newPoints.at(i).y());
359 }
360
361 // Append followed by a replace shouldn't crash
362 m_series->clear();
363 m_series->append(QPointF(22,22));
364 m_series->append(QPointF(23,23));
365 QApplication::processEvents();
366 m_series->replace(QPointF(23,23), otherPoints.at(1));
367 QCOMPARE(m_series->points().at(1).x(), otherPoints.at(1).x());
368 QCOMPARE(m_series->points().at(1).y(), otherPoints.at(1).y());
306 369 }
307 370
308 371
309 372 void tst_QXYSeries::replace_chart_data()
310 373 {
311 374 append_data();
312 375 }
313 376
314 377 void tst_QXYSeries::replace_chart()
315 378 {
316 379 m_view->show();
317 380 m_chart->addSeries(m_series);
318 381 QTest::qWaitForWindowShown(m_view);
319 382 replace_raw();
320 383 }
321 384
322 385 void tst_QXYSeries::replace_chart_animation_data()
323 386 {
324 387 append_data();
325 388 }
326 389
327 390 void tst_QXYSeries::replace_chart_animation()
328 391 {
329 392 m_chart->setAnimationOptions(QChart::AllAnimations);
330 393 replace_chart();
331 394 }
332 395
333 396 void tst_QXYSeries::insert_data()
334 397 {
335 398 append_data();
336 399 }
337 400
338 401 void tst_QXYSeries::insert()
339 402 {
340 403 QFETCH(QList<QPointF>, points);
341 404 m_series->append(points);
342 405
343 406 QSignalSpy addedSpy(m_series, SIGNAL(pointAdded(int)));
344 407
345 408 m_series->insert(0, QPointF(5, 5));
346 409 TRY_COMPARE(addedSpy.count(), 1);
347 410 QCOMPARE(m_series->points().count(), points.count() + 1);
348 411
349 412 m_series->insert(m_series->count(), QPointF(6, 6));
350 413 TRY_COMPARE(addedSpy.count(), 2);
351 414 QCOMPARE(m_series->points().count(), points.count() + 2);
352 415 }
353 416
354 417 void tst_QXYSeries::oper_data()
355 418 {
356 419 append_data();
357 420 }
358 421
359 422 void tst_QXYSeries::oper()
360 423 {
361 424 QFETCH(QList<QPointF>, points);
362 425
363 426 QSignalSpy spy0(m_series, SIGNAL(clicked(QPointF)));
364 427
365 428 foreach (const QPointF& point,points)
366 429 {
367 430 *m_series<<point;
368 431 }
369 432
370 433 QCOMPARE(m_series->points(), points);
371 434 TRY_COMPARE(spy0.count(), 0);
372 435 }
373 436
374 437
375 438 void tst_QXYSeries::pen_data()
376 439 {
377 440 QTest::addColumn<QPen>("pen");
378 441 QTest::newRow("null") << QPen();
379 442 QTest::newRow("blue") << QPen(Qt::blue);
380 443 QTest::newRow("black") << QPen(Qt::black);
381 444 QTest::newRow("red") << QPen(Qt::red);
382 445 }
383 446
384 447 void tst_QXYSeries::pen()
385 448 {
386 449 QFETCH(QPen, pen);
387 450
388 451 QSignalSpy spy0(m_series, SIGNAL(clicked(QPointF)));
389 452 m_series->setPen(pen);
390 453
391 454 TRY_COMPARE(spy0.count(), 0);
392 455 QCOMPARE(m_series->pen(), pen);
393 456
394 457 m_chart->addSeries(m_series);
395 458
396 459 if (pen != QPen())
397 460 QCOMPARE(m_series->pen(), pen);
398 461
399 462 m_chart->setTheme(QChart::ChartThemeDark);
400 463
401 464 // setting a theme will overwrite all customizations
402 465 if (pen != QPen())
403 466 QVERIFY(m_series->pen() != pen);
404 467 }
405 468
406 469 void tst_QXYSeries::pointsVisible_data()
407 470 {
408 471 QTest::addColumn<bool>("pointsVisible");
409 472 QTest::newRow("true") << true;
410 473 QTest::newRow("false") << false;
411 474 }
412 475
413 476 void tst_QXYSeries::pointsVisible_raw_data()
414 477 {
415 478 pointsVisible_data();
416 479 }
417 480
418 481 void tst_QXYSeries::pointsVisible_raw()
419 482 {
420 483 QFETCH(bool, pointsVisible);
421 484 QSignalSpy spy0(m_series, SIGNAL(clicked(QPointF)));
422 485 m_series->setPointsVisible(pointsVisible);
423 486 TRY_COMPARE(spy0.count(), 0);
424 487 QCOMPARE(m_series->pointsVisible(), pointsVisible);
425 488 }
426 489
427 490 void tst_QXYSeries::changedSignals()
428 491 {
429 492 QSignalSpy visibleSpy(m_series, SIGNAL(visibleChanged()));
430 493 QSignalSpy nameSpy(m_series, SIGNAL(nameChanged()));
431 494 QSignalSpy colorSpy(m_series, SIGNAL(colorChanged(QColor)));
432 495
433 496 // Visibility
434 497 m_series->setVisible(false);
435 498 m_series->setVisible(false);
436 499 TRY_COMPARE(visibleSpy.count(), 1);
437 500 m_series->setVisible(true);
438 501 TRY_COMPARE(visibleSpy.count(), 2);
439 502
440 503 // Color
441 504 m_series->setColor(QColor("aliceblue"));
442 505 TRY_COMPARE(colorSpy.count(), 1);
443 506
444 507 // Pen and Brush
445 508 QPen p = m_series->pen();
446 509 p.setColor("aquamarine");
447 510 m_series->setPen(p);
448 511 QBrush b = m_series->brush();
449 512 b.setColor("beige");
450 513 m_series->setBrush(b);
451 514 TRY_COMPARE(colorSpy.count(), 2);
452 515
453 516 // Verify all the signals again, to make sure no extra signals were emitted
454 517 TRY_COMPARE(visibleSpy.count(), 2);
455 518 TRY_COMPARE(nameSpy.count(), 0);
456 519 TRY_COMPARE(colorSpy.count(), 2);
457 520 }
General Comments 0
You need to be logged in to leave comments. Login now