1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include <private/xychart_p.h>
5#include <QtCharts/QXYSeries>
6#include <private/qxyseries_p.h>
7#include <private/chartpresenter_p.h>
8#include <private/abstractdomain_p.h>
9#include <private/chartdataset_p.h>
10#include <private/glxyseriesdata_p.h>
11#include <QtCharts/QXYModelMapper>
12#include <private/qabstractaxis_p.h>
13#include <QtGui/QPainter>
14#include <QtCore/QAbstractItemModel>
15
16
17QT_BEGIN_NAMESPACE
18
19XYChart::XYChart(QXYSeries *series, QGraphicsItem *item):
20 ChartItem(series->d_func(),item),
21 m_series(series),
22 m_animation(0),
23 m_dirty(true)
24{
25 connect(sender: series->d_func(), signal: &QXYSeriesPrivate::seriesUpdated,
26 context: this, slot: &XYChart::handleSeriesUpdated);
27 connect(sender: series, signal: &QXYSeries::pointReplaced, context: this, slot: &XYChart::handlePointReplaced);
28 connect(sender: series, signal: &QXYSeries::pointsReplaced, context: this, slot: &XYChart::handlePointsReplaced);
29 connect(sender: series, signal: &QXYSeries::pointAdded, context: this, slot: &XYChart::handlePointAdded);
30 connect(sender: series, signal: &QXYSeries::pointRemoved, context: this, slot: &XYChart::handlePointRemoved);
31 connect(sender: series, signal: &QXYSeries::pointsRemoved, context: this, slot: &XYChart::handlePointsRemoved);
32 connect(sender: this, signal: &XYChart::clicked, context: series, slot: &QXYSeries::clicked);
33 connect(sender: this, signal: &XYChart::hovered, context: series, slot: &QXYSeries::hovered);
34 connect(sender: this, signal: &XYChart::pressed, context: series, slot: &QXYSeries::pressed);
35 connect(sender: this, signal: &XYChart::released, context: series, slot: &QXYSeries::released);
36 connect(sender: this, signal: &XYChart::doubleClicked, context: series, slot: &QXYSeries::doubleClicked);
37 connect(sender: series, signal: &QAbstractSeries::useOpenGLChanged, context: this, slot: &XYChart::handleDomainUpdated);
38}
39
40void XYChart::setGeometryPoints(const QList<QPointF> &points)
41{
42 m_points = points;
43}
44
45void XYChart::setAnimation(XYAnimation *animation)
46{
47 m_animation = animation;
48}
49
50void XYChart::setDirty(bool dirty)
51{
52 m_dirty = dirty;
53}
54
55// Returns a list with same size as geometryPoints list, indicating
56// the off grid status of points.
57QList<bool> XYChart::offGridStatusVector()
58{
59 qreal minX = domain()->minX();
60 qreal maxX = domain()->maxX();
61 qreal minY = domain()->minY();
62 qreal maxY = domain()->maxY();
63
64 QList<bool> returnVector;
65 returnVector.resize(size: m_points.size());
66 // During remove animation series may have different number of points,
67 // so ensure we don't go over the index. No need to check for zero points, this
68 // will not be called in such a situation.
69 const int seriesLastIndex = m_series->count() - 1;
70
71 for (int i = 0; i < m_points.size(); i++) {
72 const QPointF &seriesPoint = m_series->at(index: qMin(a: seriesLastIndex, b: i));
73 if (seriesPoint.x() < minX
74 || seriesPoint.x() > maxX
75 || seriesPoint.y() < minY
76 || seriesPoint.y() > maxY) {
77 returnVector[i] = true;
78 } else {
79 returnVector[i] = false;
80 }
81 }
82 return returnVector;
83}
84
85void XYChart::updateChart(const QList<QPointF> &oldPoints, const QList<QPointF> &newPoints,
86 int index)
87{
88
89 if (m_animation) {
90 m_animation->setup(oldPoints, newPoints, index);
91 m_points = newPoints;
92 setDirty(false);
93 presenter()->startAnimation(animation: m_animation);
94 } else {
95 m_points = newPoints;
96 updateGeometry();
97 }
98}
99
100void XYChart::updateGlChart()
101{
102 dataSet()->glXYSeriesDataManager()->setPoints(series: m_series, domain: domain());
103 presenter()->updateGLWidget();
104 updateGeometry();
105}
106
107// Doesn't update gl geometry, but refreshes the chart
108void XYChart::refreshGlChart()
109{
110 if (presenter())
111 presenter()->updateGLWidget();
112}
113
114//handlers
115
116void XYChart::handlePointAdded(int index)
117{
118 Q_ASSERT(index < m_series->count());
119 Q_ASSERT(index >= 0);
120
121 if (m_series->useOpenGL()) {
122 updateGlChart();
123 } else {
124 QList<QPointF> points;
125 if (m_dirty || m_points.isEmpty()) {
126 points = domain()->calculateGeometryPoints(list: m_series->points());
127 } else {
128 points = m_points;
129 QPointF point =
130 domain()->calculateGeometryPoint(point: m_series->points().at(i: index), ok&: m_validData);
131 if (!m_validData)
132 m_points.clear();
133 else
134 points.insert(i: index, t: point);
135 }
136 updateChart(oldPoints: m_points, newPoints: points, index);
137 }
138}
139
140void XYChart::handlePointRemoved(int index)
141{
142 Q_ASSERT(index <= m_series->count());
143 Q_ASSERT(index >= 0);
144
145 if (m_series->useOpenGL()) {
146 updateGlChart();
147 } else {
148 QList<QPointF> points;
149 if (m_dirty || m_points.isEmpty()) {
150 points = domain()->calculateGeometryPoints(list: m_series->points());
151 } else {
152 points = m_points;
153 points.remove(i: index);
154 }
155 updateChart(oldPoints: m_points, newPoints: points, index);
156 }
157}
158
159void XYChart::handlePointsRemoved(int index, int count)
160{
161 Q_ASSERT(index <= m_series->count());
162 Q_ASSERT(index >= 0);
163
164 if (m_series->useOpenGL()) {
165 updateGlChart();
166 } else {
167 QList<QPointF> points;
168 if (m_dirty || m_points.isEmpty()) {
169 points = domain()->calculateGeometryPoints(list: m_series->points());
170 } else {
171 points = m_points;
172 points.remove(i: index, n: count);
173 }
174 updateChart(oldPoints: m_points, newPoints: points, index);
175 }
176}
177
178void XYChart::handlePointReplaced(int index)
179{
180 Q_ASSERT(index < m_series->count());
181 Q_ASSERT(index >= 0);
182
183 if (m_series->useOpenGL()) {
184 updateGlChart();
185 } else {
186 QList<QPointF> points;
187 if (m_dirty || m_points.isEmpty()) {
188 points = domain()->calculateGeometryPoints(list: m_series->points());
189 } else {
190 QPointF point =
191 domain()->calculateGeometryPoint(point: m_series->points().at(i: index), ok&: m_validData);
192 if (!m_validData)
193 m_points.clear();
194 points = m_points;
195 if (m_validData)
196 points.replace(i: index, t: point);
197 }
198 updateChart(oldPoints: m_points, newPoints: points, index);
199 }
200}
201
202void XYChart::handlePointsReplaced()
203{
204 if (m_series->useOpenGL()) {
205 updateGlChart();
206 } else {
207 // All the points were replaced -> recalculate
208 QList<QPointF> points = domain()->calculateGeometryPoints(list: m_series->points());
209 updateChart(oldPoints: m_points, newPoints: points, index: -1);
210 }
211}
212
213void XYChart::handleDomainUpdated()
214{
215 if (m_series->useOpenGL()) {
216 updateGlChart();
217 } else {
218 if (isEmpty()) return;
219 QList<QPointF> points = domain()->calculateGeometryPoints(list: m_series->points());
220 updateChart(oldPoints: m_points, newPoints: points);
221 }
222}
223
224void XYChart::handleSeriesUpdated()
225{
226}
227
228bool XYChart::isEmpty()
229{
230 return domain()->isEmpty() || m_series->points().isEmpty();
231}
232
233QPointF XYChart::matchForLightMarker(const QPointF &eventPos)
234{
235 if (m_series->lightMarker().isNull()
236 && (m_series->selectedLightMarker().isNull()
237 || m_series->selectedPoints().isEmpty()))
238 return QPointF(qQNaN(), qQNaN()); // 0,0 could actually be in points()
239
240 const bool useSelectedMarker = m_series->lightMarker().isNull();
241
242 QList<QPointF> points;
243 if (useSelectedMarker) {
244 const auto selectedPoints = m_series->selectedPoints();
245 for (const int &selectedPointIndex : selectedPoints)
246 points << m_series->at(index: selectedPointIndex);
247 } else {
248 points = m_series->points();
249 }
250
251 for (const QPointF &dp : points) {
252 bool ok;
253 const QPointF gp = domain()->calculateGeometryPoint(point: dp, ok);
254 if (ok) {
255 // '+2' and '+4': There is an addRect for the (mouse-)shape
256 // in LineChartItem::updateGeometry()
257 // This has a margin of 1 to make sure a press in the icon will always be detected,
258 // but as there is a bunch of 'translations' and therefore inaccuracies,
259 // so it is necessary to increase that margin to 2
260 // (otherwise you can click next to an icon, get a click event but not match it)
261 QRectF r(gp.x() - (m_series->markerSize() / 2 + 2),
262 gp.y() - (m_series->markerSize() / 2 + 2),
263 m_series->markerSize() + 4, m_series->markerSize() + 4);
264
265 if (r.contains(p: eventPos))
266 return dp;
267 }
268 }
269 return QPointF(qQNaN(), qQNaN()); // 0,0 could actually be in points()
270}
271
272QT_END_NAMESPACE
273
274#include "moc_xychart_p.cpp"
275

source code of qtcharts/src/charts/xychart/xychart.cpp