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