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

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