summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--examples/itemmodel/main.cpp9
-rw-r--r--src/datavisualization/data/abstractitemmodelhandler.cpp85
-rw-r--r--src/datavisualization/data/abstractitemmodelhandler_p.h1
-rw-r--r--src/datavisualization/data/qitemmodelscatterdataproxy.cpp4
-rw-r--r--src/datavisualization/data/scatteritemmodelhandler.cpp95
-rw-r--r--src/datavisualization/data/scatteritemmodelhandler_p.h12
-rw-r--r--src/datavisualization/engine/abstract3dcontroller.cpp12
-rw-r--r--src/datavisualization/engine/abstract3dcontroller_p.h3
-rw-r--r--src/datavisualization/engine/bars3dcontroller.cpp29
-rw-r--r--src/datavisualization/engine/scatter3dcontroller.cpp73
-rw-r--r--src/datavisualization/engine/scatter3dcontroller_p.h27
-rw-r--r--src/datavisualization/engine/surface3dcontroller.cpp21
-rw-r--r--tests/barstest/barstest.pro4
-rw-r--r--tests/barstest/chart.cpp74
-rw-r--r--tests/barstest/chart.h10
-rw-r--r--tests/barstest/custominputhandler.cpp54
-rw-r--r--tests/barstest/custominputhandler.h36
-rw-r--r--tests/barstest/main.cpp7
-rw-r--r--tests/qmldynamicdata/qml/qmldynamicdata/main.qml22
19 files changed, 480 insertions, 98 deletions
diff --git a/examples/itemmodel/main.cpp b/examples/itemmodel/main.cpp
index 46824993..bdecd1a5 100644
--- a/examples/itemmodel/main.cpp
+++ b/examples/itemmodel/main.cpp
@@ -84,17 +84,18 @@ GraphDataGenerator::GraphDataGenerator(Q3DBars *bargraph, QTableWidget *tableWid
#ifndef USE_STATIC_DATA
// Set up sample space; make it as deep as it's wide
- m_graph->setDataWindow(m_rowCount, m_columnCount);
+ m_graph->rowAxis()->setRange(0, m_rowCount);
+ m_graph->columnAxis()->setRange(0, m_columnCount);
m_tableWidget->setColumnCount(m_columnCount);
// Set selection mode to full
m_graph->setSelectionMode(QAbstract3DGraph::SelectionItemRowAndColumn);
// Hide axis labels by explicitly setting one empty string as label list
- m_graph->rowAxis()->setCategoryLabels(QStringList(QString()));
- m_graph->columnAxis()->setCategoryLabels(QStringList(QString()));
+ m_graph->rowAxis()->setLabels(QStringList(QString()));
+ m_graph->columnAxis()->setLabels(QStringList(QString()));
- m_graph->activeDataProxy()->setItemLabelFormat(QStringLiteral("@valueLabel"));
+ m_graph->seriesList().at(0)->setItemLabelFormat(QStringLiteral("@valueLabel"));
#else
//! [6]
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<QPersistentModelIndex> &parents,
@@ -134,16 +148,20 @@ void AbstractItemModelHandler::handleLayoutChanged(const QList<QPersistentModelI
Q_UNUSED(parents)
Q_UNUSED(hint)
- // Resolve moved items
- if (!m_resolveTimer.isActive())
- m_resolveTimer.start(0); // TODO Resolving entire model is inefficient
+ // Resolve entire model if layout changes
+ if (!m_resolveTimer.isActive()) {
+ m_fullReset = true;
+ m_resolveTimer.start(0);
+ }
}
void AbstractItemModelHandler::handleModelReset()
{
// Data cleared, reset array
- if (!m_resolveTimer.isActive())
- m_resolveTimer.start(0); // TODO Resolving entire model is inefficient
+ if (!m_resolveTimer.isActive()) {
+ m_fullReset = true;
+ m_resolveTimer.start(0);
+ }
}
void AbstractItemModelHandler::handleRowsInserted(const QModelIndex &parent, int start, int end)
@@ -152,9 +170,13 @@ void AbstractItemModelHandler::handleRowsInserted(const QModelIndex &parent, int
Q_UNUSED(start)
Q_UNUSED(end)
- // Resolve new items
- if (!m_resolveTimer.isActive())
- m_resolveTimer.start(0); // TODO Resolving entire model is inefficient
+ // Default handling for rowsInserted 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::handleRowsMoved(const QModelIndex &sourceParent,
@@ -169,9 +191,13 @@ void AbstractItemModelHandler::handleRowsMoved(const QModelIndex &sourceParent,
Q_UNUSED(destinationParent)
Q_UNUSED(destinationRow)
- // Resolve moved items
- if (!m_resolveTimer.isActive())
- m_resolveTimer.start(0); // TODO Resolving entire model is inefficient
+ // Default handling for rowsMoved 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::handleRowsRemoved(const QModelIndex &parent, int start, int end)
@@ -180,9 +206,13 @@ void AbstractItemModelHandler::handleRowsRemoved(const QModelIndex &parent, int
Q_UNUSED(start)
Q_UNUSED(end)
- // Resolve removed items
- if (!m_resolveTimer.isActive())
- m_resolveTimer.start(0); // TODO Resolving entire model is inefficient
+ // Default handling for rowsRemoved 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::handleMappingChanged()
@@ -194,6 +224,7 @@ void AbstractItemModelHandler::handleMappingChanged()
void AbstractItemModelHandler::handlePendingResolve()
{
resolveModel();
+ m_fullReset = false;
}
QT_END_NAMESPACE_DATAVISUALIZATION
diff --git a/src/datavisualization/data/abstractitemmodelhandler_p.h b/src/datavisualization/data/abstractitemmodelhandler_p.h
index f3b72228..daaf9906 100644
--- a/src/datavisualization/data/abstractitemmodelhandler_p.h
+++ b/src/datavisualization/data/abstractitemmodelhandler_p.h
@@ -74,6 +74,7 @@ protected:
QPointer<const QAbstractItemModel> 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<int> &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<int, QByteArray> 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<int> &roles = QVector<int> ());
+ 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 <QThread>
@@ -147,9 +148,7 @@ QList<QAbstract3DSeries *> 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<QAbstract3DInputHandler *> 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<QBarDataProxy *>(sender())->series();
- if (series->isVisible()) {
- adjustAxisRanges();
- m_isDataDirty = true;
- }
+ QBar3DSeries *series = static_cast<QBarDataProxy *>(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<QBarDataProxy *>(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<QBar3DSeries *>(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<QScatterDataProxy *>(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<QScatterDataProxy *>(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<QScatter3DSeries *>(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<InsertRemoveRecord> 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<QSurfaceDataProxy *>(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<QSurfaceDataProxy *>(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();
}
diff --git a/tests/barstest/barstest.pro b/tests/barstest/barstest.pro
index 55fbefbd..108f8aa7 100644
--- a/tests/barstest/barstest.pro
+++ b/tests/barstest/barstest.pro
@@ -2,8 +2,8 @@
error( "Couldn't find the tests.pri file!" )
}
-SOURCES += main.cpp chart.cpp
-HEADERS += chart.h
+SOURCES += main.cpp chart.cpp custominputhandler.cpp
+HEADERS += chart.h custominputhandler.h
QT += widgets
diff --git a/tests/barstest/chart.cpp b/tests/barstest/chart.cpp
index 7f049645..abbf2a59 100644
--- a/tests/barstest/chart.cpp
+++ b/tests/barstest/chart.cpp
@@ -17,12 +17,14 @@
****************************************************************************/
#include "chart.h"
+#include "custominputhandler.h"
#include <QtDataVisualization/qcategory3daxis.h>
#include <QtDataVisualization/qvalue3daxis.h>
#include <QtDataVisualization/qbardataproxy.h>
#include <QtDataVisualization/q3dscene.h>
#include <QtDataVisualization/q3dcamera.h>
#include <QtDataVisualization/q3dtheme.h>
+#include <QtDataVisualization/q3dinputhandler.h>
#include <QTime>
using namespace QtDataVisualization;
@@ -65,7 +67,8 @@ GraphModifier::GraphModifier(Q3DBars *barchart, QColorDialog *colorDialog)
m_useNullInputHandler(false),
m_defaultInputHandler(0),
m_ownTheme(0),
- m_builtinTheme(new Q3DTheme(Q3DTheme::ThemeStoneMoss))
+ m_builtinTheme(new Q3DTheme(Q3DTheme::ThemeStoneMoss)),
+ m_customInputHandler(new CustomInputHandler)
{
m_temperatureData->setObjectName("m_temperatureData");
m_temperatureData2->setObjectName("m_temperatureData2");
@@ -218,9 +221,15 @@ GraphModifier::GraphModifier(Q3DBars *barchart, QColorDialog *colorDialog)
QObject::connect(m_graph, &Q3DBars::primarySeriesChanged, this,
&GraphModifier::handlePrimarySeriesChanged);
+ QObject::connect(&m_insertRemoveTimer, &QTimer::timeout, this,
+ &GraphModifier::insertRemoveTimerTimeout);
+
m_graph->addSeries(m_temperatureData);
m_graph->addSeries(m_temperatureData2);
+ QObject::connect(&m_selectionTimer, &QTimer::timeout, this,
+ &GraphModifier::triggerSelection);
+
resetTemperatureData();
}
@@ -994,6 +1003,69 @@ void GraphModifier::primarySeriesTest()
}
+void GraphModifier::insertRemoveTestToggle()
+{
+ if (m_insertRemoveTimer.isActive()) {
+ m_insertRemoveTimer.stop();
+ m_selectionTimer.stop();
+ m_graph->removeSeries(m_dummyData);
+ m_graph->removeSeries(m_dummyData2);
+ releaseProxies();
+ releaseAxes();
+ m_graph->setActiveInputHandler(m_defaultInputHandler);
+ } else {
+ releaseProxies();
+ releaseAxes();
+ m_graph->rowAxis()->setRange(0, 32);
+ m_graph->columnAxis()->setRange(0, 10);
+ m_graph->setActiveInputHandler(m_customInputHandler);
+ m_graph->addSeries(m_dummyData);
+ m_graph->addSeries(m_dummyData2);
+ m_insertRemoveStep = 0;
+ m_insertRemoveTimer.start(100);
+ m_selectionTimer.start(10);
+ }
+}
+
+void GraphModifier::insertRemoveTimerTimeout()
+{
+ if (m_insertRemoveStep < 32) {
+ for (int k = 0; k < 1; k++) {
+ QBarDataRow *dataRow = new QBarDataRow(10);
+ for (float i = 0; i < 10; i++)
+ (*dataRow)[i].setValue(((i + 1) / 10.0f) * (float)(rand() % 100));
+
+ QString label = QStringLiteral("Insert %1").arg(insertCounter++);
+ m_dummyData->dataProxy()->insertRow(0, dataRow, label);
+ }
+ } else {
+ for (int k = 0; k < 1; k++)
+ m_dummyData->dataProxy()->removeRows(0, 1);
+ }
+
+ if (m_insertRemoveStep < 16 || (m_insertRemoveStep > 31 && m_insertRemoveStep < 48)) {
+ for (int k = 0; k < 2; k++) {
+ QBarDataRow *dataRow = new QBarDataRow(10);
+ for (float i = 0; i < 10; i++)
+ (*dataRow)[i].setValue(((i + 1) / 10.0f) * (float)(rand() % 100));
+
+ QString label = QStringLiteral("Insert %1").arg(insertCounter++);
+ m_dummyData2->dataProxy()->insertRow(0, dataRow, label);
+ }
+ } else {
+ for (int k = 0; k < 2; k++)
+ m_dummyData2->dataProxy()->removeRows(0, 1);
+ }
+
+ if (m_insertRemoveStep++ > 63)
+ m_insertRemoveStep = 0;
+}
+
+void GraphModifier::triggerSelection()
+{
+ m_graph->scene()->setSelectionQueryPosition(m_customInputHandler->inputPosition());
+}
+
void GraphModifier::setBackgroundEnabled(int enabled)
{
m_graph->activeTheme()->setBackgroundEnabled(bool(enabled));
diff --git a/tests/barstest/chart.h b/tests/barstest/chart.h
index 703f53e5..425af521 100644
--- a/tests/barstest/chart.h
+++ b/tests/barstest/chart.h
@@ -22,11 +22,13 @@
#include <QtDataVisualization/q3dbars.h>
#include <QtDataVisualization/qabstract3dinputhandler.h>
#include <QtDataVisualization/qbar3dseries.h>
+#include <QtDataVisualization/q3dtheme.h>
#include <QFont>
#include <QDebug>
#include <QStringList>
#include <QPointer>
#include <QColorDialog>
+#include <QTimer>
using namespace QtDataVisualization;
@@ -81,6 +83,7 @@ public:
void showFiveSeries();
QBarDataArray *makeDummyData();
void primarySeriesTest();
+ void insertRemoveTestToggle();
public slots:
void flipViews();
@@ -95,6 +98,9 @@ public slots:
void handleValueAxisChanged(QValue3DAxis *axis);
void handlePrimarySeriesChanged(QBar3DSeries *series);
+ void insertRemoveTimerTimeout();
+ void triggerSelection();
+
signals:
void shadowQualityChanged(int quality);
@@ -137,6 +143,10 @@ private:
QAbstract3DInputHandler *m_defaultInputHandler;
Q3DTheme *m_ownTheme;
Q3DTheme *m_builtinTheme;
+ QTimer m_insertRemoveTimer;
+ int m_insertRemoveStep;
+ QAbstract3DInputHandler *m_customInputHandler;
+ QTimer m_selectionTimer;
};
#endif
diff --git a/tests/barstest/custominputhandler.cpp b/tests/barstest/custominputhandler.cpp
new file mode 100644
index 00000000..3b050fda
--- /dev/null
+++ b/tests/barstest/custominputhandler.cpp
@@ -0,0 +1,54 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the QtDataVisualization module.
+**
+** Licensees holding valid Qt Enterprise licenses may use this file in
+** accordance with the Qt Enterprise License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+**
+****************************************************************************/
+
+#include "custominputhandler.h"
+
+#include <QtDataVisualization/Q3DCamera>
+
+CustomInputHandler::CustomInputHandler(QObject *parent) :
+ QAbstract3DInputHandler(parent)
+{
+}
+
+//! [0]
+void CustomInputHandler::mouseMoveEvent(QMouseEvent *event, const QPoint &mousePos)
+{
+ Q_UNUSED(event)
+ setInputPosition(mousePos);
+}
+//! [0]
+
+//! [1]
+void CustomInputHandler::wheelEvent(QWheelEvent *event)
+{
+ // Adjust zoom level based on what zoom range we're in.
+ int zoomLevel = scene()->activeCamera()->zoomLevel();
+ if (zoomLevel > 100)
+ zoomLevel += event->angleDelta().y() / 12;
+ else if (zoomLevel > 50)
+ zoomLevel += event->angleDelta().y() / 60;
+ else
+ zoomLevel += event->angleDelta().y() / 120;
+ if (zoomLevel > 500)
+ zoomLevel = 500;
+ else if (zoomLevel < 10)
+ zoomLevel = 10;
+
+ scene()->activeCamera()->setZoomLevel(zoomLevel);
+}
+//! [1]
diff --git a/tests/barstest/custominputhandler.h b/tests/barstest/custominputhandler.h
new file mode 100644
index 00000000..1fecb8d8
--- /dev/null
+++ b/tests/barstest/custominputhandler.h
@@ -0,0 +1,36 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the QtDataVisualization module.
+**
+** Licensees holding valid Qt Enterprise licenses may use this file in
+** accordance with the Qt Enterprise License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+**
+****************************************************************************/
+
+#ifndef CUSTOMINPUTHANDLER_H
+#define CUSTOMINPUTHANDLER_H
+
+#include <QtDataVisualization/QAbstract3DInputHandler>
+
+using namespace QtDataVisualization;
+
+class CustomInputHandler : public QAbstract3DInputHandler
+{
+ Q_OBJECT
+public:
+ explicit CustomInputHandler(QObject *parent = 0);
+
+ virtual void mouseMoveEvent(QMouseEvent *event, const QPoint &mousePos);
+ virtual void wheelEvent(QWheelEvent *event);
+};
+
+#endif
diff --git a/tests/barstest/main.cpp b/tests/barstest/main.cpp
index a0f9d783..a0e1bfc3 100644
--- a/tests/barstest/main.cpp
+++ b/tests/barstest/main.cpp
@@ -126,6 +126,10 @@ int main(int argc, char **argv)
swapAxisButton->setText(QStringLiteral("Swap value axis"));
swapAxisButton->setEnabled(false);
+ QPushButton *insertRemoveTestButton = new QPushButton(widget);
+ insertRemoveTestButton->setText(QStringLiteral("Toggle insert/remove cycle"));
+ insertRemoveTestButton->setEnabled(true);
+
QPushButton *releaseAxesButton = new QPushButton(widget);
releaseAxesButton->setText(QStringLiteral("Release all axes"));
releaseAxesButton->setEnabled(true);
@@ -293,6 +297,7 @@ int main(int argc, char **argv)
vLayout->addWidget(selectionButton, 0, Qt::AlignTop);
vLayout->addWidget(setSelectedBarButton, 0, Qt::AlignTop);
vLayout->addWidget(swapAxisButton, 0, Qt::AlignTop);
+ vLayout->addWidget(insertRemoveTestButton, 0, Qt::AlignTop);
vLayout->addWidget(releaseAxesButton, 0, Qt::AlignTop);
vLayout->addWidget(releaseProxiesButton, 1, Qt::AlignTop);
vLayout->addWidget(flipViewsButton, 0, Qt::AlignTop);
@@ -385,6 +390,8 @@ int main(int argc, char **argv)
&GraphModifier::selectBar);
QObject::connect(swapAxisButton, &QPushButton::clicked, modifier,
&GraphModifier::swapAxis);
+ QObject::connect(insertRemoveTestButton, &QPushButton::clicked, modifier,
+ &GraphModifier::insertRemoveTestToggle);
QObject::connect(releaseAxesButton, &QPushButton::clicked, modifier,
&GraphModifier::releaseAxes);
QObject::connect(releaseProxiesButton, &QPushButton::clicked, modifier,
diff --git a/tests/qmldynamicdata/qml/qmldynamicdata/main.qml b/tests/qmldynamicdata/qml/qmldynamicdata/main.qml
index 4ed3b8ae..3ad454d0 100644
--- a/tests/qmldynamicdata/qml/qmldynamicdata/main.qml
+++ b/tests/qmldynamicdata/qml/qmldynamicdata/main.qml
@@ -34,7 +34,7 @@ Item {
Timer {
id: dataTimer
- interval: 20
+ interval: 1
running: true
repeat: true
property bool isIncreasing: true
@@ -42,7 +42,16 @@ Item {
onTriggered: {
if (isIncreasing) {
graphModel.append({"xPos": Math.random(), "yPos": Math.random(), "zPos": Math.random()});
- if (graphModel.count == 500) {
+ graphModel.append({"xPos": Math.random(), "yPos": Math.random(), "zPos": Math.random()});
+ graphModel.append({"xPos": Math.random(), "yPos": Math.random(), "zPos": Math.random()});
+ graphModel.append({"xPos": Math.random(), "yPos": Math.random(), "zPos": Math.random()});
+ graphModel.append({"xPos": Math.random(), "yPos": Math.random(), "zPos": Math.random()});
+ graphModel.append({"xPos": Math.random(), "yPos": Math.random(), "zPos": Math.random()});
+ graphModel.append({"xPos": Math.random(), "yPos": Math.random(), "zPos": Math.random()});
+ graphModel.append({"xPos": Math.random(), "yPos": Math.random(), "zPos": Math.random()});
+ graphModel.append({"xPos": Math.random(), "yPos": Math.random(), "zPos": Math.random()});
+ graphModel.append({"xPos": Math.random(), "yPos": Math.random(), "zPos": Math.random()});
+ if (graphModel.count > 5000) {
scatterGraph.theme.type = Theme3D.ThemeIsabelle;
isIncreasing = false;
}
@@ -50,6 +59,15 @@ Item {
// TODO: Once QTRD-2645 is fixed, change this to remove from
// random index to add coverage.
graphModel.remove(2);
+ graphModel.remove(2);
+ graphModel.remove(2);
+ graphModel.remove(2);
+ graphModel.remove(2);
+ graphModel.remove(2);
+ graphModel.remove(2);
+ graphModel.remove(2);
+ graphModel.remove(2);
+ graphModel.remove(2);
if (graphModel.count == 2) {
scatterGraph.theme.type = Theme3D.ThemeDigia;
isIncreasing = true;