1 | // Copyright (C) 2023 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
3 | |
4 | #include "qscatterdataproxy_p.h" |
5 | #include "qscatter3dseries_p.h" |
6 | #include "qabstract3daxis_p.h" |
7 | |
8 | QT_BEGIN_NAMESPACE |
9 | |
10 | /*! |
11 | * \class QScatterDataProxy |
12 | * \inmodule QtGraphs |
13 | * \brief The QScatterDataProxy class is the data proxy for 3D scatter graphs. |
14 | * |
15 | * A scatter data proxy handles adding, inserting, changing, and removing data |
16 | * items. |
17 | * |
18 | * QScatterDataProxy takes ownership of all |
19 | * QtGraphs::QScatterDataArray and QScatterDataItem objects passed to |
20 | * it. |
21 | * |
22 | * \sa {Qt Graphs Data Handling} |
23 | */ |
24 | |
25 | /*! |
26 | * \typealias QScatterDataArray |
27 | * \relates QScatterDataProxy |
28 | * |
29 | * A list of \l {QScatterDataItem} objects. |
30 | */ |
31 | |
32 | /*! |
33 | * \qmltype ScatterDataProxy |
34 | * \inqmlmodule QtGraphs |
35 | * \ingroup graphs_qml |
36 | * \instantiates QScatterDataProxy |
37 | * \inherits AbstractDataProxy |
38 | * \brief The data proxy for 3D scatter graphs. |
39 | * |
40 | * This type handles adding, inserting, changing, and removing data items. |
41 | * |
42 | * This type is uncreatable, but contains properties that are exposed via subtypes. |
43 | * |
44 | * \sa ItemModelScatterDataProxy, {Qt Graphs Data Handling} |
45 | */ |
46 | |
47 | /*! |
48 | * \qmlproperty int ScatterDataProxy::itemCount |
49 | * The number of items in the array. |
50 | */ |
51 | |
52 | /*! |
53 | * \qmlproperty Scatter3DSeries ScatterDataProxy::series |
54 | * |
55 | * The series this proxy is attached to. |
56 | */ |
57 | |
58 | /*! |
59 | * Constructs QScatterDataProxy with the given \a parent. |
60 | */ |
61 | QScatterDataProxy::QScatterDataProxy(QObject *parent) : |
62 | QAbstractDataProxy(new QScatterDataProxyPrivate(this), parent) |
63 | { |
64 | } |
65 | |
66 | /*! |
67 | * \internal |
68 | */ |
69 | QScatterDataProxy::QScatterDataProxy(QScatterDataProxyPrivate *d, QObject *parent) : |
70 | QAbstractDataProxy(d, parent) |
71 | { |
72 | } |
73 | |
74 | /*! |
75 | * Deletes the scatter data proxy. |
76 | */ |
77 | QScatterDataProxy::~QScatterDataProxy() |
78 | { |
79 | } |
80 | |
81 | /*! |
82 | * \property QScatterDataProxy::series |
83 | * |
84 | * \brief The series this proxy is attached to. |
85 | */ |
86 | QScatter3DSeries *QScatterDataProxy::series() const |
87 | { |
88 | const Q_D(QScatterDataProxy); |
89 | return static_cast<QScatter3DSeries *>(d->series()); |
90 | } |
91 | |
92 | /*! |
93 | * Takes ownership of the array \a newArray. Clears the existing array if the |
94 | * new array differs from it. If the arrays are the same, this function |
95 | * just triggers the arrayReset() signal. |
96 | * |
97 | * Passing a null array deletes the old array and creates a new empty array. |
98 | */ |
99 | void QScatterDataProxy::resetArray(QScatterDataArray *newArray) |
100 | { |
101 | Q_D(QScatterDataProxy); |
102 | if (d->m_dataArray != newArray) |
103 | d->resetArray(newArray); |
104 | |
105 | emit arrayReset(); |
106 | emit itemCountChanged(count: itemCount()); |
107 | } |
108 | |
109 | /*! |
110 | * Replaces the item at the position \a index with the item \a item. |
111 | */ |
112 | void QScatterDataProxy::setItem(int index, const QScatterDataItem &item) |
113 | { |
114 | Q_D(QScatterDataProxy); |
115 | d->setItem(index, item); |
116 | emit itemsChanged(startIndex: index, count: 1); |
117 | } |
118 | |
119 | /*! |
120 | * Replaces the items starting from the position \a index with the items |
121 | * specified by \a items. |
122 | */ |
123 | void QScatterDataProxy::setItems(int index, const QScatterDataArray &items) |
124 | { |
125 | Q_D(QScatterDataProxy); |
126 | d->setItems(index, items); |
127 | emit itemsChanged(startIndex: index, count: items.size()); |
128 | } |
129 | |
130 | /*! |
131 | * Adds the item \a item to the end of the array. |
132 | * |
133 | * Returns the index of the added item. |
134 | */ |
135 | int QScatterDataProxy::addItem(const QScatterDataItem &item) |
136 | { |
137 | Q_D(QScatterDataProxy); |
138 | int addIndex = d->addItem(item); |
139 | emit itemsAdded(startIndex: addIndex, count: 1); |
140 | emit itemCountChanged(count: itemCount()); |
141 | return addIndex; |
142 | } |
143 | |
144 | /*! |
145 | * Adds the items specified by \a items to the end of the array. |
146 | * |
147 | * Returns the index of the first added item. |
148 | */ |
149 | int QScatterDataProxy::addItems(const QScatterDataArray &items) |
150 | { |
151 | Q_D(QScatterDataProxy); |
152 | int addIndex = d->addItems(items); |
153 | emit itemsAdded(startIndex: addIndex, count: items.size()); |
154 | emit itemCountChanged(count: itemCount()); |
155 | return addIndex; |
156 | } |
157 | |
158 | /*! |
159 | * Inserts the item \a item to the position \a index. If the index is equal to |
160 | * the data array size, the item is added to the array. |
161 | */ |
162 | void QScatterDataProxy::insertItem(int index, const QScatterDataItem &item) |
163 | { |
164 | Q_D(QScatterDataProxy); |
165 | d->insertItem(index, item); |
166 | emit itemsInserted(startIndex: index, count: 1); |
167 | emit itemCountChanged(count: itemCount()); |
168 | } |
169 | |
170 | /*! |
171 | * Inserts the items specified by \a items to the position \a index. If the |
172 | * index is equal to data array size, the items are added to the array. |
173 | */ |
174 | void QScatterDataProxy::insertItems(int index, const QScatterDataArray &items) |
175 | { |
176 | Q_D(QScatterDataProxy); |
177 | d->insertItems(index, items); |
178 | emit itemsInserted(startIndex: index, count: items.size()); |
179 | emit itemCountChanged(count: itemCount()); |
180 | } |
181 | |
182 | /*! |
183 | * Removes the number of items specified by \a removeCount starting at the |
184 | * position \a index. Attempting to remove items past the end of |
185 | * the array does nothing. |
186 | */ |
187 | void QScatterDataProxy::removeItems(int index, int removeCount) |
188 | { |
189 | Q_D(QScatterDataProxy); |
190 | if (index >= d->m_dataArray->size()) |
191 | return; |
192 | |
193 | d->removeItems(index, removeCount); |
194 | emit itemsRemoved(startIndex: index, count: removeCount); |
195 | emit itemCountChanged(count: itemCount()); |
196 | } |
197 | |
198 | /*! |
199 | * \property QScatterDataProxy::itemCount |
200 | * |
201 | * \brief The number of items in the array. |
202 | */ |
203 | int QScatterDataProxy::itemCount() const |
204 | { |
205 | const Q_D(QScatterDataProxy); |
206 | return d->m_dataArray->size(); |
207 | } |
208 | |
209 | /*! |
210 | * Returns the pointer to the data array. |
211 | */ |
212 | const QScatterDataArray *QScatterDataProxy::array() const |
213 | { |
214 | const Q_D(QScatterDataProxy); |
215 | return d->m_dataArray; |
216 | } |
217 | |
218 | /*! |
219 | * Returns the pointer to the item at the index \a index. It is guaranteed to be |
220 | * valid only until the next call that modifies data. |
221 | */ |
222 | const QScatterDataItem *QScatterDataProxy::itemAt(int index) const |
223 | { |
224 | const Q_D(QScatterDataProxy); |
225 | return &d->m_dataArray->at(i: index); |
226 | } |
227 | |
228 | /*! |
229 | * \fn void QScatterDataProxy::arrayReset() |
230 | * |
231 | * This signal is emitted when the data array is reset. |
232 | * If the contents of the whole array are changed without calling resetArray(), |
233 | * this signal needs to be emitted to update the graph. |
234 | */ |
235 | |
236 | /*! |
237 | * \fn void QScatterDataProxy::itemsAdded(int startIndex, int count) |
238 | * |
239 | * This signal is emitted when the number of items specified by \a count is |
240 | * added starting at the position \a startIndex. |
241 | * If items are added to the array without calling addItem() or addItems(), |
242 | * this signal needs to be emitted to update the graph. |
243 | */ |
244 | |
245 | /*! |
246 | * \fn void QScatterDataProxy::itemsChanged(int startIndex, int count) |
247 | * |
248 | * This signal is emitted when the number of items specified by \a count is |
249 | * changed starting at the position \a startIndex. |
250 | * If items are changed in the array without calling setItem() or setItems(), |
251 | * this signal needs to be emitted to update the graph. |
252 | */ |
253 | |
254 | /*! |
255 | * \fn void QScatterDataProxy::itemsRemoved(int startIndex, int count) |
256 | * |
257 | * This signal is emitted when the number of rows specified by \a count is |
258 | * removed starting at the position \a startIndex. |
259 | * The index may be larger than the current array size if items are removed from |
260 | * the end. If items are removed from the array without calling removeItems(), |
261 | * this signal needs to be emitted to update the graph. |
262 | */ |
263 | |
264 | /*! |
265 | * \fn void QScatterDataProxy::itemsInserted(int startIndex, int count) |
266 | * |
267 | * This signal is emitted when the number of items specified by \a count is |
268 | * inserted starting at the position \a startIndex. |
269 | * If items are inserted into the array without calling insertItem() or |
270 | * insertItems(), this signal needs to be emitted to update the graph. |
271 | */ |
272 | |
273 | // QScatterDataProxyPrivate |
274 | |
275 | QScatterDataProxyPrivate::QScatterDataProxyPrivate(QScatterDataProxy *q) |
276 | : QAbstractDataProxyPrivate(q, QAbstractDataProxy::DataTypeScatter), |
277 | m_dataArray(new QScatterDataArray) |
278 | { |
279 | } |
280 | |
281 | QScatterDataProxyPrivate::~QScatterDataProxyPrivate() |
282 | { |
283 | m_dataArray->clear(); |
284 | delete m_dataArray; |
285 | } |
286 | |
287 | void QScatterDataProxyPrivate::resetArray(QScatterDataArray *newArray) |
288 | { |
289 | if (!newArray) |
290 | newArray = new QScatterDataArray; |
291 | |
292 | if (newArray != m_dataArray) { |
293 | m_dataArray->clear(); |
294 | delete m_dataArray; |
295 | m_dataArray = newArray; |
296 | } |
297 | } |
298 | |
299 | void QScatterDataProxyPrivate::setItem(int index, const QScatterDataItem &item) |
300 | { |
301 | Q_ASSERT(index >= 0 && index < m_dataArray->size()); |
302 | (*m_dataArray)[index] = item; |
303 | } |
304 | |
305 | void QScatterDataProxyPrivate::setItems(int index, const QScatterDataArray &items) |
306 | { |
307 | Q_ASSERT(index >= 0 && (index + items.size()) <= m_dataArray->size()); |
308 | for (int i = 0; i < items.size(); i++) |
309 | (*m_dataArray)[index++] = items[i]; |
310 | } |
311 | |
312 | int QScatterDataProxyPrivate::addItem(const QScatterDataItem &item) |
313 | { |
314 | int currentSize = m_dataArray->size(); |
315 | m_dataArray->append(t: item); |
316 | return currentSize; |
317 | } |
318 | |
319 | int QScatterDataProxyPrivate::addItems(const QScatterDataArray &items) |
320 | { |
321 | int currentSize = m_dataArray->size(); |
322 | (*m_dataArray) += items; |
323 | return currentSize; |
324 | } |
325 | |
326 | void QScatterDataProxyPrivate::insertItem(int index, const QScatterDataItem &item) |
327 | { |
328 | Q_ASSERT(index >= 0 && index <= m_dataArray->size()); |
329 | m_dataArray->insert(i: index, t: item); |
330 | } |
331 | |
332 | void QScatterDataProxyPrivate::insertItems(int index, const QScatterDataArray &items) |
333 | { |
334 | Q_ASSERT(index >= 0 && index <= m_dataArray->size()); |
335 | for (int i = 0; i < items.size(); i++) |
336 | m_dataArray->insert(i: index++, t: items.at(i)); |
337 | } |
338 | |
339 | void QScatterDataProxyPrivate::removeItems(int index, int removeCount) |
340 | { |
341 | Q_ASSERT(index >= 0); |
342 | int maxRemoveCount = m_dataArray->size() - index; |
343 | removeCount = qMin(a: removeCount, b: maxRemoveCount); |
344 | m_dataArray->remove(i: index, n: removeCount); |
345 | } |
346 | |
347 | void QScatterDataProxyPrivate::limitValues(QVector3D &minValues, QVector3D &maxValues, |
348 | QAbstract3DAxis *axisX, QAbstract3DAxis *axisY, |
349 | QAbstract3DAxis *axisZ) const |
350 | { |
351 | if (m_dataArray->isEmpty()) |
352 | return; |
353 | |
354 | const QVector3D &firstPos = m_dataArray->at(i: 0).position(); |
355 | |
356 | float minX = firstPos.x(); |
357 | float maxX = minX; |
358 | float minY = firstPos.y(); |
359 | float maxY = minY; |
360 | float minZ = firstPos.z(); |
361 | float maxZ = minZ; |
362 | |
363 | if (m_dataArray->size() > 1) { |
364 | for (int i = 1; i < m_dataArray->size(); i++) { |
365 | const QVector3D &pos = m_dataArray->at(i).position(); |
366 | |
367 | float value = pos.x(); |
368 | if (qIsNaN(f: value) || qIsInf(f: value)) |
369 | continue; |
370 | if (isValidValue(axisValue: minX, value, axis: axisX)) |
371 | minX = value; |
372 | if (maxX < value) |
373 | maxX = value; |
374 | |
375 | value = pos.y(); |
376 | if (qIsNaN(f: value) || qIsInf(f: value)) |
377 | continue; |
378 | if (isValidValue(axisValue: minY, value, axis: axisY)) |
379 | minY = value; |
380 | if (maxY < value) |
381 | maxY = value; |
382 | |
383 | value = pos.z(); |
384 | if (qIsNaN(f: value) || qIsInf(f: value)) |
385 | continue; |
386 | if (isValidValue(axisValue: minZ, value, axis: axisZ)) |
387 | minZ = value; |
388 | if (maxZ < value) |
389 | maxZ = value; |
390 | } |
391 | } |
392 | |
393 | minValues.setX(minX); |
394 | minValues.setY(minY); |
395 | minValues.setZ(minZ); |
396 | |
397 | maxValues.setX(maxX); |
398 | maxValues.setY(maxY); |
399 | maxValues.setZ(maxZ); |
400 | } |
401 | |
402 | bool QScatterDataProxyPrivate::isValidValue(float axisValue, float value, |
403 | QAbstract3DAxis *axis) const |
404 | { |
405 | return (axisValue > value && (value > 0.0f |
406 | || (value == 0.0f && axis->d_func()->allowZero()) |
407 | || (value < 0.0f && axis->d_func()->allowNegatives()))); |
408 | } |
409 | |
410 | void QScatterDataProxyPrivate::setSeries(QAbstract3DSeries *series) |
411 | { |
412 | Q_Q(QScatterDataProxy); |
413 | QAbstractDataProxyPrivate::setSeries(series); |
414 | QScatter3DSeries *scatterSeries = static_cast<QScatter3DSeries *>(series); |
415 | emit q->seriesChanged(series: scatterSeries); |
416 | } |
417 | |
418 | QT_END_NAMESPACE |
419 | |