1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qsurfacedataproxy_p.h"
5#include "qsurface3dseries_p.h"
6#include "qabstract3daxis_p.h"
7
8QT_BEGIN_NAMESPACE
9
10/*!
11 * \class QSurfaceDataProxy
12 * \inmodule QtGraphs
13 * \brief The QSurfaceDataProxy class is the data proxy for a 3D surface graph.
14 *
15 * A surface data proxy handles surface related data in rows. For this it
16 * provides two auxiliary type aliases: QtGraphs::QSurfaceDataArray and
17 * QtGraphs::QSurfaceDataRow. \c QSurfaceDataArray is a QList that
18 * controls the rows. \c QSurfaceDataRow is a QList that contains
19 * QSurfaceDataItem objects. For more information about how to feed the data to
20 * the proxy, see the sample code in the Q3DSurface documentation.
21 *
22 * All rows must have the same number of items.
23 *
24 * QSurfaceDataProxy takes ownership of all \c QSurfaceDataRow objects passed to
25 * it, whether directly or in a \c QSurfaceDataArray container.
26 * To use surface data row pointers to directly modify data after adding the
27 * array to the proxy, the appropriate signal must be emitted to update the
28 * graph.
29 *
30 * To make a sensible surface, the x-value of each successive item in all rows must be
31 * either ascending or descending throughout the row.
32 * Similarly, the z-value of each successive item in all columns must be either ascending or
33 * descending throughout the column.
34 *
35 * \note Currently only surfaces with straight rows and columns are fully supported. Any row
36 * with items that do not have the exact same z-value or any column with items
37 * that do not have the exact same x-value may get clipped incorrectly if the
38 * whole surface does not completely fit within the visible x-axis or z-axis
39 * ranges.
40 *
41 * \note Surfaces with less than two rows or columns are not considered valid surfaces and will
42 * not be rendered.
43 *
44 * \note On some environments, surfaces with a lot of visible vertices may not render, because
45 * they exceed the per-draw vertex count supported by the graphics driver.
46 * This is mostly an issue on 32-bit and OpenGL ES2 platforms.
47 *
48 * \sa {Qt Graphs Data Handling}
49 */
50
51/*!
52 * \typealias QSurfaceDataRow
53 * \relates QSurfaceDataProxy
54 *
55 * A list of \l {QSurfaceDataItem} objects.
56 */
57
58/*!
59 * \typealias QSurfaceDataArray
60 * \relates QSurfaceDataProxy
61 *
62 * A list of pointers to \l {QSurfaceDataRow} objects.
63 */
64
65/*!
66 * \qmltype SurfaceDataProxy
67 * \inqmlmodule QtGraphs
68 * \ingroup graphs_qml
69 * \instantiates QSurfaceDataProxy
70 * \inherits AbstractDataProxy
71 * \brief The data proxy for a 3D surface graph.
72 *
73 * This type handles surface data items. The data is arranged into rows and columns, and all rows must have
74 * the same number of columns.
75 *
76 * This type is uncreatable, but contains properties that are exposed via subtypes.
77 *
78 * For a more complete description, see QSurfaceDataProxy.
79 *
80 * \sa ItemModelSurfaceDataProxy, {Qt Graphs Data Handling}
81 */
82
83/*!
84 * \qmlproperty int SurfaceDataProxy::rowCount
85 * The number of rows in the data array.
86 */
87
88/*!
89 * \qmlproperty int SurfaceDataProxy::columnCount
90 * The number of columns in the data array.
91 */
92
93/*!
94 * \qmlproperty Surface3DSeries SurfaceDataProxy::series
95 *
96 * The series this proxy is attached to.
97 */
98
99/*!
100 * Constructs QSurfaceDataProxy with the given \a parent.
101 */
102QSurfaceDataProxy::QSurfaceDataProxy(QObject *parent) :
103 QAbstractDataProxy(new QSurfaceDataProxyPrivate(this), parent)
104{
105}
106
107/*!
108 * \internal
109 */
110QSurfaceDataProxy::QSurfaceDataProxy(QSurfaceDataProxyPrivate *d, QObject *parent) :
111 QAbstractDataProxy(d, parent)
112{
113}
114
115/*!
116 * Deletes the surface data proxy.
117 */
118QSurfaceDataProxy::~QSurfaceDataProxy()
119{
120}
121
122/*!
123 * \property QSurfaceDataProxy::series
124 *
125 * \brief The series this proxy is attached to.
126 */
127QSurface3DSeries *QSurfaceDataProxy::series() const
128{
129 const Q_D(QSurfaceDataProxy);
130 return static_cast<QSurface3DSeries *>(d->series());
131}
132
133/*!
134 * Takes ownership of the array \a newArray. Clears the existing array if the
135 * new array differs from it. If the arrays are the same, this function
136 * just triggers the arrayReset() signal.
137 *
138 * Passing a null array deletes the old array and creates a new empty array.
139 * All rows in \a newArray must be of same length.
140 */
141void QSurfaceDataProxy::resetArray(QSurfaceDataArray *newArray)
142{
143 Q_D(QSurfaceDataProxy);
144 if (d->m_dataArray != newArray) {
145 d->resetArray(newArray);
146 }
147 emit arrayReset();
148 emit rowCountChanged(count: rowCount());
149 emit columnCountChanged(count: columnCount());
150}
151
152/*!
153 * Changes an existing row by replacing the row at the position \a rowIndex
154 * with the new row specified by \a row. The new row can be the same as the
155 * existing row already stored at the \a rowIndex. The new row must have
156 * the same number of columns as the row it is replacing.
157 */
158void QSurfaceDataProxy::setRow(int rowIndex, QSurfaceDataRow *row)
159{
160 Q_D(QSurfaceDataProxy);
161 d->setRow(rowIndex, row);
162 emit rowsChanged(startIndex: rowIndex, count: 1);
163}
164
165/*!
166 * Changes existing rows by replacing the rows starting at the position
167 * \a rowIndex with the new rows specifies by \a rows.
168 * The rows in the \a rows array can be the same as the existing rows already
169 * stored at the \a rowIndex. The new rows must have the same number of columns
170 * as the rows they are replacing.
171 */
172void QSurfaceDataProxy::setRows(int rowIndex, const QSurfaceDataArray &rows)
173{
174 Q_D(QSurfaceDataProxy);
175 d->setRows(rowIndex, rows);
176 emit rowsChanged(startIndex: rowIndex, count: rows.size());
177}
178
179/*!
180 * Changes a single item at the position specified by \a rowIndex and
181 * \a columnIndex to the item \a item.
182 */
183void QSurfaceDataProxy::setItem(int rowIndex, int columnIndex, const QSurfaceDataItem &item)
184{
185 Q_D(QSurfaceDataProxy);
186 d->setItem(rowIndex, columnIndex, item);
187 emit itemChanged(rowIndex, columnIndex);
188}
189
190/*!
191 * Changes a single item at the position \a position to the item \a item.
192 * The x-value of \a position indicates the row and the y-value indicates the
193 * column.
194 */
195void QSurfaceDataProxy::setItem(const QPoint &position, const QSurfaceDataItem &item)
196{
197 setItem(rowIndex: position.x(), columnIndex: position.y(), item);
198}
199
200/*!
201 * Adds the new row \a row to the end of an array. The new row must have
202 * the same number of columns as the rows in the initial array.
203 *
204 * Returns the index of the added row.
205 */
206int QSurfaceDataProxy::addRow(QSurfaceDataRow *row)
207{
208 Q_D(QSurfaceDataProxy);
209 int addIndex = d->addRow(row);
210 emit rowsAdded(startIndex: addIndex, count: 1);
211 emit rowCountChanged(count: rowCount());
212 return addIndex;
213}
214
215/*!
216 * Adds new \a rows to the end of an array. The new rows must have the same
217 * number of columns as the rows in the initial array.
218 *
219 * Returns the index of the first added row.
220 */
221int QSurfaceDataProxy::addRows(const QSurfaceDataArray &rows)
222{
223 Q_D(QSurfaceDataProxy);
224 int addIndex = d->addRows(rows);
225 emit rowsAdded(startIndex: addIndex, count: rows.size());
226 emit rowCountChanged(count: rowCount());
227 return addIndex;
228}
229
230/*!
231 * Inserts the new row \a row into \a rowIndex.
232 * If \a rowIndex is equal to the array size, the rows are added to the end of
233 * the array. The new row must have the same number of columns as the rows in
234 * the initial array.
235 */
236void QSurfaceDataProxy::insertRow(int rowIndex, QSurfaceDataRow *row)
237{
238 Q_D(QSurfaceDataProxy);
239 d->insertRow(rowIndex, row);
240 emit rowsInserted(startIndex: rowIndex, count: 1);
241 emit rowCountChanged(count: rowCount());
242}
243
244/*!
245 * Inserts new \a rows into \a rowIndex.
246 * If \a rowIndex is equal to the array size, the rows are added to the end of
247 * the array. The new \a rows must have the same number of columns as the rows
248 * in the initial array.
249 */
250void QSurfaceDataProxy::insertRows(int rowIndex, const QSurfaceDataArray &rows)
251{
252 Q_D(QSurfaceDataProxy);
253 d->insertRows(rowIndex, rows);
254 emit rowsInserted(startIndex: rowIndex, count: rows.size());
255 emit rowCountChanged(count: rowCount());
256}
257
258/*!
259 * Removes the number of rows specified by \a removeCount starting at the
260 * position \a rowIndex. Attempting to remove rows past the end of the
261 * array does nothing.
262 */
263void QSurfaceDataProxy::removeRows(int rowIndex, int removeCount)
264{
265 Q_D(QSurfaceDataProxy);
266 if (rowIndex < rowCount() && removeCount >= 1) {
267 d->removeRows(rowIndex, removeCount);
268 emit rowsRemoved(startIndex: rowIndex, count: removeCount);
269 emit rowCountChanged(count: rowCount());
270 }
271}
272
273/*!
274 * Returns the pointer to the data array.
275 */
276const QSurfaceDataArray *QSurfaceDataProxy::array() const
277{
278 const Q_D(QSurfaceDataProxy);
279 return d->m_dataArray;
280}
281
282/*!
283 * Returns the pointer to the item at the position specified by \a rowIndex and
284 * \a columnIndex. It is guaranteed to be valid only
285 * until the next call that modifies data.
286 */
287const QSurfaceDataItem *QSurfaceDataProxy::itemAt(int rowIndex, int columnIndex) const
288{
289 const Q_D(QSurfaceDataProxy);
290 const QSurfaceDataArray &dataArray = *d->m_dataArray;
291 Q_ASSERT(rowIndex >= 0 && rowIndex < dataArray.size());
292 const QSurfaceDataRow &dataRow = *dataArray[rowIndex];
293 Q_ASSERT(columnIndex >= 0 && columnIndex < dataRow.size());
294 return &dataRow.at(i: columnIndex);
295}
296
297/*!
298 * Returns the pointer to the item at the position \a position. The x-value of
299 * \a position indicates the row and the y-value indicates the column. The item
300 * is guaranteed to be valid only until the next call that modifies data.
301 */
302const QSurfaceDataItem *QSurfaceDataProxy::itemAt(const QPoint &position) const
303{
304 return itemAt(rowIndex: position.x(), columnIndex: position.y());
305}
306
307/*!
308 * \property QSurfaceDataProxy::rowCount
309 *
310 * \brief The number of rows in the data array.
311 */
312int QSurfaceDataProxy::rowCount() const
313{
314 const Q_D(QSurfaceDataProxy);
315 return d->m_dataArray->size();
316}
317
318/*!
319 * \property QSurfaceDataProxy::columnCount
320 *
321 * \brief The number of columns in the data array.
322 */
323int QSurfaceDataProxy::columnCount() const
324{
325 const Q_D(QSurfaceDataProxy);
326 if (d->m_dataArray->size() > 0)
327 return d->m_dataArray->at(i: 0)->size();
328 else
329 return 0;
330}
331
332/*!
333 * \fn void QSurfaceDataProxy::arrayReset()
334 *
335 * This signal is emitted when the data array is reset.
336 * If the contents of the whole array are changed without calling resetArray(),
337 * this signal needs to be emitted to update the graph.
338 */
339
340/*!
341 * \fn void QSurfaceDataProxy::rowsAdded(int startIndex, int count)
342 *
343 * This signal is emitted when the number of rows specified by \a count is
344 * added starting at the position \a startIndex.
345 * If rows are added to the array without calling addRow() or addRows(),
346 * this signal needs to be emitted to update the graph.
347 */
348
349/*!
350 * \fn void QSurfaceDataProxy::rowsChanged(int startIndex, int count)
351 *
352 * This signal is emitted when the number of rows specified by \a count is
353 * changed starting at the position \a startIndex.
354 * If rows are changed in the array without calling setRow() or setRows(),
355 * this signal needs to be emitted to update the graph.
356 */
357
358/*!
359 * \fn void QSurfaceDataProxy::rowsRemoved(int startIndex, int count)
360 *
361 * This signal is emitted when the number of rows specified by \a count is
362 * removed starting at the position \a startIndex.
363 *
364 * The index is the current array size if the rows were removed from the end of
365 * the array. If rows are removed from the array without calling removeRows(),
366 * this signal needs to be emitted to update the graph.
367 */
368
369/*!
370 * \fn void QSurfaceDataProxy::rowsInserted(int startIndex, int count)
371 *
372 * This signal is emitted when the number of rows specified by \a count is
373 * inserted at the position \a startIndex.
374 *
375 * If rows are inserted into the array without calling insertRow() or
376 * insertRows(), this signal needs to be emitted to update the graph.
377 */
378
379/*!
380 * \fn void QSurfaceDataProxy::itemChanged(int rowIndex, int columnIndex)
381 *
382 * This signal is emitted when the item at the position specified by \a rowIndex
383 * and \a columnIndex changes.
384 * If the item is changed in the array without calling setItem(),
385 * this signal needs to be emitted to update the graph.
386 */
387
388// QSurfaceDataProxyPrivate
389
390QSurfaceDataProxyPrivate::QSurfaceDataProxyPrivate(QSurfaceDataProxy *q)
391 : QAbstractDataProxyPrivate(q, QAbstractDataProxy::DataTypeSurface),
392 m_dataArray(new QSurfaceDataArray)
393{
394}
395
396QSurfaceDataProxyPrivate::~QSurfaceDataProxyPrivate()
397{
398 clearArray();
399}
400
401void QSurfaceDataProxyPrivate::resetArray(QSurfaceDataArray *newArray)
402{
403 if (!newArray)
404 newArray = new QSurfaceDataArray;
405
406 if (newArray != m_dataArray) {
407 clearArray();
408 m_dataArray = newArray;
409 }
410}
411
412void QSurfaceDataProxyPrivate::setRow(int rowIndex, QSurfaceDataRow *row)
413{
414 Q_ASSERT(rowIndex >= 0 && rowIndex < m_dataArray->size());
415 Q_ASSERT(m_dataArray->at(rowIndex)->size() == row->size());
416
417 if (row != m_dataArray->at(i: rowIndex)) {
418 clearRow(rowIndex);
419 (*m_dataArray)[rowIndex] = row;
420 }
421}
422
423void QSurfaceDataProxyPrivate::setRows(int rowIndex, const QSurfaceDataArray &rows)
424{
425 QSurfaceDataArray &dataArray = *m_dataArray;
426 Q_ASSERT(rowIndex >= 0 && (rowIndex + rows.size()) <= dataArray.size());
427
428 for (int i = 0; i < rows.size(); i++) {
429 Q_ASSERT(m_dataArray->at(rowIndex)->size() == rows.at(i)->size());
430 if (rows.at(i) != dataArray.at(i: rowIndex)) {
431 clearRow(rowIndex);
432 dataArray[rowIndex] = rows.at(i);
433 }
434 rowIndex++;
435 }
436}
437
438void QSurfaceDataProxyPrivate::setItem(int rowIndex, int columnIndex, const QSurfaceDataItem &item)
439{
440 Q_ASSERT(rowIndex >= 0 && rowIndex < m_dataArray->size());
441 QSurfaceDataRow &row = *(*m_dataArray)[rowIndex];
442 Q_ASSERT(columnIndex < row.size());
443 row[columnIndex] = item;
444}
445
446int QSurfaceDataProxyPrivate::addRow(QSurfaceDataRow *row)
447{
448 Q_ASSERT(m_dataArray->isEmpty()
449 || m_dataArray->at(0)->size() == row->size());
450 int currentSize = m_dataArray->size();
451 m_dataArray->append(t: row);
452 return currentSize;
453}
454
455int QSurfaceDataProxyPrivate::addRows(const QSurfaceDataArray &rows)
456{
457 int currentSize = m_dataArray->size();
458 for (int i = 0; i < rows.size(); i++) {
459 Q_ASSERT(m_dataArray->isEmpty()
460 || m_dataArray->at(0)->size() == rows.at(i)->size());
461 m_dataArray->append(t: rows.at(i));
462 }
463 return currentSize;
464}
465
466void QSurfaceDataProxyPrivate::insertRow(int rowIndex, QSurfaceDataRow *row)
467{
468 Q_ASSERT(rowIndex >= 0 && rowIndex <= m_dataArray->size());
469 Q_ASSERT(m_dataArray->isEmpty()
470 || m_dataArray->at(0)->size() == row->size());
471 m_dataArray->insert(i: rowIndex, t: row);
472}
473
474void QSurfaceDataProxyPrivate::insertRows(int rowIndex, const QSurfaceDataArray &rows)
475{
476 Q_ASSERT(rowIndex >= 0 && rowIndex <= m_dataArray->size());
477
478 for (int i = 0; i < rows.size(); i++) {
479 Q_ASSERT(m_dataArray->isEmpty()
480 || m_dataArray->at(0)->size() == rows.at(i)->size());
481 m_dataArray->insert(i: rowIndex++, t: rows.at(i));
482 }
483}
484
485void QSurfaceDataProxyPrivate::removeRows(int rowIndex, int removeCount)
486{
487 Q_ASSERT(rowIndex >= 0);
488 int maxRemoveCount = m_dataArray->size() - rowIndex;
489 removeCount = qMin(a: removeCount, b: maxRemoveCount);
490 for (int i = 0; i < removeCount; i++) {
491 clearRow(rowIndex);
492 m_dataArray->removeAt(i: rowIndex);
493 }
494}
495
496void QSurfaceDataProxyPrivate::limitValues(QVector3D &minValues, QVector3D &maxValues,
497 QAbstract3DAxis *axisX, QAbstract3DAxis *axisY,
498 QAbstract3DAxis *axisZ) const
499{
500 float min = 0.0f;
501 float max = 0.0f;
502
503 int rows = m_dataArray->size();
504 int columns = 0;
505 if (rows)
506 columns = m_dataArray->at(i: 0)->size();
507
508 if (rows && columns) {
509 min = m_dataArray->at(i: 0)->at(i: 0).y();
510 max = m_dataArray->at(i: 0)->at(i: 0).y();
511 }
512
513 for (int i = 0; i < rows; i++) {
514 QSurfaceDataRow *row = m_dataArray->at(i);
515 if (row) {
516 for (int j = 0; j < columns; j++) {
517 float itemValue = m_dataArray->at(i)->at(i: j).y();
518 if (qIsNaN(f: itemValue) || qIsInf(f: itemValue))
519 continue;
520 if ((min > itemValue || (qIsNaN(f: min) || qIsInf(f: min)))
521 && isValidValue(value: itemValue, axis: axisY)) {
522 min = itemValue;
523 }
524 if (max < itemValue || (qIsNaN(f: max) || qIsInf(f: max)))
525 max = itemValue;
526 }
527 }
528 }
529
530 minValues.setY(min);
531 maxValues.setY(max);
532
533 if (columns) {
534 // Have some defaults
535 float xLow = m_dataArray->at(i: 0)->at(i: 0).x();
536 float xHigh = m_dataArray->at(i: 0)->last().x();
537 float zLow = m_dataArray->at(i: 0)->at(i: 0).z();
538 float zHigh = m_dataArray->last()->at(i: 0).z();
539 for (int i = 0; i < rows; i++) {
540 for (int j = 0; j < columns; j++) {
541 float zItemValue = m_dataArray->at(i)->at(i: j).z();
542 if (qIsNaN(f: zItemValue) || qIsInf(f: zItemValue))
543 continue;
544 else if (isValidValue(value: zItemValue, axis: axisZ))
545 zLow = qMin(a: zLow,b: zItemValue);
546 }
547 if (!qIsNaN(f: zLow) && !qIsInf(f: zLow))
548 break;
549 }
550 for (int i = rows - 1; i >= 0; i--) {
551 for (int j = 0; j < columns; j++) {
552 float zItemValue = m_dataArray->at(i)->at(i: j).z();
553 if (qIsNaN(f: zItemValue) || qIsInf(f: zItemValue))
554 continue;
555 else if (isValidValue(value: zItemValue, axis: axisZ))
556 {
557 if (!qIsNaN(f: zHigh) && !qIsInf(f: zHigh))
558 zHigh = qMax(a: zHigh, b: zItemValue);
559 else
560 zHigh = zItemValue;
561 }
562 }
563 if (!qIsNaN(f: zHigh) && !qIsInf(f: zHigh))
564 break;
565 }
566 for (int j = 0; j<columns; j++){
567 for (int i = 0; i < rows; i++) {
568 float xItemValue = m_dataArray->at(i)->at(i: j).x();
569 if (qIsNaN(f: xItemValue) || qIsInf(f: xItemValue))
570 continue;
571 else if (isValidValue(value: xItemValue, axis: axisX))
572 xLow = qMin(a: xLow, b: xItemValue);
573 }
574 if (!qIsNaN(f: xLow) && !qIsInf(f: xLow))
575 break;
576 }
577 for (int j = columns-1; j >= 0; j--){
578 for (int i = 0; i < rows; i++) {
579 float xItemValue = m_dataArray->at(i)->at(i: j).x();
580 if (qIsNaN(f: xItemValue) || qIsInf(f: xItemValue))
581 continue;
582 else if (isValidValue(value: xItemValue, axis: axisX))
583 {
584 if (!qIsNaN(f: xHigh) && !qIsInf(f: xHigh))
585 xHigh = qMax(a: xHigh, b: xItemValue);
586 else
587 xHigh = xItemValue;
588 }
589 }
590 if (!qIsNaN(f: xHigh) && !qIsInf(f: xHigh))
591 break;
592 }
593 minValues.setX(xLow);
594 minValues.setZ(zLow);
595 maxValues.setX(xHigh);
596 maxValues.setZ(zHigh);
597 } else {
598 minValues.setX(axisX->d_func()->allowZero() ? 0.0f : 1.0f);
599 minValues.setZ(axisZ->d_func()->allowZero() ? 0.0f : 1.0f);
600 maxValues.setX(axisX->d_func()->allowZero() ? 0.0f : 1.0f);
601 maxValues.setZ(axisZ->d_func()->allowZero() ? 0.0f : 1.0f);
602 }
603}
604
605bool QSurfaceDataProxyPrivate::isValidValue(float value, QAbstract3DAxis *axis) const
606{
607 return (value > 0.0f || (value == 0.0f && axis->d_func()->allowZero())
608 || (value < 0.0f && axis->d_func()->allowNegatives()));
609}
610
611void QSurfaceDataProxyPrivate::clearRow(int rowIndex)
612{
613 if (m_dataArray->at(i: rowIndex)) {
614 delete m_dataArray->at(i: rowIndex);
615 (*m_dataArray)[rowIndex] = 0;
616 }
617}
618
619void QSurfaceDataProxyPrivate::clearArray()
620{
621 for (int i = 0; i < m_dataArray->size(); i++)
622 clearRow(rowIndex: i);
623 m_dataArray->clear();
624 delete m_dataArray;
625}
626
627void QSurfaceDataProxyPrivate::setSeries(QAbstract3DSeries *series)
628{
629 Q_Q(QSurfaceDataProxy);
630 QAbstractDataProxyPrivate::setSeries(series);
631 QSurface3DSeries *surfaceSeries = static_cast<QSurface3DSeries *>(series);
632 emit q->seriesChanged(series: surfaceSeries);
633}
634
635QT_END_NAMESPACE
636

source code of qtgraphs/src/graphs/data/qsurfacedataproxy.cpp