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

source code of qtgraphs/src/graphs3d/data/qscatterdataproxy.cpp