From 576050ff96cbf67014313bd7c1f2b475b00dd80c Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Tue, 13 May 2014 12:37:06 +0300 Subject: Multi-match behavior implementation for bar item model proxy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task-number: QTRD-3074 Change-Id: I8e34d2546198a743e0132f0ce201dd38daf7ce7a Reviewed-by: Tomi Korpipää --- src/datavisualization/data/baritemmodelhandler.cpp | 49 ++++++++++++++--- .../data/qitemmodelbardataproxy.cpp | 61 +++++++++++++++++++++- .../data/qitemmodelbardataproxy.h | 13 +++++ .../data/qitemmodelbardataproxy_p.h | 2 + tests/qmlmultitest/qml/qmlmultitest/Data.qml | 21 ++++++++ tests/qmlmultitest/qml/qmlmultitest/main.qml | 32 ++++++++---- 6 files changed, 159 insertions(+), 19 deletions(-) diff --git a/src/datavisualization/data/baritemmodelhandler.cpp b/src/datavisualization/data/baritemmodelhandler.cpp index 4685be44..3d1ce82f 100644 --- a/src/datavisualization/data/baritemmodelhandler.cpp +++ b/src/datavisualization/data/baritemmodelhandler.cpp @@ -17,6 +17,7 @@ ****************************************************************************/ #include "baritemmodelhandler_p.h" +#include QT_BEGIN_NAMESPACE_DATAVISUALIZATION @@ -176,8 +177,17 @@ void BarItemModelHandler::resolveModel() // Sort values into rows and columns typedef QHash ColumnValueMap; - QHash itemValueMap; - QHash itemRotationMap; + QHash itemValueMap; + QHash itemRotationMap; + + bool cumulative = m_proxy->multiMatchBehavior() == QItemModelBarDataProxy::MMBAverage + || m_proxy->multiMatchBehavior() == QItemModelBarDataProxy::MMBCumulative; + bool countMatches = m_proxy->multiMatchBehavior() == QItemModelBarDataProxy::MMBAverage; + bool takeFirst = m_proxy->multiMatchBehavior() == QItemModelBarDataProxy::MMBFirst; + QHash > *matchCountMap = 0; + if (countMatches) + matchCountMap = new QHash >; + for (int i = 0; i < rowCount; i++) { for (int j = 0; j < columnCount; j++) { QModelIndex index = m_itemModel->index(i, j); @@ -193,7 +203,19 @@ void BarItemModelHandler::resolveModel() value = valueVar.toString().replace(m_valuePattern, m_valueReplace).toFloat(); else value = valueVar.toFloat(); - itemValueMap[rowRoleStr][columnRoleStr] = value; + if (countMatches) + (*matchCountMap)[rowRoleStr][columnRoleStr]++; + + if (cumulative) { + itemValueMap[rowRoleStr][columnRoleStr] += value; + } else { + if (takeFirst && itemValueMap.contains(rowRoleStr)) { + if (itemValueMap.value(rowRoleStr).contains(columnRoleStr)) + continue; // We already have a value for this row/column combo + } + itemValueMap[rowRoleStr][columnRoleStr] = value; + } + if (m_rotationRole != noRoleIndex) { QVariant rotationVar = index.data(m_rotationRole); float rotation; @@ -203,7 +225,13 @@ void BarItemModelHandler::resolveModel() } else { rotation = rotationVar.toFloat(); } - itemRotationMap[rowRoleStr][columnRoleStr] = rotation; + if (cumulative) { + itemRotationMap[rowRoleStr][columnRoleStr] += rotation; + } else { + // We know we are in take last mode if we get here, + // as take first mode skips to next loop already earlier + itemRotationMap[rowRoleStr][columnRoleStr] = rotation; + } } if (generateRows && !rowListHash.value(rowRoleStr, false)) { rowListHash.insert(rowRoleStr, true); @@ -239,9 +267,16 @@ void BarItemModelHandler::resolveModel() QString rowKey = rowList.at(i); QBarDataRow &newProxyRow = *m_proxyArray->at(i); for (int j = 0; j < columnList.size(); j++) { - newProxyRow[j].setValue(itemValueMap[rowKey][columnList.at(j)]); - if (m_rotationRole != noRoleIndex) - newProxyRow[j].setRotation(itemRotationMap[rowKey][columnList.at(j)]); + float value = itemValueMap[rowKey][columnList.at(j)]; + if (countMatches) + value /= float((*matchCountMap)[rowKey][columnList.at(j)]); + newProxyRow[j].setValue(value); + if (m_rotationRole != noRoleIndex) { + float angle = itemRotationMap[rowKey][columnList.at(j)]; + if (countMatches) + angle /= float((*matchCountMap)[rowKey][columnList.at(j)]); + newProxyRow[j].setRotation(angle); + } } } diff --git a/src/datavisualization/data/qitemmodelbardataproxy.cpp b/src/datavisualization/data/qitemmodelbardataproxy.cpp index cccf7d44..ba98ced1 100644 --- a/src/datavisualization/data/qitemmodelbardataproxy.cpp +++ b/src/datavisualization/data/qitemmodelbardataproxy.cpp @@ -236,6 +236,37 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION * \sa rotationRole, rotationRolePattern */ +/*! + * \qmlproperty ItemModelBarDataProxy.MultiMatchBehavior ItemModelBarDataProxy::multiMatchBehavior + * This property defines how multiple matches for each row/column combination are handled. + * Defaults to ItemModelBarDataProxy.MMBLast. The chosen behavior affects both bar value + * and rotation. + * + * For example, you might have an item model with timestamped data taken at irregular intervals + * and you want to visualize total value of data items on each day with a bar graph. + * This can be done by specifying row and column categories so that each bar represents a day, + * and setting multiMatchBehavior to ItemModelBarDataProxy.MMBCumulative. + */ + +/*! + * \enum QItemModelBarDataProxy::MultiMatchBehavior + * + * Behavior types for QItemModelBarDataProxy::multiMatchBehavior property. + * + * \value MMBFirst + * The value is taken from the first item in the item model that matches + * each row/column combination. + * \value MMBLast + * The value is taken from the last item in the item model that matches + * each row/column combination. + * \value MMBAverage + * The values from all items matching each row/column combination are + * averaged together and the average is used as the bar value. + * \value MMBCumulative + * The values from all items matching each row/column combination are + * added together and the total is used as the bar value. + */ + /*! * Constructs QItemModelBarDataProxy with optional \a parent. */ @@ -783,6 +814,31 @@ QString QItemModelBarDataProxy::rotationRoleReplace() const return dptrc()->m_rotationRoleReplace; } +/*! + * \property QItemModelBarDataProxy::multiMatchBehavior + * + * This property defines how multiple matches for each row/column combination are handled. + * Defaults to QItemModelBarDataProxy::MMBLast. The chosen behavior affects both bar value + * and rotation. + * + * For example, you might have an item model with timestamped data taken at irregular intervals + * and you want to visualize total value of data items on each day with a bar graph. + * This can be done by specifying row and column categories so that each bar represents a day, + * and setting multiMatchBehavior to QItemModelBarDataProxy::MMBCumulative. + */ +void QItemModelBarDataProxy::setMultiMatchBehavior(QItemModelBarDataProxy::MultiMatchBehavior behavior) +{ + if (dptr()->m_multiMatchBehavior != behavior) { + dptr()->m_multiMatchBehavior = behavior; + emit multiMatchBehaviorChanged(behavior); + } +} + +QItemModelBarDataProxy::MultiMatchBehavior QItemModelBarDataProxy::multiMatchBehavior() const +{ + return dptrc()->m_multiMatchBehavior; +} + /*! * \internal */ @@ -806,7 +862,8 @@ QItemModelBarDataProxyPrivate::QItemModelBarDataProxyPrivate(QItemModelBarDataPr m_itemModelHandler(new BarItemModelHandler(q)), m_useModelCategories(false), m_autoRowCategories(true), - m_autoColumnCategories(true) + m_autoColumnCategories(true), + m_multiMatchBehavior(QItemModelBarDataProxy::MMBLast) { } @@ -858,6 +915,8 @@ void QItemModelBarDataProxyPrivate::connectItemModelHandler() m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged); QObject::connect(qptr(), &QItemModelBarDataProxy::rotationRoleReplaceChanged, m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged); + QObject::connect(qptr(), &QItemModelBarDataProxy::multiMatchBehaviorChanged, + m_itemModelHandler, &AbstractItemModelHandler::handleMappingChanged); } QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/data/qitemmodelbardataproxy.h b/src/datavisualization/data/qitemmodelbardataproxy.h index ad7be3a7..317befdc 100644 --- a/src/datavisualization/data/qitemmodelbardataproxy.h +++ b/src/datavisualization/data/qitemmodelbardataproxy.h @@ -30,6 +30,7 @@ class QItemModelBarDataProxyPrivate; class QT_DATAVISUALIZATION_EXPORT QItemModelBarDataProxy : public QBarDataProxy { Q_OBJECT + Q_ENUMS(MultiMatchBehavior) Q_PROPERTY(const QAbstractItemModel* itemModel READ itemModel WRITE setItemModel NOTIFY itemModelChanged) Q_PROPERTY(QString rowRole READ rowRole WRITE setRowRole NOTIFY rowRoleChanged) Q_PROPERTY(QString columnRole READ columnRole WRITE setColumnRole NOTIFY columnRoleChanged) @@ -48,8 +49,16 @@ class QT_DATAVISUALIZATION_EXPORT QItemModelBarDataProxy : public QBarDataProxy Q_PROPERTY(QString columnRoleReplace READ columnRoleReplace WRITE setColumnRoleReplace NOTIFY columnRoleReplaceChanged REVISION 1) Q_PROPERTY(QString valueRoleReplace READ valueRoleReplace WRITE setValueRoleReplace NOTIFY valueRoleReplaceChanged REVISION 1) Q_PROPERTY(QString rotationRoleReplace READ rotationRoleReplace WRITE setRotationRoleReplace NOTIFY rotationRoleReplaceChanged REVISION 1) + Q_PROPERTY(MultiMatchBehavior multiMatchBehavior READ multiMatchBehavior WRITE setMultiMatchBehavior NOTIFY multiMatchBehaviorChanged REVISION 1) public: + enum MultiMatchBehavior { + MMBFirst = 0, + MMBLast = 1, + MMBAverage = 2, + MMBCumulative = 3 + }; + explicit QItemModelBarDataProxy(QObject *parent = 0); QItemModelBarDataProxy(const QAbstractItemModel *itemModel, QObject *parent = 0); QItemModelBarDataProxy(const QAbstractItemModel *itemModel, const QString &valueRole, @@ -120,6 +129,9 @@ public: void setRotationRoleReplace(const QString &replace); QString rotationRoleReplace() const; + void setMultiMatchBehavior(MultiMatchBehavior behavior); + MultiMatchBehavior multiMatchBehavior() const; + signals: void itemModelChanged(const QAbstractItemModel* itemModel); void rowRoleChanged(const QString &role); @@ -139,6 +151,7 @@ signals: Q_REVISION(1) void columnRoleReplaceChanged(const QString &replace); Q_REVISION(1) void valueRoleReplaceChanged(const QString &replace); Q_REVISION(1) void rotationRoleReplaceChanged(const QString &replace); + Q_REVISION(1) void multiMatchBehaviorChanged(MultiMatchBehavior behavior); protected: QItemModelBarDataProxyPrivate *dptr(); diff --git a/src/datavisualization/data/qitemmodelbardataproxy_p.h b/src/datavisualization/data/qitemmodelbardataproxy_p.h index 3e8fa3ae..84564d02 100644 --- a/src/datavisualization/data/qitemmodelbardataproxy_p.h +++ b/src/datavisualization/data/qitemmodelbardataproxy_p.h @@ -73,6 +73,8 @@ private: QString m_valueRoleReplace; QString m_rotationRoleReplace; + QItemModelBarDataProxy::MultiMatchBehavior m_multiMatchBehavior; + friend class BarItemModelHandler; friend class QItemModelBarDataProxy; }; diff --git a/tests/qmlmultitest/qml/qmlmultitest/Data.qml b/tests/qmlmultitest/qml/qmlmultitest/Data.qml index ddc0aad8..2ef168da 100644 --- a/tests/qmlmultitest/qml/qmlmultitest/Data.qml +++ b/tests/qmlmultitest/qml/qmlmultitest/Data.qml @@ -43,6 +43,27 @@ Item { ListElement{ coords: "1,4"; data: "21.3/14.6/5.83"; } ListElement{ coords: "2,4"; data: "22.5/14.8/7.32"; } ListElement{ coords: "3,4"; data: "23.7/14.3/6.90"; } + + ListElement{ coords: "0,0"; data: "40.0/30.0/14.75"; } + ListElement{ coords: "1,0"; data: "41.1/30.3/13.00"; } + ListElement{ coords: "2,0"; data: "42.5/30.7/11.24"; } + ListElement{ coords: "3,0"; data: "44.0/30.5/12.53"; } + ListElement{ coords: "0,1"; data: "40.2/31.2/13.55"; } + ListElement{ coords: "1,1"; data: "41.3/31.5/13.03"; } + ListElement{ coords: "2,1"; data: "42.6/31.7/13.46"; } + ListElement{ coords: "3,1"; data: "43.4/31.5/14.12"; } + ListElement{ coords: "0,2"; data: "40.2/32.3/13.37"; } + ListElement{ coords: "1,2"; data: "41.1/32.4/12.98"; } + ListElement{ coords: "2,2"; data: "42.5/32.1/13.33"; } + ListElement{ coords: "3,2"; data: "43.3/32.7/13.23"; } + ListElement{ coords: "0,3"; data: "40.7/33.3/15.34"; } + ListElement{ coords: "1,3"; data: "41.5/33.2/14.54"; } + ListElement{ coords: "2,3"; data: "42.4/33.6/14.65"; } + ListElement{ coords: "3,3"; data: "43.2/33.4/16.67"; } + ListElement{ coords: "0,4"; data: "40.6/35.0/16.01"; } + ListElement{ coords: "1,4"; data: "41.3/34.6/15.83"; } + ListElement{ coords: "2,4"; data: "42.5/34.8/17.32"; } + ListElement{ coords: "3,4"; data: "43.7/34.3/16.90"; } } } diff --git a/tests/qmlmultitest/qml/qmlmultitest/main.qml b/tests/qmlmultitest/qml/qmlmultitest/main.qml index 84eb4294..b5a62902 100644 --- a/tests/qmlmultitest/qml/qmlmultitest/main.qml +++ b/tests/qmlmultitest/qml/qmlmultitest/main.qml @@ -59,6 +59,7 @@ Rectangle { Surface3DSeries { itemLabelFormat: "Pop density at (@xLabel N, @zLabel E): @yLabel" ItemModelSurfaceDataProxy { + id: surfaceProxy itemModel: data.sharedData // The surface data points are not neatly lined up in rows and columns, // so we define explicit row and column roles. @@ -118,10 +119,11 @@ Rectangle { } NewButton { + id: mmbButton Layout.fillHeight: true Layout.fillWidth: true - text: "Toggle Mesh Styles" - onClicked: toggleMeshStyle() // call a helper function to keep button itself simpler + text: "MMB: Last" + onClicked: changeMMB() // call a helper function to keep button itself simpler } } } @@ -146,6 +148,7 @@ Rectangle { itemLabelFormat: "Pop density at (@xLabel N, @zLabel E): @yLabel" mesh: Abstract3DSeries.MeshCube ItemModelScatterDataProxy { + id: scatterProxy itemModel: data.sharedData // Mapping model roles to scatter series item coordinates. xPosRole: "data" @@ -187,17 +190,21 @@ Rectangle { name: "Population density" ItemModelBarDataProxy { + id: barProxy itemModel: data.sharedData // Mapping model roles to bar series rows, columns, and values. rowRole: "coords" columnRole: "coords" valueRole: "data" + rotationRole: "coords" rowRolePattern: /(\d),\d/ columnRolePattern: /(\d),(\d)/ valueRolePattern: /^([^\/]*)\/([^\/]*)\/(.*)$/ + rotationRolePattern: /(\d)\,(\d)/ rowRoleReplace: "\\1" columnRoleReplace: "\\2" valueRoleReplace: "\\3" + rotationRoleReplace: "\\2\\1" } } } @@ -219,16 +226,19 @@ Rectangle { barGraph.scene.activeCamera.zoomLevel = 100.0 } - function toggleMeshStyle() { - if (barGraph.seriesList[0].meshSmooth === true) { - barGraph.seriesList[0].meshSmooth = false - if (surfaceGraph.seriesList[0].flatShadingSupported) - surfaceGraph.seriesList[0].flatShadingEnabled = true - scatterGraph.seriesList[0].meshSmooth = false + function changeMMB() { + if (barProxy.multiMatchBehavior === ItemModelBarDataProxy.MMBLast) { + barProxy.multiMatchBehavior = ItemModelBarDataProxy.MMBAverage + mmbButton.text = "MMB: Average" + } else if (barProxy.multiMatchBehavior === ItemModelBarDataProxy.MMBAverage) { + barProxy.multiMatchBehavior = ItemModelBarDataProxy.MMBCumulative + mmbButton.text = "MMB: Cumulative" + } else if (barProxy.multiMatchBehavior === ItemModelBarDataProxy.MMBCumulative) { + barProxy.multiMatchBehavior = ItemModelBarDataProxy.MMBFirst + mmbButton.text = "MMB: First" } else { - barGraph.seriesList[0].meshSmooth = true - surfaceGraph.seriesList[0].flatShadingEnabled = false - scatterGraph.seriesList[0].meshSmooth = true + barProxy.multiMatchBehavior = ItemModelBarDataProxy.MMBLast + mmbButton.text = "MMB: Last" } } } -- cgit v1.2.3