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