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/scatterchartitem_p.h>
31#include <QtCharts/QScatterSeries>
32#include <private/qscatterseries_p.h>
33#include <private/chartpresenter_p.h>
34#include <private/abstractdomain_p.h>
35#include <QtCharts/QChart>
36#include <QtGui/QPainter>
37#include <QtWidgets/QGraphicsScene>
38#include <QtCore/QDebug>
39#include <QtWidgets/QGraphicsSceneMouseEvent>
40
41QT_CHARTS_BEGIN_NAMESPACE
42
43ScatterChartItem::ScatterChartItem(QScatterSeries *series, QGraphicsItem *item)
44 : XYChart(series,item),
45 m_series(series),
46 m_items(this),
47 m_visible(true),
48 m_shape(QScatterSeries::MarkerShapeRectangle),
49 m_size(15),
50 m_pointLabelsVisible(false),
51 m_pointLabelsFormat(series->pointLabelsFormat()),
52 m_pointLabelsFont(series->pointLabelsFont()),
53 m_pointLabelsColor(series->pointLabelsColor()),
54 m_pointLabelsClipping(true),
55 m_mousePressed(false)
56{
57 QObject::connect(sender: m_series->d_func(), SIGNAL(updated()), receiver: this, SLOT(handleUpdated()));
58 QObject::connect(sender: m_series, SIGNAL(visibleChanged()), receiver: this, SLOT(handleUpdated()));
59 QObject::connect(sender: m_series, SIGNAL(opacityChanged()), receiver: this, SLOT(handleUpdated()));
60 QObject::connect(sender: series, SIGNAL(pointLabelsFormatChanged(QString)),
61 receiver: this, SLOT(handleUpdated()));
62 QObject::connect(sender: series, SIGNAL(pointLabelsVisibilityChanged(bool)),
63 receiver: this, SLOT(handleUpdated()));
64 QObject::connect(sender: series, SIGNAL(pointLabelsFontChanged(QFont)), receiver: this, SLOT(handleUpdated()));
65 QObject::connect(sender: series, SIGNAL(pointLabelsColorChanged(QColor)), receiver: this, SLOT(handleUpdated()));
66 QObject::connect(sender: series, SIGNAL(pointLabelsClippingChanged(bool)), receiver: this, SLOT(handleUpdated()));
67
68 setZValue(ChartPresenter::ScatterSeriesZValue);
69 setFlags(QGraphicsItem::ItemClipsChildrenToShape);
70
71 handleUpdated();
72
73 m_items.setHandlesChildEvents(false);
74}
75
76QRectF ScatterChartItem::boundingRect() const
77{
78 return m_rect;
79}
80
81void ScatterChartItem::createPoints(int count)
82{
83 for (int i = 0; i < count; ++i) {
84
85 QGraphicsItem *item = 0;
86
87 switch (m_shape) {
88 case QScatterSeries::MarkerShapeCircle: {
89 item = new CircleMarker(0, 0, m_size, m_size, this);
90 const QRectF &rect = item->boundingRect();
91 item->setPos(ax: -rect.width() / 2, ay: -rect.height() / 2);
92 break;
93 }
94 case QScatterSeries::MarkerShapeRectangle:
95 item = new RectangleMarker(0, 0, m_size, m_size, this);
96 item->setPos(ax: -m_size / 2, ay: -m_size / 2);
97 break;
98 default:
99 qWarning() << "Unsupported marker type";
100 break;
101 }
102 m_items.addToGroup(item);
103 }
104}
105
106void ScatterChartItem::deletePoints(int count)
107{
108 QList<QGraphicsItem *> items = m_items.childItems();
109
110 for (int i = 0; i < count; ++i) {
111 QGraphicsItem *item = items.takeLast();
112 m_markerMap.remove(key: item);
113 delete(item);
114 }
115}
116
117void ScatterChartItem::markerSelected(QGraphicsItem *marker)
118{
119 emit XYChart::clicked(point: m_markerMap[marker]);
120}
121
122void ScatterChartItem::markerHovered(QGraphicsItem *marker, bool state)
123{
124 emit XYChart::hovered(point: m_markerMap[marker], state);
125}
126
127void ScatterChartItem::markerPressed(QGraphicsItem *marker)
128{
129 emit XYChart::pressed(point: m_markerMap[marker]);
130}
131
132void ScatterChartItem::markerReleased(QGraphicsItem *marker)
133{
134 emit XYChart::released(point: m_markerMap[marker]);
135}
136
137void ScatterChartItem::markerDoubleClicked(QGraphicsItem *marker)
138{
139 emit XYChart::doubleClicked(point: m_markerMap[marker]);
140}
141
142void ScatterChartItem::updateGeometry()
143{
144 if (m_series->useOpenGL()) {
145 if (m_items.childItems().count())
146 deletePoints(count: m_items.childItems().count());
147 if (!m_rect.isEmpty()) {
148 prepareGeometryChange();
149 // Changed signal seems to trigger even with empty region
150 m_rect = QRectF();
151 }
152 update();
153 return;
154 }
155
156 const QVector<QPointF>& points = geometryPoints();
157
158 if (points.size() == 0) {
159 deletePoints(count: m_items.childItems().count());
160 return;
161 }
162
163 int diff = m_items.childItems().size() - points.size();
164
165 if (diff > 0)
166 deletePoints(count: diff);
167 else if (diff < 0)
168 createPoints(count: -diff);
169
170 if (diff != 0)
171 handleUpdated();
172
173 QList<QGraphicsItem *> items = m_items.childItems();
174
175 QRectF clipRect(QPointF(0,0),domain()->size());
176
177 // Only zoom in if the clipRect fits inside int limits. QWidget::update() uses
178 // a region that has to be compatible with QRect.
179 if (clipRect.height() <= INT_MAX
180 && clipRect.width() <= INT_MAX) {
181 QVector<bool> offGridStatus = offGridStatusVector();
182 const int seriesLastIndex = m_series->count() - 1;
183
184 for (int i = 0; i < points.size(); i++) {
185 QGraphicsItem *item = items.at(i);
186 const QPointF &point = points.at(i);
187 const QRectF &rect = item->boundingRect();
188 // During remove animation series may have different number of points,
189 // so ensure we don't go over the index. Animation handling itself ensures that
190 // if there is actually no points in the series, then it won't generate a fake point,
191 // so we can be assured there is always at least one point in m_series here.
192 // Note that marker map values can be technically incorrect during the animation,
193 // if it was caused by an insert, but this shouldn't be a problem as the points are
194 // fake anyway. After remove animation stops, geometry is updated to correct one.
195 m_markerMap[item] = m_series->at(index: qMin(a: seriesLastIndex, b: i));
196 QPointF position;
197 position.setX(point.x() - rect.width() / 2);
198 position.setY(point.y() - rect.height() / 2);
199 item->setPos(position);
200
201 if (!m_visible || offGridStatus.at(i))
202 item->setVisible(false);
203 else
204 item->setVisible(true);
205 }
206
207 prepareGeometryChange();
208 m_rect = clipRect;
209 }
210}
211
212void ScatterChartItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
213{
214 Q_UNUSED(option)
215 Q_UNUSED(widget)
216
217 if (m_series->useOpenGL())
218 return;
219
220 QRectF clipRect = QRectF(QPointF(0, 0), domain()->size());
221
222 painter->save();
223 painter->setClipRect(clipRect);
224
225 if (m_pointLabelsVisible) {
226 if (m_pointLabelsClipping)
227 painter->setClipping(true);
228 else
229 painter->setClipping(false);
230 m_series->d_func()->drawSeriesPointLabels(painter, points: m_points,
231 offset: m_series->markerSize() / 2
232 + m_series->pen().width());
233 }
234
235 painter->restore();
236}
237
238void ScatterChartItem::setPen(const QPen &pen)
239{
240 foreach (QGraphicsItem *item , m_items.childItems())
241 static_cast<QAbstractGraphicsShapeItem*>(item)->setPen(pen);
242}
243
244void ScatterChartItem::setBrush(const QBrush &brush)
245{
246 foreach (QGraphicsItem *item , m_items.childItems())
247 static_cast<QAbstractGraphicsShapeItem*>(item)->setBrush(brush);
248}
249
250void ScatterChartItem::handleUpdated()
251{
252 if (m_series->useOpenGL()) {
253 if ((m_series->isVisible() != m_visible)) {
254 m_visible = m_series->isVisible();
255 refreshGlChart();
256 }
257 return;
258 }
259
260 int count = m_items.childItems().count();
261 if (count == 0)
262 return;
263
264 bool recreate = m_visible != m_series->isVisible()
265 || m_size != m_series->markerSize()
266 || m_shape != m_series->markerShape();
267 m_visible = m_series->isVisible();
268 m_size = m_series->markerSize();
269 m_shape = m_series->markerShape();
270 setVisible(m_visible);
271 setOpacity(m_series->opacity());
272 m_pointLabelsFormat = m_series->pointLabelsFormat();
273 m_pointLabelsVisible = m_series->pointLabelsVisible();
274 m_pointLabelsFont = m_series->pointLabelsFont();
275 m_pointLabelsColor = m_series->pointLabelsColor();
276 bool labelClippingChanged = m_pointLabelsClipping != m_series->pointLabelsClipping();
277 m_pointLabelsClipping = m_series->pointLabelsClipping();
278
279 if (recreate) {
280 deletePoints(count);
281 createPoints(count);
282
283 // Updating geometry is now safe, because it won't call handleUpdated unless it creates/deletes points
284 updateGeometry();
285 }
286
287 setPen(m_series->pen());
288 setBrush(m_series->brush());
289 // Update whole chart in case label clipping changed as labels can be outside series area
290 if (labelClippingChanged)
291 m_series->chart()->update();
292 else
293 update();
294}
295
296QT_CHARTS_END_NAMESPACE
297
298#include "moc_scatterchartitem_p.cpp"
299

source code of qtcharts/src/charts/scatterchart/scatterchartitem.cpp