From 177f9d385c8cd062c4bad78cf6b794a96fa025ad Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Tue, 21 Jan 2014 08:55:03 +0200 Subject: Selection correction for scatter when data changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements item 3) in QTRD-2645 Task-number: QTRD-264 Change-Id: Ibe758bbfb3b4a74b55589a410b402bbdf07ea64f Reviewed-by: Tomi Korpipää --- .../data/abstractitemmodelhandler.cpp | 85 +++++++++++++------ .../data/abstractitemmodelhandler_p.h | 1 + .../data/qitemmodelscatterdataproxy.cpp | 4 +- .../data/scatteritemmodelhandler.cpp | 95 ++++++++++++++++++---- .../data/scatteritemmodelhandler_p.h | 12 +++ .../engine/abstract3dcontroller.cpp | 12 ++- .../engine/abstract3dcontroller_p.h | 3 +- src/datavisualization/engine/bars3dcontroller.cpp | 29 ++++--- .../engine/scatter3dcontroller.cpp | 73 +++++++++++++---- .../engine/scatter3dcontroller_p.h | 27 ++++++ .../engine/surface3dcontroller.cpp | 21 +++-- 11 files changed, 273 insertions(+), 89 deletions(-) (limited to 'src') diff --git a/src/datavisualization/data/abstractitemmodelhandler.cpp b/src/datavisualization/data/abstractitemmodelhandler.cpp index cf87c3f9..879ce086 100644 --- a/src/datavisualization/data/abstractitemmodelhandler.cpp +++ b/src/datavisualization/data/abstractitemmodelhandler.cpp @@ -23,7 +23,8 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION AbstractItemModelHandler::AbstractItemModelHandler(QObject *parent) : QObject(parent), - resolvePending(0) + resolvePending(0), + m_fullReset(true) { m_resolveTimer.setSingleShot(true); QObject::connect(&m_resolveTimer, &QTimer::timeout, @@ -81,9 +82,12 @@ void AbstractItemModelHandler::handleColumnsInserted(const QModelIndex &parent, Q_UNUSED(start) Q_UNUSED(end) - // Resolve new items - if (!m_resolveTimer.isActive()) - m_resolveTimer.start(0); // TODO Resolving entire model is inefficient + // Manipulating columns changes all rows in proxies that map rows/columns directly, + // and its effects are not clearly defined in others -> always do full reset. + if (!m_resolveTimer.isActive()) { + m_fullReset = true; + m_resolveTimer.start(0); + } } void AbstractItemModelHandler::handleColumnsMoved(const QModelIndex &sourceParent, @@ -98,9 +102,12 @@ void AbstractItemModelHandler::handleColumnsMoved(const QModelIndex &sourceParen Q_UNUSED(destinationParent) Q_UNUSED(destinationColumn) - // Resolve moved items - if (!m_resolveTimer.isActive()) - m_resolveTimer.start(0); // TODO Resolving entire model is inefficient + // Manipulating columns changes all rows in proxies that map rows/columns directly, + // and its effects are not clearly defined in others -> always do full reset. + if (!m_resolveTimer.isActive()) { + m_fullReset = true; + m_resolveTimer.start(0); + } } void AbstractItemModelHandler::handleColumnsRemoved(const QModelIndex &parent, @@ -110,9 +117,12 @@ void AbstractItemModelHandler::handleColumnsRemoved(const QModelIndex &parent, Q_UNUSED(start) Q_UNUSED(end) - // Remove old items - if (!m_resolveTimer.isActive()) - m_resolveTimer.start(0); // TODO Resolving entire model is inefficient + // Manipulating columns changes all rows in proxies that map rows/columns directly, + // and its effects are not clearly defined in others -> always do full reset. + if (!m_resolveTimer.isActive()) { + m_fullReset = true; + m_resolveTimer.start(0); + } } void AbstractItemModelHandler::handleDataChanged(const QModelIndex &topLeft, @@ -123,9 +133,13 @@ void AbstractItemModelHandler::handleDataChanged(const QModelIndex &topLeft, Q_UNUSED(bottomRight) Q_UNUSED(roles) - // Resolve changed items - if (!m_resolveTimer.isActive()) - m_resolveTimer.start(0); // TODO Resolving entire model is inefficient + // Default handling for dataChanged is to do full reset, as it cannot be optimized + // in a general case, where we do not know which row/column/index the item model item + // actually ended up to in the proxy. + if (!m_resolveTimer.isActive()) { + m_fullReset = true; + m_resolveTimer.start(0); + } } void AbstractItemModelHandler::handleLayoutChanged(const QList &parents, @@ -134,16 +148,20 @@ void AbstractItemModelHandler::handleLayoutChanged(const QList m_itemModel; // Not owned bool resolvePending; QTimer m_resolveTimer; + bool m_fullReset; private: Q_DISABLE_COPY(AbstractItemModelHandler) diff --git a/src/datavisualization/data/qitemmodelscatterdataproxy.cpp b/src/datavisualization/data/qitemmodelscatterdataproxy.cpp index 74979309..16a7c8f7 100644 --- a/src/datavisualization/data/qitemmodelscatterdataproxy.cpp +++ b/src/datavisualization/data/qitemmodelscatterdataproxy.cpp @@ -32,7 +32,9 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION * for Q3DScatter. It maps roles of QAbstractItemModel to the XYZ-values of Q3DScatter points. * * The data is resolved asynchronously whenever the mapping or the model changes. - * QScatterDataProxy::arrayReset() is emitted when the data has been resolved. + * QScatterDataProxy::arrayReset() is emitted when the data has been resolved. However, inserts, + * removes, and single data item changes after the model initialization are resolved synchronously, + * unless the same frame also contains a change that causes the whole model to be resolved. * * Mapping ignores rows and columns of the QAbstractItemModel and treats * all items equally. It requires the model to provide at least three roles for the data items diff --git a/src/datavisualization/data/scatteritemmodelhandler.cpp b/src/datavisualization/data/scatteritemmodelhandler.cpp index 2faa02a9..81ecf8b0 100644 --- a/src/datavisualization/data/scatteritemmodelhandler.cpp +++ b/src/datavisualization/data/scatteritemmodelhandler.cpp @@ -21,6 +21,8 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION +static const int noRoleIndex = -1; + ScatterItemModelHandler::ScatterItemModelHandler(QItemModelScatterDataProxy *proxy, QObject *parent) : AbstractItemModelHandler(parent), m_proxy(proxy), @@ -32,6 +34,79 @@ ScatterItemModelHandler::~ScatterItemModelHandler() { } +void ScatterItemModelHandler::handleDataChanged(const QModelIndex &topLeft, + const QModelIndex &bottomRight, + const QVector &roles) +{ + // Do nothing if full reset already pending + if (!m_fullReset) { + if (m_itemModel->columnCount() > 1) { + // If the data model is multi-column, do full asynchronous reset to simplify things + AbstractItemModelHandler::handleDataChanged(topLeft, bottomRight, roles); + } else { + int start = qMin(topLeft.row(), bottomRight.row()); + int end = qMax(topLeft.row(), bottomRight.row()); + + QScatterDataArray array(end - start + 1); + int count = 0; + for (int i = start; i <= end; i++) + modelPosToScatterItem(i, 0, array[count++]); + + m_proxy->setItems(start, array); + } + } +} + +void ScatterItemModelHandler::handleRowsInserted(const QModelIndex &parent, int start, int end) +{ + // Do nothing if full reset already pending + if (!m_fullReset) { + if (!m_proxy->itemCount() || m_itemModel->columnCount() > 1) { + // If inserting into an empty array, do full asynchronous reset to avoid multiple + // separate inserts when initializing the model. + // If the data model is multi-column, do full asynchronous reset to simplify things + AbstractItemModelHandler::handleRowsInserted(parent, start, end); + } else { + QScatterDataArray array(end - start + 1); + int count = 0; + for (int i = start; i <= end; i++) + modelPosToScatterItem(i, 0, array[count++]); + + m_proxy->insertItems(start, array); + } + } +} + +void ScatterItemModelHandler::handleRowsRemoved(const QModelIndex &parent, int start, int end) +{ + Q_UNUSED(parent) + + // Do nothing if full reset already pending + if (!m_fullReset) { + if (m_itemModel->columnCount() > 1) { + // If the data model is multi-column, do full asynchronous reset to simplify things + AbstractItemModelHandler::handleRowsRemoved(parent, start, end); + } else { + m_proxy->removeItems(start, end - start + 1); + } + } +} + +void ScatterItemModelHandler::modelPosToScatterItem(int modelRow, int modelColumn, QScatterDataItem &item) +{ + QModelIndex index = m_itemModel->index(modelRow, modelColumn); + float xPos(0.0f); + float yPos(0.0f); + float zPos(0.0f); + if (m_xPosRole != noRoleIndex) + xPos = index.data(m_xPosRole).toFloat(); + if (m_yPosRole != noRoleIndex) + yPos = index.data(m_yPosRole).toFloat(); + if (m_zPosRole != noRoleIndex) + zPos = index.data(m_zPosRole).toFloat(); + item.setPosition(QVector3D(xPos, yPos, zPos)); +} + // Resolve entire item model into QScatterDataArray. void ScatterItemModelHandler::resolveModel() { @@ -41,12 +116,10 @@ void ScatterItemModelHandler::resolveModel() return; } - static const int noRoleIndex = -1; - QHash roleHash = m_itemModel->roleNames(); - const int xPosRole = roleHash.key(m_proxy->xPosRole().toLatin1(), noRoleIndex); - const int yPosRole = roleHash.key(m_proxy->yPosRole().toLatin1(), noRoleIndex); - const int zPosRole = roleHash.key(m_proxy->zPosRole().toLatin1(), noRoleIndex); + m_xPosRole = roleHash.key(m_proxy->xPosRole().toLatin1(), noRoleIndex); + m_yPosRole = roleHash.key(m_proxy->yPosRole().toLatin1(), noRoleIndex); + m_zPosRole = roleHash.key(m_proxy->zPosRole().toLatin1(), noRoleIndex); const int columnCount = m_itemModel->columnCount(); const int rowCount = m_itemModel->rowCount(); const int totalCount = rowCount * columnCount; @@ -59,17 +132,7 @@ void ScatterItemModelHandler::resolveModel() // Parse data into newProxyArray for (int i = 0; i < rowCount; i++) { for (int j = 0; j < columnCount; j++) { - QModelIndex index = m_itemModel->index(i, j); - float xPos(0.0f); - float yPos(0.0f); - float zPos(0.0f); - if (xPosRole != noRoleIndex) - xPos = index.data(xPosRole).toFloat(); - if (yPosRole != noRoleIndex) - yPos = index.data(yPosRole).toFloat(); - if (zPosRole != noRoleIndex) - zPos = index.data(zPosRole).toFloat(); - (*m_proxyArray)[runningCount].setPosition(QVector3D(xPos, yPos, zPos)); + modelPosToScatterItem(i, j, (*m_proxyArray)[runningCount]); runningCount++; } } diff --git a/src/datavisualization/data/scatteritemmodelhandler_p.h b/src/datavisualization/data/scatteritemmodelhandler_p.h index f54ed2c5..06927509 100644 --- a/src/datavisualization/data/scatteritemmodelhandler_p.h +++ b/src/datavisualization/data/scatteritemmodelhandler_p.h @@ -41,11 +41,23 @@ public: ScatterItemModelHandler(QItemModelScatterDataProxy *proxy, QObject *parent = 0); virtual ~ScatterItemModelHandler(); +public slots: + virtual void handleDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, + const QVector &roles = QVector ()); + virtual void handleRowsInserted(const QModelIndex &parent, int start, int end); + virtual void handleRowsRemoved(const QModelIndex &parent, int start, int end); + protected: void virtual resolveModel(); +private: + void modelPosToScatterItem(int modelRow, int modelColumn, QScatterDataItem &item); + QItemModelScatterDataProxy *m_proxy; // Not owned QScatterDataArray *m_proxyArray; // Not owned + int m_xPosRole; + int m_yPosRole; + int m_zPosRole; }; QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/engine/abstract3dcontroller.cpp b/src/datavisualization/engine/abstract3dcontroller.cpp index 9942a2fe..1efbea8d 100644 --- a/src/datavisualization/engine/abstract3dcontroller.cpp +++ b/src/datavisualization/engine/abstract3dcontroller.cpp @@ -30,6 +30,7 @@ #include "qabstract3dseries_p.h" #include "thememanager_p.h" #include "q3dscene_p.h" +#include "q3dscene.h" #include @@ -147,9 +148,7 @@ QList Abstract3DController::seriesList() */ void Abstract3DController::synchDataToRenderer() { - // If we don't have a renderer, don't do anything - if (!m_renderer) - return; + // Subclass implementations check for renderer validity already, so no need to check here. // If there is a pending click from renderer, handle that first. if (m_renderer->isClickPending()) { @@ -157,7 +156,7 @@ void Abstract3DController::synchDataToRenderer() m_renderer->clearClickPending(); } - // TODO: start recording inserts/removals + startRecordingRemovesAndInserts(); if (m_scene->d_ptr->m_sceneDirty) m_renderer->updateScene(m_scene); @@ -1016,6 +1015,11 @@ QCategory3DAxis *Abstract3DController::createDefaultCategoryAxis() return defaultAxis; } +void Abstract3DController::startRecordingRemovesAndInserts() +{ + // Default implementation does nothing +} + void Abstract3DController::emitNeedRender() { if (!m_renderPending) { diff --git a/src/datavisualization/engine/abstract3dcontroller_p.h b/src/datavisualization/engine/abstract3dcontroller_p.h index 17c1e1fb..0b89251d 100644 --- a/src/datavisualization/engine/abstract3dcontroller_p.h +++ b/src/datavisualization/engine/abstract3dcontroller_p.h @@ -138,9 +138,9 @@ private: ThemeManager *m_themeManager; QAbstract3DGraph::SelectionFlags m_selectionMode; QAbstract3DGraph::ShadowQuality m_shadowQuality; - Q3DScene *m_scene; protected: + Q3DScene *m_scene; QList m_inputHandlers; // List of all added input handlers QAbstract3DInputHandler *m_activeInputHandler; CameraHelper *m_cameraHelper; @@ -273,6 +273,7 @@ protected: virtual QAbstract3DAxis *createDefaultAxis(QAbstract3DAxis::AxisOrientation orientation); QValue3DAxis *createDefaultValueAxis(); QCategory3DAxis *createDefaultCategoryAxis(); + virtual void startRecordingRemovesAndInserts(); private: void setAxisHelper(QAbstract3DAxis::AxisOrientation orientation, QAbstract3DAxis *axis, diff --git a/src/datavisualization/engine/bars3dcontroller.cpp b/src/datavisualization/engine/bars3dcontroller.cpp index d85e474c..b0627377 100644 --- a/src/datavisualization/engine/bars3dcontroller.cpp +++ b/src/datavisualization/engine/bars3dcontroller.cpp @@ -70,6 +70,9 @@ void Bars3DController::initializeOpenGL() void Bars3DController::synchDataToRenderer() { + if (!isInitialized()) + return; + // Background change requires reloading the meshes in bar graphs, so dirty the series visuals if (m_themeManager->activeTheme()->d_ptr->m_dirtyBits.backgroundEnabledDirty) { m_isSeriesVisualsDirty = true; @@ -79,9 +82,6 @@ void Bars3DController::synchDataToRenderer() Abstract3DController::synchDataToRenderer(); - if (!isInitialized()) - return; - // Notify changes to renderer if (m_changeTracker.barSpecsChanged) { m_renderer->updateBarSpecs(m_barThicknessRatio, m_barSpacing, m_isBarSpecRelative); @@ -135,12 +135,8 @@ void Bars3DController::handleRowsRemoved(int startIndex, int count) { Q_UNUSED(startIndex) Q_UNUSED(count) - QBar3DSeries *series = static_cast(sender())->series(); - if (series->isVisible()) { - adjustAxisRanges(); - m_isDataDirty = true; - } + QBar3DSeries *series = static_cast(sender())->series(); if (series == m_selectedBarSeries) { // If rows removed from selected series before the selection, adjust the selection int selectedRow = m_selectedBar.x(); @@ -154,6 +150,11 @@ void Bars3DController::handleRowsRemoved(int startIndex, int count) } } + if (series->isVisible()) { + adjustAxisRanges(); + m_isDataDirty = true; + } + emitNeedRender(); } @@ -162,11 +163,6 @@ void Bars3DController::handleRowsInserted(int startIndex, int count) Q_UNUSED(startIndex) Q_UNUSED(count) QBar3DSeries *series = static_cast(sender())->series(); - if (series->isVisible()) { - adjustAxisRanges(); - m_isDataDirty = true; - } - if (series == m_selectedBarSeries) { // If rows inserted to selected series before the selection, adjust the selection int selectedRow = m_selectedBar.x(); @@ -176,6 +172,11 @@ void Bars3DController::handleRowsInserted(int startIndex, int count) } } + if (series->isVisible()) { + adjustAxisRanges(); + m_isDataDirty = true; + } + emitNeedRender(); } @@ -244,8 +245,6 @@ void Bars3DController::handlePendingClick() QPoint position = m_renderer->clickedPosition(); QBar3DSeries *series = static_cast(m_renderer->clickedSeries()); - // TODO: Adjust position according to inserts/removes in the series - setSelectedBar(position, series); m_renderer->resetClickedStatus(); diff --git a/src/datavisualization/engine/scatter3dcontroller.cpp b/src/datavisualization/engine/scatter3dcontroller.cpp index 4527ac2a..6aa10a11 100644 --- a/src/datavisualization/engine/scatter3dcontroller.cpp +++ b/src/datavisualization/engine/scatter3dcontroller.cpp @@ -29,11 +29,14 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION +static const int insertRemoveRecordReserveSize = 31; + Scatter3DController::Scatter3DController(QRect boundRect, Q3DScene *scene) : Abstract3DController(boundRect, scene), m_renderer(0), m_selectedItem(invalidSelectionIndex()), - m_selectedItemSeries(0) + m_selectedItemSeries(0), + m_recordInsertsAndRemoves(false) { // Setting a null axis creates a new default axis according to orientation and graph type. // Note: These cannot be set in Abstract3DController constructor, as they will call virtual @@ -62,11 +65,11 @@ void Scatter3DController::initializeOpenGL() void Scatter3DController::synchDataToRenderer() { - Abstract3DController::synchDataToRenderer(); - if (!isInitialized()) return; + Abstract3DController::synchDataToRenderer(); + // Notify changes to renderer if (m_changeTracker.selectedItemChanged) { m_renderer->updateSelectedItem(m_selectedItem, m_selectedItemSeries); @@ -157,11 +160,6 @@ void Scatter3DController::handleItemsRemoved(int startIndex, int count) Q_UNUSED(count) // TODO should dirty only affected values? QScatter3DSeries *series = static_cast(sender())->series(); - if (series->isVisible()) { - adjustValueAxisRange(); - m_isDataDirty = true; - } - if (series == m_selectedItemSeries) { // If items removed from selected series before the selection, adjust the selection int selectedItem = m_selectedItem; @@ -175,6 +173,16 @@ void Scatter3DController::handleItemsRemoved(int startIndex, int count) } } + if (series->isVisible()) { + adjustValueAxisRange(); + m_isDataDirty = true; + } + + if (m_recordInsertsAndRemoves) { + InsertRemoveRecord record(false, startIndex, count, series); + m_insertRemoveRecords.append(record); + } + emitNeedRender(); } @@ -184,11 +192,6 @@ void Scatter3DController::handleItemsInserted(int startIndex, int count) Q_UNUSED(count) // TODO should dirty only affected values? QScatter3DSeries *series = static_cast(sender())->series(); - if (series->isVisible()) { - adjustValueAxisRange(); - m_isDataDirty = true; - } - if (series == m_selectedItemSeries) { // If items inserted to selected series before the selection, adjust the selection int selectedItem = m_selectedItem; @@ -198,9 +201,33 @@ void Scatter3DController::handleItemsInserted(int startIndex, int count) } } + if (series->isVisible()) { + adjustValueAxisRange(); + m_isDataDirty = true; + } + + if (m_recordInsertsAndRemoves) { + InsertRemoveRecord record(true, startIndex, count, series); + m_insertRemoveRecords.append(record); + } + emitNeedRender(); } +void Scatter3DController::startRecordingRemovesAndInserts() +{ + m_recordInsertsAndRemoves = false; + + if (m_scene->selectionQueryPosition() != Q3DScene::invalidSelectionPoint()) { + m_recordInsertsAndRemoves = true; + if (m_insertRemoveRecords.size()) { + m_insertRemoveRecords.clear(); + // Reserve some space for remove/insert records to avoid unnecessary reallocations. + m_insertRemoveRecords.reserve(insertRemoveRecordReserveSize); + } + } +} + void Scatter3DController::handleAxisAutoAdjustRangeChangedInOrientation( QAbstract3DAxis::AxisOrientation orientation, bool autoAdjust) { @@ -229,7 +256,25 @@ void Scatter3DController::handlePendingClick() int index = m_renderer->clickedIndex(); QScatter3DSeries *series = static_cast(m_renderer->clickedSeries()); - // TODO: Adjust position according to inserts/removes in the series + // Adjust position according to recorded events + int recordCount = m_insertRemoveRecords.size(); + if (recordCount) { + for (int i = 0; i < recordCount; i++) { + const InsertRemoveRecord &record = m_insertRemoveRecords.at(i); + if (series == record.m_series && record.m_startIndex <= index) { + if (record.m_isInsert) { + index += record.m_count; + } else { + if ((record.m_startIndex + record.m_count) > index) { + index = -1; // Selected row removed + break; + } else { + index -= record.m_count; // Move selected item down by amount of items removed + } + } + } + } + } setSelectedItem(index, series); diff --git a/src/datavisualization/engine/scatter3dcontroller_p.h b/src/datavisualization/engine/scatter3dcontroller_p.h index 35f4015d..2e9ade44 100644 --- a/src/datavisualization/engine/scatter3dcontroller_p.h +++ b/src/datavisualization/engine/scatter3dcontroller_p.h @@ -62,6 +62,30 @@ private: QScatter3DSeries *m_selectedItemSeries; // Points to the series for which the bar is selected // in single series selection cases. + struct InsertRemoveRecord { + bool m_isInsert; + int m_startIndex; + int m_count; + QAbstract3DSeries *m_series; + + InsertRemoveRecord() : + m_isInsert(false), + m_startIndex(0), + m_count(0), + m_series(0) + {} + + InsertRemoveRecord(bool isInsert, int startIndex, int count, QAbstract3DSeries *series) : + m_isInsert(isInsert), + m_startIndex(startIndex), + m_count(count), + m_series(series) + {} + }; + + QVector m_insertRemoveRecords; + bool m_recordInsertsAndRemoves; + public: explicit Scatter3DController(QRect rect, Q3DScene *scene = 0); ~Scatter3DController(); @@ -93,6 +117,9 @@ public slots: void handleItemsRemoved(int startIndex, int count); void handleItemsInserted(int startIndex, int count); +protected: + virtual void startRecordingRemovesAndInserts(); + private: void adjustValueAxisRange(); diff --git a/src/datavisualization/engine/surface3dcontroller.cpp b/src/datavisualization/engine/surface3dcontroller.cpp index 984f65ba..f0dac44b 100644 --- a/src/datavisualization/engine/surface3dcontroller.cpp +++ b/src/datavisualization/engine/surface3dcontroller.cpp @@ -59,7 +59,6 @@ void Surface3DController::initializeOpenGL() m_renderer = new Surface3DRenderer(this); setRenderer(m_renderer); - synchDataToRenderer(); emitNeedRender(); } @@ -388,11 +387,6 @@ void Surface3DController::handleRowsInserted(int startIndex, int count) Q_UNUSED(startIndex) Q_UNUSED(count) QSurface3DSeries *series = static_cast(sender())->series(); - if (series->isVisible()) { - adjustValueAxisRange(); - m_isDataDirty = true; - } - if (series == m_selectedSeries) { // If rows inserted to selected series before the selection, adjust the selection int selectedRow = m_selectedPoint.x(); @@ -402,6 +396,11 @@ void Surface3DController::handleRowsInserted(int startIndex, int count) } } + if (series->isVisible()) { + adjustValueAxisRange(); + m_isDataDirty = true; + } + emitNeedRender(); } @@ -410,11 +409,6 @@ void Surface3DController::handleRowsRemoved(int startIndex, int count) Q_UNUSED(startIndex) Q_UNUSED(count) QSurface3DSeries *series = static_cast(sender())->series(); - if (series->isVisible()) { - adjustValueAxisRange(); - m_isDataDirty = true; - } - if (series == m_selectedSeries) { // If rows removed from selected series before the selection, adjust the selection int selectedRow = m_selectedPoint.x(); @@ -428,6 +422,11 @@ void Surface3DController::handleRowsRemoved(int startIndex, int count) } } + if (series->isVisible()) { + adjustValueAxisRange(); + m_isDataDirty = true; + } + emitNeedRender(); } -- cgit v1.2.3