1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the Qt Charts module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 or (at your option) any later version
20** approved by the KDE Free Qt Foundation. The licenses are as published by
21** the Free Software Foundation and appearing in the file LICENSE.GPL3
22** included in the packaging of this file. Please review the following
23** information to ensure the GNU General Public License requirements will
24** be met: https://www.gnu.org/licenses/gpl-3.0.html.
25**
26** $QT_END_LICENSE$
27**
28****************************************************************************/
29
30#include <private/areachartitem_p.h>
31#include <QtCharts/QAreaSeries>
32#include <private/qareaseries_p.h>
33#include <QtCharts/QLineSeries>
34#include <private/chartpresenter_p.h>
35#include <private/abstractdomain_p.h>
36#include <private/chartdataset_p.h>
37#include <QtGui/QPainter>
38#include <QtWidgets/QGraphicsSceneMouseEvent>
39#include <QtCore/QDebug>
40
41
42QT_CHARTS_BEGIN_NAMESPACE
43
44AreaChartItem::AreaChartItem(QAreaSeries *areaSeries, QGraphicsItem* item)
45 : ChartItem(areaSeries->d_func(),item),
46 m_series(areaSeries),
47 m_upper(0),
48 m_lower(0),
49 m_pointsVisible(false),
50 m_pointLabelsVisible(false),
51 m_pointLabelsFormat(areaSeries->pointLabelsFormat()),
52 m_pointLabelsFont(areaSeries->pointLabelsFont()),
53 m_pointLabelsColor(areaSeries->pointLabelsColor()),
54 m_pointLabelsClipping(true),
55 m_mousePressed(false)
56{
57 setAcceptHoverEvents(true);
58 setFlag(flag: QGraphicsItem::ItemIsSelectable, enabled: true);
59 setZValue(ChartPresenter::LineChartZValue);
60 if (m_series->upperSeries())
61 m_upper = new AreaBoundItem(this, m_series->upperSeries());
62 if (m_series->lowerSeries())
63 m_lower = new AreaBoundItem(this, m_series->lowerSeries());
64
65 QObject::connect(sender: m_series->d_func(), SIGNAL(updated()), receiver: this, SLOT(handleUpdated()));
66 QObject::connect(sender: m_series, SIGNAL(visibleChanged()), receiver: this, SLOT(handleUpdated()));
67 QObject::connect(sender: m_series, SIGNAL(opacityChanged()), receiver: this, SLOT(handleUpdated()));
68 QObject::connect(sender: this, SIGNAL(clicked(QPointF)), receiver: areaSeries, SIGNAL(clicked(QPointF)));
69 QObject::connect(sender: this, SIGNAL(hovered(QPointF,bool)), receiver: areaSeries, SIGNAL(hovered(QPointF,bool)));
70 QObject::connect(sender: this, SIGNAL(pressed(QPointF)), receiver: areaSeries, SIGNAL(pressed(QPointF)));
71 QObject::connect(sender: this, SIGNAL(released(QPointF)), receiver: areaSeries, SIGNAL(released(QPointF)));
72 QObject::connect(sender: this, SIGNAL(doubleClicked(QPointF)),
73 receiver: areaSeries, SIGNAL(doubleClicked(QPointF)));
74 QObject::connect(sender: areaSeries, SIGNAL(pointLabelsFormatChanged(QString)),
75 receiver: this, SLOT(handleUpdated()));
76 QObject::connect(sender: areaSeries, SIGNAL(pointLabelsVisibilityChanged(bool)),
77 receiver: this, SLOT(handleUpdated()));
78 QObject::connect(sender: areaSeries, SIGNAL(pointLabelsFontChanged(QFont)),
79 receiver: this, SLOT(handleUpdated()));
80 QObject::connect(sender: areaSeries, SIGNAL(pointLabelsColorChanged(QColor)),
81 receiver: this, SLOT(handleUpdated()));
82 QObject::connect(sender: areaSeries, SIGNAL(pointLabelsClippingChanged(bool)),
83 receiver: this, SLOT(handleUpdated()));
84
85 handleUpdated();
86}
87
88AreaChartItem::~AreaChartItem()
89{
90 delete m_upper;
91 delete m_lower;
92}
93
94void AreaChartItem::setPresenter(ChartPresenter *presenter)
95{
96 if (m_upper)
97 m_upper->setPresenter(presenter);
98 if (m_lower)
99 m_lower->setPresenter(presenter);
100 ChartItem::setPresenter(presenter);
101}
102
103void AreaChartItem::setUpperSeries(QLineSeries *series)
104{
105 delete m_upper;
106 if (series)
107 m_upper = new AreaBoundItem(this, series);
108 else
109 m_upper = 0;
110 if (m_upper) {
111 m_upper->setPresenter(presenter());
112 fixEdgeSeriesDomain(edgeSeries: m_upper);
113 } else {
114 updatePath();
115 }
116}
117
118void AreaChartItem::setLowerSeries(QLineSeries *series)
119{
120 delete m_lower;
121 if (series)
122 m_lower = new AreaBoundItem(this, series);
123 else
124 m_lower = 0;
125 if (m_lower) {
126 m_lower->setPresenter(presenter());
127 fixEdgeSeriesDomain(edgeSeries: m_lower);
128 } else {
129 updatePath();
130 }
131}
132
133QRectF AreaChartItem::boundingRect() const
134{
135 return m_rect;
136}
137
138QPainterPath AreaChartItem::shape() const
139{
140 return m_path;
141}
142
143void AreaChartItem::updatePath()
144{
145 QPainterPath path;
146 QRectF rect(QPointF(0,0),domain()->size());
147
148 if (m_upper) {
149 path = m_upper->path();
150
151 if (m_lower) {
152 // Note: Polarcharts draw area correctly only when both series have equal width or are
153 // fully displayed. If one series is partally off-chart, the connecting line between
154 // the series does not attach to the end of the partially hidden series but to the point
155 // where it intersects the axis line. The problem is especially noticeable when one of
156 // the series is entirely off-chart, in which case the connecting line connects two
157 // ends of the visible series.
158 // This happens because we get the paths from linechart, which omits off-chart segments.
159 // To properly fix, linechart would need to provide true full path, in right, left,
160 // and the rest portions to enable proper clipping. However, combining those to single
161 // visually unified area would be a nightmare, since they would have to be painted
162 // separately.
163 path.connectPath(path: m_lower->path().toReversed());
164 } else {
165 QPointF first = path.pointAtPercent(t: 0);
166 QPointF last = path.pointAtPercent(t: 1);
167 if (presenter()->chartType() == QChart::ChartTypeCartesian) {
168 path.lineTo(x: last.x(), y: rect.bottom());
169 path.lineTo(x: first.x(), y: rect.bottom());
170 } else { // polar
171 path.lineTo(p: rect.center());
172 }
173 }
174 path.closeSubpath();
175 }
176
177 // Only zoom in if the bounding rect of the path fits inside int limits. QWidget::update() uses
178 // a region that has to be compatible with QRect.
179 if (path.boundingRect().height() <= INT_MAX
180 && path.boundingRect().width() <= INT_MAX) {
181 prepareGeometryChange();
182 m_path = path;
183 m_rect = path.boundingRect();
184 update();
185 }
186}
187
188void AreaChartItem::handleUpdated()
189{
190 setVisible(m_series->isVisible());
191 m_pointsVisible = m_series->pointsVisible();
192 m_linePen = m_series->pen();
193 m_brush = m_series->brush();
194 m_pointPen = m_series->pen();
195 m_pointPen.setWidthF(2 * m_pointPen.width());
196 setOpacity(m_series->opacity());
197 m_pointLabelsFormat = m_series->pointLabelsFormat();
198 m_pointLabelsVisible = m_series->pointLabelsVisible();
199 m_pointLabelsFont = m_series->pointLabelsFont();
200 m_pointLabelsColor = m_series->pointLabelsColor();
201 bool labelClippingChanged = m_pointLabelsClipping != m_series->pointLabelsClipping();
202 m_pointLabelsClipping = m_series->pointLabelsClipping();
203 // Update whole chart in case label clipping changed as labels can be outside series area
204 if (labelClippingChanged)
205 m_series->chart()->update();
206 else
207 update();
208}
209
210void AreaChartItem::handleDomainUpdated()
211{
212 fixEdgeSeriesDomain(edgeSeries: m_upper);
213 fixEdgeSeriesDomain(edgeSeries: m_lower);
214}
215
216void AreaChartItem::fixEdgeSeriesDomain(LineChartItem *edgeSeries)
217{
218 if (edgeSeries) {
219 AbstractDomain* mainDomain = domain();
220 AbstractDomain* edgeDomain = edgeSeries->domain();
221
222 if (edgeDomain->type() != mainDomain->type()) {
223 // Change the domain of edge series to the same type as the area series
224 edgeDomain = dataSet()->createDomain(type: mainDomain->type());
225 edgeSeries->seriesPrivate()->setDomain(edgeDomain);
226 }
227 edgeDomain->setSize(mainDomain->size());
228 edgeDomain->setRange(minX: mainDomain->minX(), maxX: mainDomain->maxX(), minY: mainDomain->minY(), maxY: mainDomain->maxY());
229 edgeDomain->setReverseX(mainDomain->isReverseX());
230 edgeDomain->setReverseY(mainDomain->isReverseY());
231 edgeSeries->handleDomainUpdated();
232 }
233}
234
235void AreaChartItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
236{
237 Q_UNUSED(widget)
238 Q_UNUSED(option)
239
240 painter->save();
241 painter->setPen(m_linePen);
242 painter->setBrush(m_brush);
243 QRectF clipRect = QRectF(QPointF(0, 0), domain()->size());
244 if (presenter()->chartType() == QChart::ChartTypePolar)
245 painter->setClipRegion(QRegion(clipRect.toRect(), QRegion::Ellipse));
246 else
247 painter->setClipRect(clipRect);
248
249 painter->drawPath(path: m_path);
250 if (m_pointsVisible) {
251 painter->setPen(m_pointPen);
252 if (m_upper)
253 painter->drawPoints(points: m_upper->geometryPoints());
254 if (m_lower)
255 painter->drawPoints(points: m_lower->geometryPoints());
256 }
257
258 // Draw series point label
259 if (m_pointLabelsVisible) {
260 static const QString xPointTag(QLatin1String("@xPoint"));
261 static const QString yPointTag(QLatin1String("@yPoint"));
262 const int labelOffset = 2;
263
264 if (m_pointLabelsClipping)
265 painter->setClipping(true);
266 else
267 painter->setClipping(false);
268
269 QFont f(m_pointLabelsFont);
270 f.setPixelSize(QFontInfo(m_pointLabelsFont).pixelSize());
271 painter->setFont(f);
272 painter->setPen(QPen(m_pointLabelsColor));
273 QFontMetrics fm(painter->font());
274
275 QString pointLabel;
276
277 if (m_series->upperSeries()) {
278 for (int i(0); i < m_series->upperSeries()->count(); i++) {
279 pointLabel = m_pointLabelsFormat;
280 pointLabel.replace(before: xPointTag,
281 after: presenter()->numberToString(value: m_series->upperSeries()->at(index: i).x()));
282 pointLabel.replace(before: yPointTag,
283 after: presenter()->numberToString(value: m_series->upperSeries()->at(index: i).y()));
284
285 // Position text in relation to the point
286 int pointLabelWidth = fm.horizontalAdvance(pointLabel);
287 QPointF position(m_upper->geometryPoints().at(i));
288 position.setX(position.x() - pointLabelWidth / 2);
289 position.setY(position.y() - m_series->upperSeries()->pen().width() / 2
290 - labelOffset);
291 painter->drawText(p: position, s: pointLabel);
292 }
293 }
294
295 if (m_series->lowerSeries()) {
296 for (int i(0); i < m_series->lowerSeries()->count(); i++) {
297 pointLabel = m_pointLabelsFormat;
298 pointLabel.replace(before: xPointTag,
299 after: presenter()->numberToString(value: m_series->lowerSeries()->at(index: i).x()));
300 pointLabel.replace(before: yPointTag,
301 after: presenter()->numberToString(value: m_series->lowerSeries()->at(index: i).y()));
302
303 // Position text in relation to the point
304 int pointLabelWidth = fm.horizontalAdvance(pointLabel);
305 QPointF position(m_lower->geometryPoints().at(i));
306 position.setX(position.x() - pointLabelWidth / 2);
307 position.setY(position.y() - m_series->lowerSeries()->pen().width() / 2
308 - labelOffset);
309 painter->drawText(p: position, s: pointLabel);
310 }
311 }
312 }
313
314 painter->restore();
315}
316
317void AreaChartItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
318{
319 emit pressed(point: domain()->calculateDomainPoint(point: event->pos()));
320 m_lastMousePos = event->pos();
321 m_mousePressed = true;
322 ChartItem::mousePressEvent(event);
323}
324
325void AreaChartItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
326{
327 emit hovered(point: domain()->calculateDomainPoint(point: event->pos()), state: true);
328 event->accept();
329// QGraphicsItem::hoverEnterEvent(event);
330}
331
332void AreaChartItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
333{
334 emit hovered(point: domain()->calculateDomainPoint(point: event->pos()), state: false);
335 event->accept();
336// QGraphicsItem::hoverEnterEvent(event);
337}
338
339void AreaChartItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
340{
341 emit released(point: domain()->calculateDomainPoint(point: m_lastMousePos));
342 if (m_mousePressed)
343 emit clicked(point: domain()->calculateDomainPoint(point: m_lastMousePos));
344 m_mousePressed = false;
345 ChartItem::mouseReleaseEvent(event);
346}
347
348void AreaChartItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
349{
350 emit doubleClicked(point: domain()->calculateDomainPoint(point: m_lastMousePos));
351 ChartItem::mouseDoubleClickEvent(event);
352}
353
354QT_CHARTS_END_NAMESPACE
355
356#include "moc_areachartitem_p.cpp"
357

source code of qtcharts/src/charts/areachart/areachartitem.cpp