##// END OF EJS Templates
Fix zooming in crash...
Titta Heikkala -
r2603:1e4c29e111d4
parent child
Show More
@@ -1,192 +1,198
1 1 /****************************************************************************
2 2 **
3 3 ** Copyright (C) 2013 Digia Plc
4 4 ** All rights reserved.
5 5 ** For any questions to Digia, please use contact form at http://qt.digia.com
6 6 **
7 7 ** This file is part of the Qt Enterprise Charts Add-on.
8 8 **
9 9 ** $QT_BEGIN_LICENSE$
10 10 ** Licensees holding valid Qt Enterprise licenses may use this file in
11 11 ** accordance with the Qt Enterprise License Agreement provided with the
12 12 ** Software or, alternatively, in accordance with the terms contained in
13 13 ** a written agreement between you and Digia.
14 14 **
15 15 ** If you have questions regarding the use of this file, please use
16 16 ** contact form at http://qt.digia.com
17 17 ** $QT_END_LICENSE$
18 18 **
19 19 ****************************************************************************/
20 20
21 21 #include "areachartitem_p.h"
22 22 #include "qareaseries.h"
23 23 #include "qareaseries_p.h"
24 24 #include "qlineseries.h"
25 25 #include "chartpresenter_p.h"
26 26 #include "abstractdomain_p.h"
27 27 #include <QPainter>
28 28 #include <QGraphicsSceneMouseEvent>
29 29 #include <QDebug>
30 30
31 31
32 32 QTCOMMERCIALCHART_BEGIN_NAMESPACE
33 33
34 34 AreaChartItem::AreaChartItem(QAreaSeries *areaSeries, QGraphicsItem* item)
35 35 : ChartItem(areaSeries->d_func(),item),
36 36 m_series(areaSeries),
37 37 m_upper(0),
38 38 m_lower(0),
39 39 m_pointsVisible(false)
40 40 {
41 41 setAcceptHoverEvents(true);
42 42 setZValue(ChartPresenter::LineChartZValue);
43 43 if (m_series->upperSeries())
44 44 m_upper = new AreaBoundItem(this, m_series->upperSeries());
45 45 if (m_series->lowerSeries())
46 46 m_lower = new AreaBoundItem(this, m_series->lowerSeries());
47 47
48 48 QObject::connect(m_series->d_func(), SIGNAL(updated()), this, SLOT(handleUpdated()));
49 49 QObject::connect(m_series, SIGNAL(visibleChanged()), this, SLOT(handleUpdated()));
50 50 QObject::connect(m_series, SIGNAL(opacityChanged()), this, SLOT(handleUpdated()));
51 51 QObject::connect(this, SIGNAL(clicked(QPointF)), areaSeries, SIGNAL(clicked(QPointF)));
52 52 QObject::connect(this, SIGNAL(hovered(QPointF,bool)), areaSeries, SIGNAL(hovered(QPointF,bool)));
53 53
54 54 handleUpdated();
55 55 }
56 56
57 57 AreaChartItem::~AreaChartItem()
58 58 {
59 59 delete m_upper;
60 60 delete m_lower;
61 61 }
62 62
63 63 void AreaChartItem::setPresenter(ChartPresenter *presenter)
64 64 {
65 65 if (m_upper)
66 66 m_upper->setPresenter(presenter);
67 67 if (m_lower) {
68 68 m_lower->setPresenter(presenter);
69 69 }
70 70 ChartItem::setPresenter(presenter);
71 71 }
72 72
73 73 QRectF AreaChartItem::boundingRect() const
74 74 {
75 75 return m_rect;
76 76 }
77 77
78 78 QPainterPath AreaChartItem::shape() const
79 79 {
80 80 return m_path;
81 81 }
82 82
83 83 void AreaChartItem::updatePath()
84 84 {
85 85 QPainterPath path;
86 86 QRectF rect(QPointF(0,0),domain()->size());
87 87
88 88 path = m_upper->path();
89 89
90 90 if (m_lower) {
91 91 // Note: Polarcharts always draw area correctly only when both series have equal width or are
92 92 // fully displayed. If one series is partally off-chart, the connecting line between
93 93 // the series does not attach to the end of the partially hidden series but to the point
94 94 // where it intersects the axis line. The problem is especially noticeable when one of the series
95 95 // is entirely off-chart, in which case the connecting line connects two ends of the
96 96 // visible series.
97 97 // This happens because we get the paths from linechart, which omits off-chart segments.
98 98 // To properly fix, linechart would need to provide true full path, in right, left, and the rest
99 99 // portions to enable proper clipping. However, combining those to single visually unified area
100 100 // would be a nightmare, since they would have to be painted separately.
101 101 path.connectPath(m_lower->path().toReversed());
102 102 } else {
103 103 QPointF first = path.pointAtPercent(0);
104 104 QPointF last = path.pointAtPercent(1);
105 105 if (presenter()->chartType() == QChart::ChartTypeCartesian) {
106 106 path.lineTo(last.x(), rect.bottom());
107 107 path.lineTo(first.x(), rect.bottom());
108 108 } else { // polar
109 109 path.lineTo(rect.center());
110 110 }
111 111 }
112 112 path.closeSubpath();
113 prepareGeometryChange();
114 m_path = path;
115 m_rect = path.boundingRect();
116 update();
113
114 // Only zoom in if the bounding rect of the path fits inside int limits. QWidget::update() uses
115 // a region that has to be compatible with QRect.
116 if (path.boundingRect().height() <= INT_MAX
117 && path.boundingRect().width() <= INT_MAX) {
118 prepareGeometryChange();
119 m_path = path;
120 m_rect = path.boundingRect();
121 update();
122 }
117 123 }
118 124
119 125 void AreaChartItem::handleUpdated()
120 126 {
121 127 setVisible(m_series->isVisible());
122 128 m_pointsVisible = m_series->pointsVisible();
123 129 m_linePen = m_series->pen();
124 130 m_brush = m_series->brush();
125 131 m_pointPen = m_series->pen();
126 132 m_pointPen.setWidthF(2 * m_pointPen.width());
127 133 setOpacity(m_series->opacity());
128 134 update();
129 135 }
130 136
131 137 void AreaChartItem::handleDomainUpdated()
132 138 {
133 139 if (m_upper) {
134 140 AbstractDomain* d = m_upper->domain();
135 141 d->setSize(domain()->size());
136 142 d->setRange(domain()->minX(),domain()->maxX(),domain()->minY(),domain()->maxY());
137 143 m_upper->handleDomainUpdated();
138 144 }
139 145
140 146 if (m_lower) {
141 147 AbstractDomain* d = m_lower->domain();
142 148 d->setSize(domain()->size());
143 149 d->setRange(domain()->minX(),domain()->maxX(),domain()->minY(),domain()->maxY());
144 150 m_lower->handleDomainUpdated();
145 151 }
146 152 }
147 153
148 154 void AreaChartItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
149 155 {
150 156 Q_UNUSED(widget)
151 157 Q_UNUSED(option)
152 158 painter->save();
153 159 painter->setPen(m_linePen);
154 160 painter->setBrush(m_brush);
155 161 QRectF clipRect = QRectF(QPointF(0, 0), domain()->size());
156 162 if (presenter()->chartType() == QChart::ChartTypePolar)
157 163 painter->setClipRegion(QRegion(clipRect.toRect(), QRegion::Ellipse));
158 164 else
159 165 painter->setClipRect(clipRect);
160 166 painter->drawPath(m_path);
161 167 if (m_pointsVisible) {
162 168 painter->setPen(m_pointPen);
163 169 painter->drawPoints(m_upper->geometryPoints());
164 170 if (m_lower)
165 171 painter->drawPoints(m_lower->geometryPoints());
166 172 }
167 173 painter->restore();
168 174 }
169 175
170 176 void AreaChartItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
171 177 {
172 178 emit clicked(m_upper->domain()->calculateDomainPoint(event->pos()));
173 179 ChartItem::mousePressEvent(event);
174 180 }
175 181
176 182 void AreaChartItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
177 183 {
178 184 emit hovered(domain()->calculateDomainPoint(event->pos()), true);
179 185 event->accept();
180 186 // QGraphicsItem::hoverEnterEvent(event);
181 187 }
182 188
183 189 void AreaChartItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
184 190 {
185 191 emit hovered(domain()->calculateDomainPoint(event->pos()), false);
186 192 event->accept();
187 193 // QGraphicsItem::hoverEnterEvent(event);
188 194 }
189 195
190 196 #include "moc_areachartitem_p.cpp"
191 197
192 198 QTCOMMERCIALCHART_END_NAMESPACE
@@ -1,372 +1,385
1 1 /****************************************************************************
2 2 **
3 3 ** Copyright (C) 2013 Digia Plc
4 4 ** All rights reserved.
5 5 ** For any questions to Digia, please use contact form at http://qt.digia.com
6 6 **
7 7 ** This file is part of the Qt Enterprise Charts Add-on.
8 8 **
9 9 ** $QT_BEGIN_LICENSE$
10 10 ** Licensees holding valid Qt Enterprise licenses may use this file in
11 11 ** accordance with the Qt Enterprise License Agreement provided with the
12 12 ** Software or, alternatively, in accordance with the terms contained in
13 13 ** a written agreement between you and Digia.
14 14 **
15 15 ** If you have questions regarding the use of this file, please use
16 16 ** contact form at http://qt.digia.com
17 17 ** $QT_END_LICENSE$
18 18 **
19 19 ****************************************************************************/
20 20
21 21 #include "linechartitem_p.h"
22 22 #include "qlineseries.h"
23 23 #include "qlineseries_p.h"
24 24 #include "chartpresenter_p.h"
25 25 #include "polardomain_p.h"
26 26 #include <QPainter>
27 27 #include <QGraphicsSceneMouseEvent>
28 28
29 29 QTCOMMERCIALCHART_BEGIN_NAMESPACE
30 30
31 31 const qreal mouseEventMinWidth(12);
32 32
33 33 LineChartItem::LineChartItem(QLineSeries *series, QGraphicsItem *item)
34 34 : XYChart(series,item),
35 35 m_series(series),
36 36 m_pointsVisible(false),
37 37 m_chartType(QChart::ChartTypeUndefined)
38 38 {
39 39 setAcceptHoverEvents(true);
40 40 setZValue(ChartPresenter::LineChartZValue);
41 41 QObject::connect(series->d_func(), SIGNAL(updated()), this, SLOT(handleUpdated()));
42 42 QObject::connect(series, SIGNAL(visibleChanged()), this, SLOT(handleUpdated()));
43 43 QObject::connect(series, SIGNAL(opacityChanged()), this, SLOT(handleUpdated()));
44 44 handleUpdated();
45 45 }
46 46
47 47 QRectF LineChartItem::boundingRect() const
48 48 {
49 49 return m_rect;
50 50 }
51 51
52 52 QPainterPath LineChartItem::shape() const
53 53 {
54 54 return m_shapePath;
55 55 }
56 56
57 57 void LineChartItem::updateGeometry()
58 58 {
59 59 m_points = geometryPoints();
60 60 const QVector<QPointF> &points = m_points;
61 61
62 62 if (points.size() == 0) {
63 63 prepareGeometryChange();
64 64 m_fullPath = QPainterPath();
65 65 m_linePath = QPainterPath();
66 66 m_rect = QRect();
67 67 return;
68 68 }
69 69
70 70 QPainterPath linePath;
71 71 QPainterPath fullPath;
72 72 // Use worst case scenario to determine required margin.
73 73 qreal margin = m_linePen.width() * 1.42;
74 74
75 75 // Area series use component line series that aren't necessarily added to the chart themselves,
76 76 // so check if chart type is forced before trying to obtain it from the chart.
77 77 QChart::ChartType chartType = m_chartType;
78 78 if (chartType == QChart::ChartTypeUndefined)
79 79 chartType = m_series->chart()->chartType();
80 80
81 81 // For polar charts, we need special handling for angular (horizontal)
82 82 // points that are off-grid.
83 83 if (chartType == QChart::ChartTypePolar) {
84 84 QPainterPath linePathLeft;
85 85 QPainterPath linePathRight;
86 86 QPainterPath *currentSegmentPath = 0;
87 87 QPainterPath *previousSegmentPath = 0;
88 88 qreal minX = domain()->minX();
89 89 qreal maxX = domain()->maxX();
90 90 qreal minY = domain()->minY();
91 91 QPointF currentSeriesPoint = m_series->at(0);
92 92 QPointF currentGeometryPoint = points.at(0);
93 93 QPointF previousGeometryPoint = points.at(0);
94 94 int size = m_linePen.width();
95 95 bool pointOffGrid = false;
96 96 bool previousPointWasOffGrid = (currentSeriesPoint.x() < minX || currentSeriesPoint.x() > maxX);
97 97
98 98 qreal domainRadius = domain()->size().height() / 2.0;
99 99 const QPointF centerPoint(domainRadius, domainRadius);
100 100
101 101 if (!previousPointWasOffGrid) {
102 102 fullPath.moveTo(points.at(0));
103 103 if (m_pointsVisible && currentSeriesPoint.y() >= minY) {
104 104 // Do not draw ellipses for points below minimum Y.
105 105 linePath.addEllipse(points.at(0), size, size);
106 106 fullPath.addEllipse(points.at(0), size, size);
107 107 linePath.moveTo(points.at(0));
108 108 fullPath.moveTo(points.at(0));
109 109 }
110 110 }
111 111
112 112 qreal leftMarginLine = centerPoint.x() - margin;
113 113 qreal rightMarginLine = centerPoint.x() + margin;
114 114 qreal horizontal = centerPoint.y();
115 115
116 116 // See ScatterChartItem::updateGeometry() for explanation why seriesLastIndex is needed
117 117 const int seriesLastIndex = m_series->count() - 1;
118 118
119 119 for (int i = 1; i < points.size(); i++) {
120 120 // Interpolating line fragments would be ugly when thick pen is used,
121 121 // so we work around it by utilizing three separate
122 122 // paths for line segments and clip those with custom regions at paint time.
123 123 // "Right" path contains segments that cross the axis line with visible point on the
124 124 // right side of the axis line, as well as segments that have one point within the margin
125 125 // on the right side of the axis line and another point on the right side of the chart.
126 126 // "Left" path contains points with similarly on the left side.
127 127 // "Full" path contains rest of the points.
128 128 // This doesn't yield perfect results always. E.g. when segment covers more than 90
129 129 // degrees and both of the points are within the margin, one in the top half and one in the
130 130 // bottom half of the chart, the bottom one gets clipped incorrectly.
131 131 // However, this should be rare occurrence in any sensible chart.
132 132 currentSeriesPoint = m_series->at(qMin(seriesLastIndex, i));
133 133 currentGeometryPoint = points.at(i);
134 134 pointOffGrid = (currentSeriesPoint.x() < minX || currentSeriesPoint.x() > maxX);
135 135
136 136 // Draw something unless both off-grid
137 137 if (!pointOffGrid || !previousPointWasOffGrid) {
138 138 QPointF intersectionPoint;
139 139 qreal y;
140 140 if (pointOffGrid != previousPointWasOffGrid) {
141 141 if (currentGeometryPoint.x() == previousGeometryPoint.x()) {
142 142 y = currentGeometryPoint.y() + (currentGeometryPoint.y() - previousGeometryPoint.y()) / 2.0;
143 143 } else {
144 144 qreal ratio = (centerPoint.x() - currentGeometryPoint.x()) / (currentGeometryPoint.x() - previousGeometryPoint.x());
145 145 y = currentGeometryPoint.y() + (currentGeometryPoint.y() - previousGeometryPoint.y()) * ratio;
146 146 }
147 147 intersectionPoint = QPointF(centerPoint.x(), y);
148 148 }
149 149
150 150 bool dummyOk; // We know points are ok, but this is needed
151 151 qreal currentAngle = static_cast<PolarDomain *>(domain())->toAngularCoordinate(currentSeriesPoint.x(), dummyOk);
152 152 qreal previousAngle = static_cast<PolarDomain *>(domain())->toAngularCoordinate(m_series->at(i - 1).x(), dummyOk);
153 153
154 154 if ((qAbs(currentAngle - previousAngle) > 180.0)) {
155 155 // If the angle between two points is over 180 degrees (half X range),
156 156 // any direct segment between them becomes meaningless.
157 157 // In this case two line segments are drawn instead, from previous
158 158 // point to the center and from center to current point.
159 159 if ((previousAngle < 0.0 || (previousAngle <= 180.0 && previousGeometryPoint.x() < rightMarginLine))
160 160 && previousGeometryPoint.y() < horizontal) {
161 161 currentSegmentPath = &linePathRight;
162 162 } else if ((previousAngle > 360.0 || (previousAngle > 180.0 && previousGeometryPoint.x() > leftMarginLine))
163 163 && previousGeometryPoint.y() < horizontal) {
164 164 currentSegmentPath = &linePathLeft;
165 165 } else if (previousAngle > 0.0 && previousAngle < 360.0) {
166 166 currentSegmentPath = &linePath;
167 167 } else {
168 168 currentSegmentPath = 0;
169 169 }
170 170
171 171 if (currentSegmentPath) {
172 172 if (previousSegmentPath != currentSegmentPath)
173 173 currentSegmentPath->moveTo(previousGeometryPoint);
174 174 if (previousPointWasOffGrid)
175 175 fullPath.moveTo(intersectionPoint);
176 176
177 177 currentSegmentPath->lineTo(centerPoint);
178 178 fullPath.lineTo(centerPoint);
179 179 }
180 180
181 181 previousSegmentPath = currentSegmentPath;
182 182
183 183 if ((currentAngle < 0.0 || (currentAngle <= 180.0 && currentGeometryPoint.x() < rightMarginLine))
184 184 && currentGeometryPoint.y() < horizontal) {
185 185 currentSegmentPath = &linePathRight;
186 186 } else if ((currentAngle > 360.0 || (currentAngle > 180.0 &&currentGeometryPoint.x() > leftMarginLine))
187 187 && currentGeometryPoint.y() < horizontal) {
188 188 currentSegmentPath = &linePathLeft;
189 189 } else if (currentAngle > 0.0 && currentAngle < 360.0) {
190 190 currentSegmentPath = &linePath;
191 191 } else {
192 192 currentSegmentPath = 0;
193 193 }
194 194
195 195 if (currentSegmentPath) {
196 196 if (previousSegmentPath != currentSegmentPath)
197 197 currentSegmentPath->moveTo(centerPoint);
198 198 if (!previousSegmentPath)
199 199 fullPath.moveTo(centerPoint);
200 200
201 201 currentSegmentPath->lineTo(currentGeometryPoint);
202 202 if (pointOffGrid)
203 203 fullPath.lineTo(intersectionPoint);
204 204 else
205 205 fullPath.lineTo(currentGeometryPoint);
206 206 }
207 207 } else {
208 208 if (previousAngle < 0.0 || currentAngle < 0.0
209 209 || ((previousAngle <= 180.0 && currentAngle <= 180.0)
210 210 && ((previousGeometryPoint.x() < rightMarginLine && previousGeometryPoint.y() < horizontal)
211 211 || (currentGeometryPoint.x() < rightMarginLine && currentGeometryPoint.y() < horizontal)))) {
212 212 currentSegmentPath = &linePathRight;
213 213 } else if (previousAngle > 360.0 || currentAngle > 360.0
214 214 || ((previousAngle > 180.0 && currentAngle > 180.0)
215 215 && ((previousGeometryPoint.x() > leftMarginLine && previousGeometryPoint.y() < horizontal)
216 216 || (currentGeometryPoint.x() > leftMarginLine && currentGeometryPoint.y() < horizontal)))) {
217 217 currentSegmentPath = &linePathLeft;
218 218 } else {
219 219 currentSegmentPath = &linePath;
220 220 }
221 221
222 222 if (currentSegmentPath != previousSegmentPath)
223 223 currentSegmentPath->moveTo(previousGeometryPoint);
224 224 if (previousPointWasOffGrid)
225 225 fullPath.moveTo(intersectionPoint);
226 226
227 227 if (pointOffGrid)
228 228 fullPath.lineTo(intersectionPoint);
229 229 else
230 230 fullPath.lineTo(currentGeometryPoint);
231 231 currentSegmentPath->lineTo(currentGeometryPoint);
232 232 }
233 233 } else {
234 234 currentSegmentPath = 0;
235 235 }
236 236
237 237 previousPointWasOffGrid = pointOffGrid;
238 238 if (m_pointsVisible && !pointOffGrid && currentSeriesPoint.y() >= minY) {
239 239 linePath.addEllipse(points.at(i), size, size);
240 240 fullPath.addEllipse(points.at(i), size, size);
241 241 linePath.moveTo(points.at(i));
242 242 fullPath.moveTo(points.at(i));
243 243 }
244 244 previousSegmentPath = currentSegmentPath;
245 245 previousGeometryPoint = currentGeometryPoint;
246 246 }
247 247 m_linePathPolarRight = linePathRight;
248 248 m_linePathPolarLeft = linePathLeft;
249 249 // Note: This construction of m_fullpath is not perfect. The partial segments that are
250 250 // outside left/right clip regions at axis boundary still generate hover/click events,
251 251 // because shape doesn't get clipped. It doesn't seem possible to do sensibly.
252 252 } else { // not polar
253 253 linePath.moveTo(points.at(0));
254 254 if (m_pointsVisible) {
255 255 int size = m_linePen.width();
256 256 linePath.addEllipse(points.at(0), size, size);
257 257 linePath.moveTo(points.at(0));
258 258 for (int i = 1; i < points.size(); i++) {
259 259 linePath.lineTo(points.at(i));
260 260 linePath.addEllipse(points.at(i), size, size);
261 261 linePath.moveTo(points.at(i));
262 262 }
263 263 } else {
264 264 for (int i = 1; i < points.size(); i++)
265 265 linePath.lineTo(points.at(i));
266 266 }
267 267 fullPath = linePath;
268 268 }
269 269
270 270 QPainterPathStroker stroker;
271 271 // QPainter::drawLine does not respect join styles, for example BevelJoin becomes MiterJoin.
272 272 // This is why we are prepared for the "worst case" scenario, i.e. use always MiterJoin and
273 273 // multiply line width with square root of two when defining shape and bounding rectangle.
274 274 stroker.setWidth(margin);
275 275 stroker.setJoinStyle(Qt::MiterJoin);
276 276 stroker.setCapStyle(Qt::SquareCap);
277 277 stroker.setMiterLimit(m_linePen.miterLimit());
278 278
279 prepareGeometryChange();
279 QPainterPath checkShapePath = stroker.createStroke(fullPath);
280
281 // Only zoom in if the bounding rects of the paths fit inside int limits. QWidget::update() uses
282 // a region that has to be compatible with QRect.
283 if (checkShapePath.boundingRect().height() <= INT_MAX
284 && checkShapePath.boundingRect().width() <= INT_MAX
285 && linePath.boundingRect().height() <= INT_MAX
286 && linePath.boundingRect().width() <= INT_MAX
287 && fullPath.boundingRect().height() <= INT_MAX
288 && fullPath.boundingRect().width() <= INT_MAX) {
289 prepareGeometryChange();
280 290
281 m_linePath = linePath;
282 m_fullPath = fullPath;
283 m_shapePath = stroker.createStroke(fullPath);
291 m_linePath = linePath;
292 m_fullPath = fullPath;
293 m_shapePath = checkShapePath;
284 294
285 m_rect = m_shapePath.boundingRect();
295 m_rect = m_shapePath.boundingRect();
296 } else {
297 update();
298 }
286 299 }
287 300
288 301 void LineChartItem::handleUpdated()
289 302 {
290 303 // If points visiblity has changed, a geometry update is needed.
291 304 // Also, if pen changes when points are visible, geometry update is needed.
292 305 bool doGeometryUpdate =
293 306 (m_pointsVisible != m_series->pointsVisible())
294 307 || (m_series->pointsVisible() && (m_linePen != m_series->pen()));
295 308 setVisible(m_series->isVisible());
296 309 setOpacity(m_series->opacity());
297 310 m_pointsVisible = m_series->pointsVisible();
298 311 m_linePen = m_series->pen();
299 312 if (doGeometryUpdate)
300 313 updateGeometry();
301 314 update();
302 315 }
303 316
304 317 void LineChartItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
305 318 {
306 319 Q_UNUSED(widget)
307 320 Q_UNUSED(option)
308 321
309 322 QRectF clipRect = QRectF(QPointF(0, 0), domain()->size());
310 323
311 324 painter->save();
312 325 painter->setPen(m_linePen);
313 326 bool alwaysUsePath = false;
314 327
315 328 if (m_series->chart()->chartType() == QChart::ChartTypePolar) {
316 329 qreal halfWidth = domain()->size().width() / 2.0;
317 330 QRectF clipRectLeft = QRectF(0, 0, halfWidth, domain()->size().height());
318 331 QRectF clipRectRight = QRectF(halfWidth, 0, halfWidth, domain()->size().height());
319 332 QRegion fullPolarClipRegion(clipRect.toRect(), QRegion::Ellipse);
320 333 QRegion clipRegionLeft(fullPolarClipRegion.intersected(clipRectLeft.toRect()));
321 334 QRegion clipRegionRight(fullPolarClipRegion.intersected(clipRectRight.toRect()));
322 335 painter->setClipRegion(clipRegionLeft);
323 336 painter->drawPath(m_linePathPolarLeft);
324 337 painter->setClipRegion(clipRegionRight);
325 338 painter->drawPath(m_linePathPolarRight);
326 339 painter->setClipRegion(fullPolarClipRegion);
327 340 alwaysUsePath = true; // required for proper clipping
328 341 } else {
329 342 painter->setClipRect(clipRect);
330 343 }
331 344
332 345 if (m_pointsVisible) {
333 346 painter->setBrush(m_linePen.color());
334 347 painter->drawPath(m_linePath);
335 348 } else {
336 349 painter->setBrush(QBrush(Qt::NoBrush));
337 350 if (m_linePen.style() != Qt::SolidLine || alwaysUsePath) {
338 351 // If pen style is not solid line, always fall back to path painting
339 352 // to ensure proper continuity of the pattern
340 353 painter->drawPath(m_linePath);
341 354 } else {
342 355 for (int i(1); i < m_points.size(); i++)
343 356 painter->drawLine(m_points.at(i - 1), m_points.at(i));
344 357 }
345 358 }
346 359
347 360 painter->restore();
348 361 }
349 362
350 363 void LineChartItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
351 364 {
352 365 emit XYChart::clicked(domain()->calculateDomainPoint(event->pos()));
353 366 QGraphicsItem::mousePressEvent(event);
354 367 }
355 368
356 369 void LineChartItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
357 370 {
358 371 emit XYChart::hovered(domain()->calculateDomainPoint(event->pos()), true);
359 372 // event->accept();
360 373 QGraphicsItem::hoverEnterEvent(event);
361 374 }
362 375
363 376 void LineChartItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
364 377 {
365 378 emit XYChart::hovered(domain()->calculateDomainPoint(event->pos()), false);
366 379 // event->accept();
367 380 QGraphicsItem::hoverEnterEvent(event);
368 381 }
369 382
370 383 #include "moc_linechartitem_p.cpp"
371 384
372 385 QTCOMMERCIALCHART_END_NAMESPACE
@@ -1,206 +1,211
1 1 /****************************************************************************
2 2 **
3 3 ** Copyright (C) 2013 Digia Plc
4 4 ** All rights reserved.
5 5 ** For any questions to Digia, please use contact form at http://qt.digia.com
6 6 **
7 7 ** This file is part of the Qt Enterprise Charts Add-on.
8 8 **
9 9 ** $QT_BEGIN_LICENSE$
10 10 ** Licensees holding valid Qt Enterprise licenses may use this file in
11 11 ** accordance with the Qt Enterprise License Agreement provided with the
12 12 ** Software or, alternatively, in accordance with the terms contained in
13 13 ** a written agreement between you and Digia.
14 14 **
15 15 ** If you have questions regarding the use of this file, please use
16 16 ** contact form at http://qt.digia.com
17 17 ** $QT_END_LICENSE$
18 18 **
19 19 ****************************************************************************/
20 20
21 21 #include "scatterchartitem_p.h"
22 22 #include "qscatterseries.h"
23 23 #include "qscatterseries_p.h"
24 24 #include "chartpresenter_p.h"
25 25 #include "abstractdomain_p.h"
26 26 #include "qchart.h"
27 27 #include <QPainter>
28 28 #include <QGraphicsScene>
29 29 #include <QDebug>
30 30 #include <QGraphicsSceneMouseEvent>
31 31
32 32 QTCOMMERCIALCHART_BEGIN_NAMESPACE
33 33
34 34 ScatterChartItem::ScatterChartItem(QScatterSeries *series, QGraphicsItem *item)
35 35 : XYChart(series,item),
36 36 m_series(series),
37 37 m_items(this),
38 38 m_visible(true),
39 39 m_shape(QScatterSeries::MarkerShapeRectangle),
40 40 m_size(15)
41 41 {
42 42 QObject::connect(m_series->d_func(), SIGNAL(updated()), this, SLOT(handleUpdated()));
43 43 QObject::connect(m_series, SIGNAL(visibleChanged()), this, SLOT(handleUpdated()));
44 44 QObject::connect(m_series, SIGNAL(opacityChanged()), this, SLOT(handleUpdated()));
45 45
46 46 setZValue(ChartPresenter::ScatterSeriesZValue);
47 47 setFlags(QGraphicsItem::ItemClipsChildrenToShape);
48 48
49 49 handleUpdated();
50 50
51 51 m_items.setHandlesChildEvents(false);
52 52 }
53 53
54 54 QRectF ScatterChartItem::boundingRect() const
55 55 {
56 56 return m_rect;
57 57 }
58 58
59 59 void ScatterChartItem::createPoints(int count)
60 60 {
61 61 for (int i = 0; i < count; ++i) {
62 62
63 63 QGraphicsItem *item = 0;
64 64
65 65 switch (m_shape) {
66 66 case QScatterSeries::MarkerShapeCircle: {
67 67 item = new CircleMarker(0, 0, m_size, m_size, this);
68 68 const QRectF &rect = item->boundingRect();
69 69 item->setPos(-rect.width() / 2, -rect.height() / 2);
70 70 break;
71 71 }
72 72 case QScatterSeries::MarkerShapeRectangle:
73 73 item = new RectangleMarker(0, 0, m_size, m_size, this);
74 74 item->setPos(-m_size / 2, -m_size / 2);
75 75 break;
76 76 default:
77 77 qWarning() << "Unsupported marker type";
78 78 break;
79 79 }
80 80 m_items.addToGroup(item);
81 81 }
82 82 }
83 83
84 84 void ScatterChartItem::deletePoints(int count)
85 85 {
86 86 QList<QGraphicsItem *> items = m_items.childItems();
87 87
88 88 for (int i = 0; i < count; ++i) {
89 89 QGraphicsItem *item = items.takeLast();
90 90 m_markerMap.remove(item);
91 91 delete(item);
92 92 }
93 93 }
94 94
95 95 void ScatterChartItem::markerSelected(QGraphicsItem *marker)
96 96 {
97 97 emit XYChart::clicked(m_markerMap[marker]);
98 98 }
99 99
100 100 void ScatterChartItem::markerHovered(QGraphicsItem *marker, bool state)
101 101 {
102 102 emit XYChart::hovered(m_markerMap[marker], state);
103 103 }
104 104
105 105 void ScatterChartItem::updateGeometry()
106 106 {
107 107
108 108 const QVector<QPointF>& points = geometryPoints();
109 109
110 110 if (points.size() == 0) {
111 111 deletePoints(m_items.childItems().count());
112 112 return;
113 113 }
114 114
115 115 int diff = m_items.childItems().size() - points.size();
116 116
117 117 if (diff > 0)
118 118 deletePoints(diff);
119 119 else if (diff < 0)
120 120 createPoints(-diff);
121 121
122 122 if (diff != 0)
123 123 handleUpdated();
124 124
125 125 QList<QGraphicsItem *> items = m_items.childItems();
126 126
127 127 QRectF clipRect(QPointF(0,0),domain()->size());
128 128
129 QVector<bool> offGridStatus = offGridStatusVector();
130 const int seriesLastIndex = m_series->count() - 1;
131
132 for (int i = 0; i < points.size(); i++) {
133 QGraphicsItem *item = items.at(i);
134 const QPointF &point = points.at(i);
135 const QRectF &rect = item->boundingRect();
136 // During remove 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. After remove animation stops, geometry is updated to correct one.
143 m_markerMap[item] = m_series->at(qMin(seriesLastIndex, i));
144 item->setPos(point.x() - rect.width() / 2, point.y() - rect.height() / 2);
145
146 if (!m_visible || offGridStatus.at(i))
147 item->setVisible(false);
148 else
149 item->setVisible(true);
150 }
129 // Only zoom in if the clipRect fits inside int limits. QWidget::update() uses
130 // a region that has to be compatible with QRect.
131 if (clipRect.height() <= INT_MAX
132 && clipRect.width() <= INT_MAX) {
133 QVector<bool> offGridStatus = offGridStatusVector();
134 const int seriesLastIndex = m_series->count() - 1;
135
136 for (int i = 0; i < points.size(); i++) {
137 QGraphicsItem *item = items.at(i);
138 const QPointF &point = points.at(i);
139 const QRectF &rect = item->boundingRect();
140 // During remove animation series may have different number of points,
141 // so ensure we don't go over the index. Animation handling itself ensures that
142 // if there is actually no points in the series, then it won't generate a fake point,
143 // so we can be assured there is always at least one point in m_series here.
144 // Note that marker map values can be technically incorrect during the animation,
145 // if it was caused by an insert, but this shouldn't be a problem as the points are
146 // fake anyway. After remove animation stops, geometry is updated to correct one.
147 m_markerMap[item] = m_series->at(qMin(seriesLastIndex, i));
148 item->setPos(point.x() - rect.width() / 2, point.y() - rect.height() / 2);
149
150 if (!m_visible || offGridStatus.at(i))
151 item->setVisible(false);
152 else
153 item->setVisible(true);
154 }
151 155
152 prepareGeometryChange();
153 m_rect = clipRect;
156 prepareGeometryChange();
157 m_rect = clipRect;
158 }
154 159 }
155 160
156 161 void ScatterChartItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
157 162 {
158 163 Q_UNUSED(painter)
159 164 Q_UNUSED(option)
160 165 Q_UNUSED(widget)
161 166 }
162 167
163 168 void ScatterChartItem::setPen(const QPen &pen)
164 169 {
165 170 foreach (QGraphicsItem *item , m_items.childItems())
166 171 static_cast<QAbstractGraphicsShapeItem*>(item)->setPen(pen);
167 172 }
168 173
169 174 void ScatterChartItem::setBrush(const QBrush &brush)
170 175 {
171 176 foreach (QGraphicsItem *item , m_items.childItems())
172 177 static_cast<QAbstractGraphicsShapeItem*>(item)->setBrush(brush);
173 178 }
174 179
175 180 void ScatterChartItem::handleUpdated()
176 181 {
177 182 int count = m_items.childItems().count();
178 183
179 184 if (count == 0)
180 185 return;
181 186
182 187 bool recreate = m_visible != m_series->isVisible()
183 188 || m_size != m_series->markerSize()
184 189 || m_shape != m_series->markerShape();
185 190
186 191 m_visible = m_series->isVisible();
187 192 m_size = m_series->markerSize();
188 193 m_shape = m_series->markerShape();
189 194 setOpacity(m_series->opacity());
190 195
191 196 if (recreate) {
192 197 deletePoints(count);
193 198 createPoints(count);
194 199
195 200 // Updating geometry is now safe, because it won't call handleUpdated unless it creates/deletes points
196 201 updateGeometry();
197 202 }
198 203
199 204 setPen(m_series->pen());
200 205 setBrush(m_series->brush());
201 206 update();
202 207 }
203 208
204 209 #include "moc_scatterchartitem_p.cpp"
205 210
206 211 QTCOMMERCIALCHART_END_NAMESPACE
@@ -1,459 +1,468
1 1 /****************************************************************************
2 2 **
3 3 ** Copyright (C) 2013 Digia Plc
4 4 ** All rights reserved.
5 5 ** For any questions to Digia, please use contact form at http://qt.digia.com
6 6 **
7 7 ** This file is part of the Qt Enterprise Charts Add-on.
8 8 **
9 9 ** $QT_BEGIN_LICENSE$
10 10 ** Licensees holding valid Qt Enterprise licenses may use this file in
11 11 ** accordance with the Qt Enterprise License Agreement provided with the
12 12 ** Software or, alternatively, in accordance with the terms contained in
13 13 ** a written agreement between you and Digia.
14 14 **
15 15 ** If you have questions regarding the use of this file, please use
16 16 ** contact form at http://qt.digia.com
17 17 ** $QT_END_LICENSE$
18 18 **
19 19 ****************************************************************************/
20 20
21 21 #include "splinechartitem_p.h"
22 22 #include "qsplineseries_p.h"
23 23 #include "chartpresenter_p.h"
24 24 #include "splineanimation_p.h"
25 25 #include "polardomain_p.h"
26 26 #include <QPainter>
27 27 #include <QGraphicsSceneMouseEvent>
28 28
29 29 QTCOMMERCIALCHART_BEGIN_NAMESPACE
30 30
31 31 SplineChartItem::SplineChartItem(QSplineSeries *series, QGraphicsItem *item)
32 32 : XYChart(series,item),
33 33 m_series(series),
34 34 m_pointsVisible(false),
35 35 m_animation(0)
36 36 {
37 37 setAcceptHoverEvents(true);
38 38 setZValue(ChartPresenter::SplineChartZValue);
39 39 QObject::connect(m_series->d_func(), SIGNAL(updated()), this, SLOT(handleUpdated()));
40 40 QObject::connect(series, SIGNAL(visibleChanged()), this, SLOT(handleUpdated()));
41 41 QObject::connect(series, SIGNAL(opacityChanged()), this, SLOT(handleUpdated()));
42 42 handleUpdated();
43 43 }
44 44
45 45 QRectF SplineChartItem::boundingRect() const
46 46 {
47 47 return m_rect;
48 48 }
49 49
50 50 QPainterPath SplineChartItem::shape() const
51 51 {
52 52 return m_fullPath;
53 53 }
54 54
55 55 void SplineChartItem::setAnimation(SplineAnimation *animation)
56 56 {
57 57 m_animation = animation;
58 58 XYChart::setAnimation(animation);
59 59 }
60 60
61 61 ChartAnimation *SplineChartItem::animation() const
62 62 {
63 63 return m_animation;
64 64 }
65 65
66 66 void SplineChartItem::setControlGeometryPoints(QVector<QPointF>& points)
67 67 {
68 68 m_controlPoints = points;
69 69 }
70 70
71 71 QVector<QPointF> SplineChartItem::controlGeometryPoints() const
72 72 {
73 73 return m_controlPoints;
74 74 }
75 75
76 76 void SplineChartItem::updateChart(QVector<QPointF> &oldPoints, QVector<QPointF> &newPoints, int index)
77 77 {
78 78 QVector<QPointF> controlPoints;
79 79 if (newPoints.count() >= 2)
80 80 controlPoints = calculateControlPoints(newPoints);
81 81
82 82 if (m_animation)
83 83 m_animation->setup(oldPoints, newPoints, m_controlPoints, controlPoints, index);
84 84
85 85 m_points = newPoints;
86 86 m_controlPoints = controlPoints;
87 87 setDirty(false);
88 88
89 89 if (m_animation)
90 90 presenter()->startAnimation(m_animation);
91 91 else
92 92 updateGeometry();
93 93 }
94 94
95 95 void SplineChartItem::updateGeometry()
96 96 {
97 97 const QVector<QPointF> &points = m_points;
98 98 const QVector<QPointF> &controlPoints = m_controlPoints;
99 99
100 100 if ((points.size() < 2) || (controlPoints.size() < 2)) {
101 101 prepareGeometryChange();
102 102 m_path = QPainterPath();
103 103 m_rect = QRect();
104 104 return;
105 105 }
106 106
107 107 Q_ASSERT(points.count() * 2 - 2 == controlPoints.count());
108 108
109 109 QPainterPath splinePath;
110 110 QPainterPath fullPath;
111 111 // Use worst case scenario to determine required margin.
112 112 qreal margin = m_linePen.width() * 1.42;
113 113
114 114 if (m_series->chart()->chartType() == QChart::ChartTypePolar) {
115 115 QPainterPath splinePathLeft;
116 116 QPainterPath splinePathRight;
117 117 QPainterPath *currentSegmentPath = 0;
118 118 QPainterPath *previousSegmentPath = 0;
119 119 qreal minX = domain()->minX();
120 120 qreal maxX = domain()->maxX();
121 121 qreal minY = domain()->minY();
122 122 QPointF currentSeriesPoint = m_series->at(0);
123 123 QPointF currentGeometryPoint = points.at(0);
124 124 QPointF previousGeometryPoint = points.at(0);
125 125 bool pointOffGrid = false;
126 126 bool previousPointWasOffGrid = (currentSeriesPoint.x() < minX || currentSeriesPoint.x() > maxX);
127 127 m_visiblePoints.clear();
128 128 m_visiblePoints.reserve(points.size());
129 129
130 130 qreal domainRadius = domain()->size().height() / 2.0;
131 131 const QPointF centerPoint(domainRadius, domainRadius);
132 132
133 133 if (!previousPointWasOffGrid) {
134 134 fullPath.moveTo(points.at(0));
135 135 // Do not draw points for points below minimum Y.
136 136 if (m_pointsVisible && currentSeriesPoint.y() >= minY)
137 137 m_visiblePoints.append(currentGeometryPoint);
138 138 }
139 139
140 140 qreal leftMarginLine = centerPoint.x() - margin;
141 141 qreal rightMarginLine = centerPoint.x() + margin;
142 142 qreal horizontal = centerPoint.y();
143 143
144 144 // See ScatterChartItem::updateGeometry() for explanation why seriesLastIndex is needed
145 145 const int seriesLastIndex = m_series->count() - 1;
146 146
147 147 for (int i = 1; i < points.size(); i++) {
148 148 // Interpolating spline fragments accurately is not trivial, and would anyway be ugly
149 149 // when thick pen is used, so we work around it by utilizing three separate
150 150 // paths for spline segments and clip those with custom regions at paint time.
151 151 // "Right" path contains segments that cross the axis line with visible point on the
152 152 // right side of the axis line, as well as segments that have one point within the margin
153 153 // on the right side of the axis line and another point on the right side of the chart.
154 154 // "Left" path contains points with similarly on the left side.
155 155 // "Full" path contains rest of the points.
156 156 // This doesn't yield perfect results always. E.g. when segment covers more than 90
157 157 // degrees and both of the points are within the margin, one in the top half and one in the
158 158 // bottom half of the chart, the bottom one gets clipped incorrectly.
159 159 // However, this should be rare occurrence in any sensible chart.
160 160 currentSeriesPoint = m_series->at(qMin(seriesLastIndex, i));
161 161 currentGeometryPoint = points.at(i);
162 162 pointOffGrid = (currentSeriesPoint.x() < minX || currentSeriesPoint.x() > maxX);
163 163
164 164 // Draw something unless both off-grid
165 165 if (!pointOffGrid || !previousPointWasOffGrid) {
166 166 bool dummyOk; // We know points are ok, but this is needed
167 167 qreal currentAngle = static_cast<PolarDomain *>(domain())->toAngularCoordinate(currentSeriesPoint.x(), dummyOk);
168 168 qreal previousAngle = static_cast<PolarDomain *>(domain())->toAngularCoordinate(m_series->at(i - 1).x(), dummyOk);
169 169
170 170 if ((qAbs(currentAngle - previousAngle) > 180.0)) {
171 171 // If the angle between two points is over 180 degrees (half X range),
172 172 // any direct segment between them becomes meaningless.
173 173 // In this case two line segments are drawn instead, from previous
174 174 // point to the center and from center to current point.
175 175 if ((previousAngle < 0.0 || (previousAngle <= 180.0 && previousGeometryPoint.x() < rightMarginLine))
176 176 && previousGeometryPoint.y() < horizontal) {
177 177 currentSegmentPath = &splinePathRight;
178 178 } else if ((previousAngle > 360.0 || (previousAngle > 180.0 && previousGeometryPoint.x() > leftMarginLine))
179 179 && previousGeometryPoint.y() < horizontal) {
180 180 currentSegmentPath = &splinePathLeft;
181 181 } else if (previousAngle > 0.0 && previousAngle < 360.0) {
182 182 currentSegmentPath = &splinePath;
183 183 } else {
184 184 currentSegmentPath = 0;
185 185 }
186 186
187 187 if (currentSegmentPath) {
188 188 if (previousSegmentPath != currentSegmentPath)
189 189 currentSegmentPath->moveTo(previousGeometryPoint);
190 190 if (!previousSegmentPath)
191 191 fullPath.moveTo(previousGeometryPoint);
192 192
193 193 currentSegmentPath->lineTo(centerPoint);
194 194 fullPath.lineTo(centerPoint);
195 195 }
196 196
197 197 previousSegmentPath = currentSegmentPath;
198 198
199 199 if ((currentAngle < 0.0 || (currentAngle <= 180.0 && currentGeometryPoint.x() < rightMarginLine))
200 200 && currentGeometryPoint.y() < horizontal) {
201 201 currentSegmentPath = &splinePathRight;
202 202 } else if ((currentAngle > 360.0 || (currentAngle > 180.0 &&currentGeometryPoint.x() > leftMarginLine))
203 203 && currentGeometryPoint.y() < horizontal) {
204 204 currentSegmentPath = &splinePathLeft;
205 205 } else if (currentAngle > 0.0 && currentAngle < 360.0) {
206 206 currentSegmentPath = &splinePath;
207 207 } else {
208 208 currentSegmentPath = 0;
209 209 }
210 210
211 211 if (currentSegmentPath) {
212 212 if (previousSegmentPath != currentSegmentPath)
213 213 currentSegmentPath->moveTo(centerPoint);
214 214 if (!previousSegmentPath)
215 215 fullPath.moveTo(centerPoint);
216 216
217 217 currentSegmentPath->lineTo(currentGeometryPoint);
218 218 fullPath.lineTo(currentGeometryPoint);
219 219 }
220 220 } else {
221 221 QPointF cp1 = controlPoints[2 * (i - 1)];
222 222 QPointF cp2 = controlPoints[(2 * i) - 1];
223 223
224 224 if (previousAngle < 0.0 || currentAngle < 0.0
225 225 || ((previousAngle <= 180.0 && currentAngle <= 180.0)
226 226 && ((previousGeometryPoint.x() < rightMarginLine && previousGeometryPoint.y() < horizontal)
227 227 || (currentGeometryPoint.x() < rightMarginLine && currentGeometryPoint.y() < horizontal)))) {
228 228 currentSegmentPath = &splinePathRight;
229 229 } else if (previousAngle > 360.0 || currentAngle > 360.0
230 230 || ((previousAngle > 180.0 && currentAngle > 180.0)
231 231 && ((previousGeometryPoint.x() > leftMarginLine && previousGeometryPoint.y() < horizontal)
232 232 || (currentGeometryPoint.x() > leftMarginLine && currentGeometryPoint.y() < horizontal)))) {
233 233 currentSegmentPath = &splinePathLeft;
234 234 } else {
235 235 currentSegmentPath = &splinePath;
236 236 }
237 237
238 238 if (currentSegmentPath != previousSegmentPath)
239 239 currentSegmentPath->moveTo(previousGeometryPoint);
240 240 if (!previousSegmentPath)
241 241 fullPath.moveTo(previousGeometryPoint);
242 242
243 243 fullPath.cubicTo(cp1, cp2, currentGeometryPoint);
244 244 currentSegmentPath->cubicTo(cp1, cp2, currentGeometryPoint);
245 245 }
246 246 } else {
247 247 currentSegmentPath = 0;
248 248 }
249 249
250 250 previousPointWasOffGrid = pointOffGrid;
251 251 if (!pointOffGrid && m_pointsVisible && currentSeriesPoint.y() >= minY)
252 252 m_visiblePoints.append(currentGeometryPoint);
253 253 previousSegmentPath = currentSegmentPath;
254 254 previousGeometryPoint = currentGeometryPoint;
255 255 }
256 256
257 257 m_pathPolarRight = splinePathRight;
258 258 m_pathPolarLeft = splinePathLeft;
259 259 // Note: This construction of m_fullpath is not perfect. The partial segments that are
260 260 // outside left/right clip regions at axis boundary still generate hover/click events,
261 261 // because shape doesn't get clipped. It doesn't seem possible to do sensibly.
262 262 } else { // not polar
263 263 splinePath.moveTo(points.at(0));
264 264 for (int i = 0; i < points.size() - 1; i++) {
265 265 const QPointF &point = points.at(i + 1);
266 266 splinePath.cubicTo(controlPoints[2 * i], controlPoints[2 * i + 1], point);
267 267 }
268 268 fullPath = splinePath;
269 269 }
270 m_path = splinePath;
271 270
272 271 QPainterPathStroker stroker;
273 272 // The full path is comprised of three separate paths.
274 273 // This is why we are prepared for the "worst case" scenario, i.e. use always MiterJoin and
275 274 // multiply line width with square root of two when defining shape and bounding rectangle.
276 275 stroker.setWidth(margin);
277 276 stroker.setJoinStyle(Qt::MiterJoin);
278 277 stroker.setCapStyle(Qt::SquareCap);
279 278 stroker.setMiterLimit(m_linePen.miterLimit());
280 279
281 prepareGeometryChange();
280 // Only zoom in if the bounding rects of the path fit inside int limits. QWidget::update() uses
281 // a region that has to be compatible with QRect.
282 QPainterPath checkShapePath = stroker.createStroke(fullPath);
283 if (checkShapePath.boundingRect().height() <= INT_MAX
284 && checkShapePath.boundingRect().width() <= INT_MAX
285 && splinePath.boundingRect().height() <= INT_MAX
286 && splinePath.boundingRect().width() <= INT_MAX) {
287 m_path = splinePath;
282 288
283 m_fullPath = stroker.createStroke(fullPath);
284 m_rect = m_fullPath.boundingRect();
289 prepareGeometryChange();
290
291 m_fullPath = checkShapePath;
292 m_rect = m_fullPath.boundingRect();
293 }
285 294 }
286 295
287 296 /*!
288 297 Calculates control points which are needed by QPainterPath.cubicTo function to draw the cubic Bezier cureve between two points.
289 298 */
290 299 QVector<QPointF> SplineChartItem::calculateControlPoints(const QVector<QPointF> &points)
291 300 {
292 301 QVector<QPointF> controlPoints;
293 302 controlPoints.resize(points.count() * 2 - 2);
294 303
295 304 int n = points.count() - 1;
296 305
297 306 if (n == 1) {
298 307 //for n==1
299 308 controlPoints[0].setX((2 * points[0].x() + points[1].x()) / 3);
300 309 controlPoints[0].setY((2 * points[0].y() + points[1].y()) / 3);
301 310 controlPoints[1].setX(2 * controlPoints[0].x() - points[0].x());
302 311 controlPoints[1].setY(2 * controlPoints[0].y() - points[0].y());
303 312 return controlPoints;
304 313 }
305 314
306 315 // Calculate first Bezier control points
307 316 // Set of equations for P0 to Pn points.
308 317 //
309 318 // | 2 1 0 0 ... 0 0 0 ... 0 0 0 | | P1_1 | | P0 + 2 * P1 |
310 319 // | 1 4 1 0 ... 0 0 0 ... 0 0 0 | | P1_2 | | 4 * P1 + 2 * P2 |
311 320 // | 0 1 4 1 ... 0 0 0 ... 0 0 0 | | P1_3 | | 4 * P2 + 2 * P3 |
312 321 // | . . . . . . . . . . . . | | ... | | ... |
313 322 // | 0 0 0 0 ... 1 4 1 ... 0 0 0 | * | P1_i | = | 4 * P(i-1) + 2 * Pi |
314 323 // | . . . . . . . . . . . . | | ... | | ... |
315 324 // | 0 0 0 0 0 0 0 0 ... 1 4 1 | | P1_(n-1)| | 4 * P(n-2) + 2 * P(n-1) |
316 325 // | 0 0 0 0 0 0 0 0 ... 0 2 7 | | P1_n | | 8 * P(n-1) + Pn |
317 326 //
318 327 QVector<qreal> vector;
319 328 vector.resize(n);
320 329
321 330 vector[0] = points[0].x() + 2 * points[1].x();
322 331
323 332
324 333 for (int i = 1; i < n - 1; ++i)
325 334 vector[i] = 4 * points[i].x() + 2 * points[i + 1].x();
326 335
327 336 vector[n - 1] = (8 * points[n - 1].x() + points[n].x()) / 2.0;
328 337
329 338 QVector<qreal> xControl = firstControlPoints(vector);
330 339
331 340 vector[0] = points[0].y() + 2 * points[1].y();
332 341
333 342 for (int i = 1; i < n - 1; ++i)
334 343 vector[i] = 4 * points[i].y() + 2 * points[i + 1].y();
335 344
336 345 vector[n - 1] = (8 * points[n - 1].y() + points[n].y()) / 2.0;
337 346
338 347 QVector<qreal> yControl = firstControlPoints(vector);
339 348
340 349 for (int i = 0, j = 0; i < n; ++i, ++j) {
341 350
342 351 controlPoints[j].setX(xControl[i]);
343 352 controlPoints[j].setY(yControl[i]);
344 353
345 354 j++;
346 355
347 356 if (i < n - 1) {
348 357 controlPoints[j].setX(2 * points[i + 1].x() - xControl[i + 1]);
349 358 controlPoints[j].setY(2 * points[i + 1].y() - yControl[i + 1]);
350 359 } else {
351 360 controlPoints[j].setX((points[n].x() + xControl[n - 1]) / 2);
352 361 controlPoints[j].setY((points[n].y() + yControl[n - 1]) / 2);
353 362 }
354 363 }
355 364 return controlPoints;
356 365 }
357 366
358 367 QVector<qreal> SplineChartItem::firstControlPoints(const QVector<qreal>& vector)
359 368 {
360 369 QVector<qreal> result;
361 370
362 371 int count = vector.count();
363 372 result.resize(count);
364 373 result[0] = vector[0] / 2.0;
365 374
366 375 QVector<qreal> temp;
367 376 temp.resize(count);
368 377 temp[0] = 0;
369 378
370 379 qreal b = 2.0;
371 380
372 381 for (int i = 1; i < count; i++) {
373 382 temp[i] = 1 / b;
374 383 b = (i < count - 1 ? 4.0 : 3.5) - temp[i];
375 384 result[i] = (vector[i] - result[i - 1]) / b;
376 385 }
377 386
378 387 for (int i = 1; i < count; i++)
379 388 result[count - i - 1] -= temp[count - i] * result[count - i];
380 389
381 390 return result;
382 391 }
383 392
384 393 //handlers
385 394
386 395 void SplineChartItem::handleUpdated()
387 396 {
388 397 setVisible(m_series->isVisible());
389 398 setOpacity(m_series->opacity());
390 399 m_pointsVisible = m_series->pointsVisible();
391 400 m_linePen = m_series->pen();
392 401 m_pointPen = m_series->pen();
393 402 m_pointPen.setWidthF(2 * m_pointPen.width());
394 403 update();
395 404 }
396 405
397 406 //painter
398 407
399 408 void SplineChartItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
400 409 {
401 410 Q_UNUSED(widget)
402 411 Q_UNUSED(option)
403 412
404 413 QRectF clipRect = QRectF(QPointF(0, 0), domain()->size());
405 414
406 415 painter->save();
407 416 painter->setPen(m_linePen);
408 417 painter->setBrush(Qt::NoBrush);
409 418
410 419 if (m_series->chart()->chartType() == QChart::ChartTypePolar) {
411 420 qreal halfWidth = domain()->size().width() / 2.0;
412 421 QRectF clipRectLeft = QRectF(0, 0, halfWidth, domain()->size().height());
413 422 QRectF clipRectRight = QRectF(halfWidth, 0, halfWidth, domain()->size().height());
414 423 QRegion fullPolarClipRegion(clipRect.toRect(), QRegion::Ellipse);
415 424 QRegion clipRegionLeft(fullPolarClipRegion.intersected(clipRectLeft.toRect()));
416 425 QRegion clipRegionRight(fullPolarClipRegion.intersected(clipRectRight.toRect()));
417 426 painter->setClipRegion(clipRegionLeft);
418 427 painter->drawPath(m_pathPolarLeft);
419 428 painter->setClipRegion(clipRegionRight);
420 429 painter->drawPath(m_pathPolarRight);
421 430 painter->setClipRegion(fullPolarClipRegion);
422 431 } else {
423 432 painter->setClipRect(clipRect);
424 433 }
425 434
426 435 painter->drawPath(m_path);
427 436
428 437 if (m_pointsVisible) {
429 438 painter->setPen(m_pointPen);
430 439 if (m_series->chart()->chartType() == QChart::ChartTypePolar)
431 440 painter->drawPoints(m_visiblePoints);
432 441 else
433 442 painter->drawPoints(geometryPoints());
434 443 }
435 444
436 445 painter->restore();
437 446 }
438 447
439 448 void SplineChartItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
440 449 {
441 450 emit XYChart::clicked(domain()->calculateDomainPoint(event->pos()));
442 451 QGraphicsItem::mousePressEvent(event);
443 452 }
444 453
445 454 void SplineChartItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
446 455 {
447 456 emit XYChart::hovered(domain()->calculateDomainPoint(event->pos()), true);
448 457 QGraphicsItem::hoverEnterEvent(event);
449 458 }
450 459
451 460 void SplineChartItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
452 461 {
453 462 emit XYChart::hovered(domain()->calculateDomainPoint(event->pos()), false);
454 463 QGraphicsItem::hoverLeaveEvent(event);
455 464 }
456 465
457 466 #include "moc_splinechartitem_p.cpp"
458 467
459 468 QTCOMMERCIALCHART_END_NAMESPACE
General Comments 0
You need to be logged in to leave comments. Login now