// Copyright (C) 2017 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "qbardataproxy_p.h" #include "qbar3dseries_p.h" QT_BEGIN_NAMESPACE /*! * \class QBarDataProxy * \inmodule QtDataVisualization * \brief The QBarDataProxy class is the data proxy for a 3D bars graph. * \since QtDataVisualization 1.0 * * A bar data proxy handles adding, inserting, changing, and removing rows of * data. * * The data array is a list of vectors (rows) of QBarDataItem instances. * Each row can contain a different number of items or even be null. * * QBarDataProxy takes ownership of all QtDataVisualization::QBarDataRow objects * passed to it, whether directly or in a QtDataVisualization::QBarDataArray container. * If bar data row pointers are used to directly modify data after adding the * array to the proxy, the appropriate signal must be emitted to update the * graph. * * QBarDataProxy optionally keeps track of row and column labels, which QCategory3DAxis can utilize * to show axis labels. The row and column labels are stored in a separate array from the data and * row manipulation methods provide alternate versions that do not affect the row labels. * This enables the option of having row labels that relate to the position of the data in the * array rather than the data itself. * * \sa {Qt Data Visualization Data Handling} */ /*! * \typedef QBarDataRow * \relates QBarDataProxy * * A list of \l {QBarDataItem} objects. */ /*! * \typedef QBarDataArray * \relates QBarDataProxy * * A list of pointers to \l {QBarDataRow} objects. */ /*! * \qmltype BarDataProxy * \inqmlmodule QtDataVisualization * \since QtDataVisualization 1.0 * \ingroup datavisualization_qml * \instantiates QBarDataProxy * \inherits AbstractDataProxy * \brief The data proxy for a 3D bars graph. * * This type handles adding, inserting, changing, and removing rows of data with Qt Quick 2. * * This type is uncreatable, but contains properties that are exposed via subtypes. * * For a more complete description, see QBarDataProxy. * * \sa ItemModelBarDataProxy, {Qt Data Visualization Data Handling} */ /*! * \qmlproperty int BarDataProxy::rowCount * The number of rows in the array. */ /*! * \qmlproperty list BarDataProxy::rowLabels * * The optional row labels for the array. Indexes in this array match the row * indexes in the data array. * If the list is shorter than the number of rows, all rows will not get labels. */ /*! * \qmlproperty list BarDataProxy::columnLabels * * The optional column labels for the array. Indexes in this array match column indexes in rows. * If the list is shorter than the longest row, all columns will not get labels. */ /*! * \qmlproperty Bar3DSeries BarDataProxy::series * * The series this proxy is attached to. */ /*! * Constructs a bar data proxy with the given \a parent. */ QBarDataProxy::QBarDataProxy(QObject *parent) : QAbstractDataProxy(new QBarDataProxyPrivate(this), parent) { } /*! * \internal */ QBarDataProxy::QBarDataProxy(QBarDataProxyPrivate *d, QObject *parent) : QAbstractDataProxy(d, parent) { } /*! * Deletes the bar data proxy. */ QBarDataProxy::~QBarDataProxy() { } /*! * \property QBarDataProxy::series * * \brief The series this proxy is attached to. */ QBar3DSeries *QBarDataProxy::series() const { return static_cast(d_ptr->series()); } /*! * Clears the existing array and row and column labels. */ void QBarDataProxy::resetArray() { resetArray(0, QStringList(), QStringList()); emit rowCountChanged(rowCount()); } /*! * Takes ownership of the array \a newArray. Clears the existing array if the * new array differs from it. If the arrays are the same, this function * just triggers the arrayReset() signal. * * Passing a null array deletes the old array and creates a new empty array. * Row and column labels are not affected. */ void QBarDataProxy::resetArray(QBarDataArray *newArray) { dptr()->resetArray(newArray, 0, 0); emit arrayReset(); emit rowCountChanged(rowCount()); } /*! * Takes ownership of the array \a newArray. Clears the existing array if the * new array differs from it. If the arrays are the same, this function * just triggers the arrayReset() signal. * * Passing a null array deletes the old array and creates a new empty array. * * The \a rowLabels and \a columnLabels lists specify the new labels for rows and columns. */ void QBarDataProxy::resetArray(QBarDataArray *newArray, const QStringList &rowLabels, const QStringList &columnLabels) { dptr()->resetArray(newArray, &rowLabels, &columnLabels); emit arrayReset(); emit rowCountChanged(rowCount()); } /*! * Changes an existing row by replacing the row at the position \a rowIndex * with the new row specified by \a row. The new row can be * the same as the existing row already stored at \a rowIndex. * Existing row labels are not affected. */ void QBarDataProxy::setRow(int rowIndex, QBarDataRow *row) { dptr()->setRow(rowIndex, row, 0); emit rowsChanged(rowIndex, 1); } /*! * Changes an existing row by replacing the row at the position \a rowIndex * with the new row specified by \a row. The new row can be * the same as the existing row already stored at \a rowIndex. * Changes the row label to \a label. */ void QBarDataProxy::setRow(int rowIndex, QBarDataRow *row, const QString &label) { dptr()->setRow(rowIndex, row, &label); emit rowsChanged(rowIndex, 1); } /*! * Changes existing rows by replacing the rows starting at the position * \a rowIndex with the new rows specifies by \a rows. * Existing row labels are not affected. The rows in the \a rows array can be * the same as the existing rows already stored at \a rowIndex. */ void QBarDataProxy::setRows(int rowIndex, const QBarDataArray &rows) { dptr()->setRows(rowIndex, rows, 0); emit rowsChanged(rowIndex, rows.size()); } /*! * Changes existing rows by replacing the rows starting at the position * \a rowIndex with the new rows specifies by \a rows. * The row labels are changed to \a labels. The rows in the \a rows array can be * the same as the existing rows already stored at \a rowIndex. */ void QBarDataProxy::setRows(int rowIndex, const QBarDataArray &rows, const QStringList &labels) { dptr()->setRows(rowIndex, rows, &labels); emit rowsChanged(rowIndex, rows.size()); } /*! * Changes a single item at the position specified by \a rowIndex and * \a columnIndex to the item \a item. */ void QBarDataProxy::setItem(int rowIndex, int columnIndex, const QBarDataItem &item) { dptr()->setItem(rowIndex, columnIndex, item); emit itemChanged(rowIndex, columnIndex); } /*! * Changes a single item at the position \a position to the item \a item. * The x-value of \a position indicates the row and the y-value indicates the * column. */ void QBarDataProxy::setItem(const QPoint &position, const QBarDataItem &item) { setItem(position.x(), position.y(), item); } /*! * Adds the new row \a row to the end of an array. * Existing row labels are not affected. * * Returns the index of the added row. */ int QBarDataProxy::addRow(QBarDataRow *row) { int addIndex = dptr()->addRow(row, 0); emit rowsAdded(addIndex, 1); emit rowCountChanged(rowCount()); return addIndex; } /*! * Adds a the new row \a row with the label \a label to the end of an array. * * Returns the index of the added row. */ int QBarDataProxy::addRow(QBarDataRow *row, const QString &label) { int addIndex = dptr()->addRow(row, &label); emit rowsAdded(addIndex, 1); emit rowCountChanged(rowCount()); return addIndex; } /*! * Adds the new \a rows to the end of an array. * Existing row labels are not affected. * * Returns the index of the first added row. */ int QBarDataProxy::addRows(const QBarDataArray &rows) { int addIndex = dptr()->addRows(rows, 0); emit rowsAdded(addIndex, rows.size()); emit rowCountChanged(rowCount()); return addIndex; } /*! * Adds the new \a rows with \a labels to the end of the array. * * Returns the index of the first added row. */ int QBarDataProxy::addRows(const QBarDataArray &rows, const QStringList &labels) { int addIndex = dptr()->addRows(rows, &labels); emit rowsAdded(addIndex, rows.size()); emit rowCountChanged(rowCount()); return addIndex; } /*! * Inserts the new row \a row into \a rowIndex. * If \a rowIndex is equal to the array size, the rows are added to the end of * the array. * The existing row labels are not affected. * \note The row labels array will be out of sync with the row array after this call * if there were labeled rows beyond the inserted row. */ void QBarDataProxy::insertRow(int rowIndex, QBarDataRow *row) { dptr()->insertRow(rowIndex, row, 0); emit rowsInserted(rowIndex, 1); emit rowCountChanged(rowCount()); } /*! * Inserts the new row \a row with the label \a label into \a rowIndex. * If \a rowIndex is equal to array size, rows are added to the end of the * array. */ void QBarDataProxy::insertRow(int rowIndex, QBarDataRow *row, const QString &label) { dptr()->insertRow(rowIndex, row, &label); emit rowsInserted(rowIndex, 1); emit rowCountChanged(rowCount()); } /*! * Inserts new \a rows into \a rowIndex. * If \a rowIndex is equal to the array size, the rows are added to the end of * the array. The existing row labels are not affected. * \note The row labels array will be out of sync with the row array after this call * if there were labeled rows beyond the inserted rows. */ void QBarDataProxy::insertRows(int rowIndex, const QBarDataArray &rows) { dptr()->insertRows(rowIndex, rows, 0); emit rowsInserted(rowIndex, rows.size()); emit rowCountChanged(rowCount()); } /*! * Inserts new \a rows with \a labels into \a rowIndex. * If \a rowIndex is equal to the array size, the rows are added to the end of * the array. */ void QBarDataProxy::insertRows(int rowIndex, const QBarDataArray &rows, const QStringList &labels) { dptr()->insertRows(rowIndex, rows, &labels); emit rowsInserted(rowIndex, rows.size()); emit rowCountChanged(rowCount()); } /*! * Removes the number of rows specified by \a removeCount starting at the * position \a rowIndex. Attempting to remove rows past the end of the * array does nothing. If \a removeLabels is \c true, the corresponding row * labels are also removed. Otherwise, the row labels are not affected. * \note If \a removeLabels is \c false, the row labels array will be out of * sync with the row array if there are labeled rows beyond the removed rows. */ void QBarDataProxy::removeRows(int rowIndex, int removeCount, bool removeLabels) { if (rowIndex < rowCount() && removeCount >= 1) { dptr()->removeRows(rowIndex, removeCount, removeLabels); emit rowsRemoved(rowIndex, removeCount); emit rowCountChanged(rowCount()); } } /*! * \property QBarDataProxy::rowCount * * \brief The number of rows in the array. */ int QBarDataProxy::rowCount() const { return dptrc()->m_dataArray->size(); } /*! * \property QBarDataProxy::rowLabels * * \brief The optional row labels for the array. * * Indexes in this array match the row indexes in the data array. * If the list is shorter than the number of rows, all rows will not get labels. */ QStringList QBarDataProxy::rowLabels() const { return dptrc()->m_rowLabels; } void QBarDataProxy::setRowLabels(const QStringList &labels) { if (dptr()->m_rowLabels != labels) { dptr()->m_rowLabels = labels; emit rowLabelsChanged(); } } /*! * \property QBarDataProxy::columnLabels * * \brief The optional column labels for the array. * * Indexes in this array match column indexes in rows. * If the list is shorter than the longest row, all columns will not get labels. */ QStringList QBarDataProxy::columnLabels() const { return dptrc()->m_columnLabels; } void QBarDataProxy::setColumnLabels(const QStringList &labels) { if (dptr()->m_columnLabels != labels) { dptr()->m_columnLabels = labels; emit columnLabelsChanged(); } } /*! * Returns the pointer to the data array. */ const QBarDataArray *QBarDataProxy::array() const { return dptrc()->m_dataArray; } /*! * Returns the pointer to the row at the position \a rowIndex. It is guaranteed * to be valid only until the next call that modifies data. */ const QBarDataRow *QBarDataProxy::rowAt(int rowIndex) const { const QBarDataArray &dataArray = *dptrc()->m_dataArray; Q_ASSERT(rowIndex >= 0 && rowIndex < dataArray.size()); return dataArray[rowIndex]; } /*! * Returns the pointer to the item at the position specified by \a rowIndex and * \a columnIndex. It is guaranteed to be valid only * until the next call that modifies data. */ const QBarDataItem *QBarDataProxy::itemAt(int rowIndex, int columnIndex) const { const QBarDataArray &dataArray = *dptrc()->m_dataArray; Q_ASSERT(rowIndex >= 0 && rowIndex < dataArray.size()); const QBarDataRow &dataRow = *dataArray[rowIndex]; Q_ASSERT(columnIndex >= 0 && columnIndex < dataRow.size()); return &dataRow.at(columnIndex); } /*! * Returns the pointer to the item at the position \a position. The x-value of * \a position indicates the row and the y-value indicates the column. The item * is guaranteed to be valid only until the next call that modifies data. */ const QBarDataItem *QBarDataProxy::itemAt(const QPoint &position) const { return itemAt(position.x(), position.y()); } /*! * \internal */ QBarDataProxyPrivate *QBarDataProxy::dptr() { return static_cast(d_ptr.data()); } /*! * \internal */ const QBarDataProxyPrivate *QBarDataProxy::dptrc() const { return static_cast(d_ptr.data()); } /*! * \fn void QBarDataProxy::arrayReset() * * This signal is emitted when the data array is reset. * If the contents of the whole array are changed without calling resetArray(), * this signal needs to be emitted to update the graph. */ /*! * \fn void QBarDataProxy::rowsAdded(int startIndex, int count) * * This signal is emitted when the number of rows specified by \a count is * added starting at the position \a startIndex. * If rows are added to the array without calling addRow() or addRows(), * this signal needs to be emitted to update the graph. */ /*! * \fn void QBarDataProxy::rowsChanged(int startIndex, int count) * * This signal is emitted when the number of rows specified by \a count is * changed starting at the position \a startIndex. * If rows are changed in the array without calling setRow() or setRows(), * this signal needs to be emitted to update the graph. */ /*! * \fn void QBarDataProxy::rowsRemoved(int startIndex, int count) * * This signal is emitted when the number of rows specified by \a count is * removed starting at the position \a startIndex. * * The index is the current array size if the rows were removed from the end of * the array. If rows are removed from the array without calling removeRows(), * this signal needs to be emitted to update the graph. */ /*! * \fn void QBarDataProxy::rowsInserted(int startIndex, int count) * * This signal is emitted when the number of rows specified by \a count is * inserted at the position \a startIndex. * * If rows are inserted into the array without calling insertRow() or * insertRows(), this signal needs to be emitted to update the graph. */ /*! * \fn void QBarDataProxy::itemChanged(int rowIndex, int columnIndex) * * This signal is emitted when the item at the position specified by \a rowIndex * and \a columnIndex changes. * If the item is changed in the array without calling setItem(), * this signal needs to be emitted to update the graph. */ // QBarDataProxyPrivate QBarDataProxyPrivate::QBarDataProxyPrivate(QBarDataProxy *q) : QAbstractDataProxyPrivate(q, QAbstractDataProxy::DataTypeBar), m_dataArray(new QBarDataArray) { } QBarDataProxyPrivate::~QBarDataProxyPrivate() { clearArray(); } void QBarDataProxyPrivate::resetArray(QBarDataArray *newArray, const QStringList *rowLabels, const QStringList *columnLabels) { if (rowLabels) qptr()->setRowLabels(*rowLabels); if (columnLabels) qptr()->setColumnLabels(*columnLabels); if (!newArray) newArray = new QBarDataArray; if (newArray != m_dataArray) { clearArray(); m_dataArray = newArray; } } void QBarDataProxyPrivate::setRow(int rowIndex, QBarDataRow *row, const QString *label) { Q_ASSERT(rowIndex >= 0 && rowIndex < m_dataArray->size()); if (label) fixRowLabels(rowIndex, 1, QStringList(*label), false); if (row != m_dataArray->at(rowIndex)) { clearRow(rowIndex); (*m_dataArray)[rowIndex] = row; } } void QBarDataProxyPrivate::setRows(int rowIndex, const QBarDataArray &rows, const QStringList *labels) { QBarDataArray &dataArray = *m_dataArray; Q_ASSERT(rowIndex >= 0 && (rowIndex + rows.size()) <= dataArray.size()); if (labels) fixRowLabels(rowIndex, rows.size(), *labels, false); for (int i = 0; i < rows.size(); i++) { if (rows.at(i) != dataArray.at(rowIndex)) { clearRow(rowIndex); dataArray[rowIndex] = rows.at(i); } rowIndex++; } } void QBarDataProxyPrivate::setItem(int rowIndex, int columnIndex, const QBarDataItem &item) { Q_ASSERT(rowIndex >= 0 && rowIndex < m_dataArray->size()); QBarDataRow &row = *(*m_dataArray)[rowIndex]; Q_ASSERT(columnIndex < row.size()); row[columnIndex] = item; } int QBarDataProxyPrivate::addRow(QBarDataRow *row, const QString *label) { int currentSize = m_dataArray->size(); if (label) fixRowLabels(currentSize, 1, QStringList(*label), false); m_dataArray->append(row); return currentSize; } int QBarDataProxyPrivate::addRows(const QBarDataArray &rows, const QStringList *labels) { int currentSize = m_dataArray->size(); if (labels) fixRowLabels(currentSize, rows.size(), *labels, false); for (int i = 0; i < rows.size(); i++) m_dataArray->append(rows.at(i)); return currentSize; } void QBarDataProxyPrivate::insertRow(int rowIndex, QBarDataRow *row, const QString *label) { Q_ASSERT(rowIndex >= 0 && rowIndex <= m_dataArray->size()); if (label) fixRowLabels(rowIndex, 1, QStringList(*label), true); m_dataArray->insert(rowIndex, row); } void QBarDataProxyPrivate::insertRows(int rowIndex, const QBarDataArray &rows, const QStringList *labels) { Q_ASSERT(rowIndex >= 0 && rowIndex <= m_dataArray->size()); if (labels) fixRowLabels(rowIndex, rows.size(), *labels, true); for (int i = 0; i < rows.size(); i++) m_dataArray->insert(rowIndex++, rows.at(i)); } void QBarDataProxyPrivate::removeRows(int rowIndex, int removeCount, bool removeLabels) { Q_ASSERT(rowIndex >= 0); int maxRemoveCount = m_dataArray->size() - rowIndex; removeCount = qMin(removeCount, maxRemoveCount); bool labelsChanged = false; for (int i = 0; i < removeCount; i++) { clearRow(rowIndex); m_dataArray->removeAt(rowIndex); if (removeLabels && m_rowLabels.size() > rowIndex) { m_rowLabels.removeAt(rowIndex); labelsChanged = true; } } if (labelsChanged) emit qptr()->rowLabelsChanged(); } QBarDataProxy *QBarDataProxyPrivate::qptr() { return static_cast(q_ptr); } void QBarDataProxyPrivate::clearRow(int rowIndex) { if (m_dataArray->at(rowIndex)) { delete m_dataArray->at(rowIndex); (*m_dataArray)[rowIndex] = 0; } } void QBarDataProxyPrivate::clearArray() { for (int i = 0; i < m_dataArray->size(); i++) clearRow(i); m_dataArray->clear(); delete m_dataArray; } /*! * \internal * Fixes the row label array to include specified labels. */ void QBarDataProxyPrivate::fixRowLabels(int startIndex, int count, const QStringList &newLabels, bool isInsert) { bool changed = false; int currentSize = m_rowLabels.size(); int newSize = newLabels.size(); if (startIndex >= currentSize) { // Adding labels past old label array, create empty strings to fill intervening space if (newSize) { for (int i = currentSize; i < startIndex; i++) m_rowLabels << QString(); // Doesn't matter if insert, append, or just change when there were no existing // strings, just append new strings. m_rowLabels << newLabels; changed = true; } } else { if (isInsert) { int insertIndex = startIndex; if (count) changed = true; for (int i = 0; i < count; i++) { if (i < newSize) m_rowLabels.insert(insertIndex++, newLabels.at(i)); else m_rowLabels.insert(insertIndex++, QString()); } } else { // Either append or change, replace labels up to array end and then add new ones int lastChangeIndex = count + startIndex; int newIndex = 0; for (int i = startIndex; i < lastChangeIndex; i++) { if (i >= currentSize) { // Label past the current size, so just append the new label if (newSize < newIndex) { changed = true; m_rowLabels << newLabels.at(newIndex); } else { break; // No point appending empty strings, so just exit } } else if (newSize > newIndex) { // Replace existing label if (m_rowLabels.at(i) != newLabels.at(newIndex)) { changed = true; m_rowLabels[i] = newLabels.at(newIndex); } } else { // No more new labels, so clear existing label if (!m_rowLabels.at(i).isEmpty()) { changed = true; m_rowLabels[i] = QString(); } } newIndex++; } } } if (changed) emit qptr()->rowLabelsChanged(); } QPair QBarDataProxyPrivate::limitValues(int startRow, int endRow, int startColumn, int endColumn) const { QPair limits = qMakePair(0.0f, 0.0f); endRow = qMin(endRow, m_dataArray->size() - 1); for (int i = startRow; i <= endRow; i++) { QBarDataRow *row = m_dataArray->at(i); if (row) { int lastColumn = qMin(endColumn, row->size() - 1); for (int j = startColumn; j <= lastColumn; j++) { const QBarDataItem &item = row->at(j); float itemValue = item.value(); if (limits.second < itemValue) limits.second = itemValue; if (limits.first > itemValue) limits.first = itemValue; } } } return limits; } void QBarDataProxyPrivate::setSeries(QAbstract3DSeries *series) { QAbstractDataProxyPrivate::setSeries(series); QBar3DSeries *barSeries = static_cast(series); emit qptr()->seriesChanged(barSeries); } QT_END_NAMESPACE