This entry describes the code for integrating FacetZoom into an application in order to arrange discrete data (e.g. photos) according to a non time-based facet (e.g. location, person, etc.).
Internally, the FacetZoom widget utilizes the Qt QGraphicsView class for visualization. Consequently, it offers methods in its interface which are best suited for data visualizations which make use of QGraphicsView as well. The interface class is called DataPlacementControlInterface.
To receive a notification every time the FacetZoom widget is modified and the data visualization needs to be updated, a placementChangedSignal is emitted on the FacetZoom widget. This signal can be connected to only on the FacetZoom class as Qt does not permit offering Signals in virtual (interface) classes. Therefore, we can connect a customViewInstance subclass of QGraphicsView to a FacetZoom instance fzInstance as follows:
connect(fzInstance, SIGNAL(placementChangedSignal(DataPlacementControlInterface*)), vizInstance, SLOT(dpcChangedSlot(DataPlacementControlInterface*)));
Now, every time dpcChangedSlot is invoked by an emission of placementChangedSignal, the vizInstance receives a pointer to the emitting FacetZoom widget along with the call. This way, the visualization instance is able to retrieve the information needed to arrange the data in its view.
Arranging items in the visualization
As FacetZoom is datatype-independent, each cell in a FacetZoom instance is identified by an abstract unique number (uniqueness is guaranteed across all levels within the instance). Therefore, a mapping from the metadata values, which were used to construct the FacetZoom instance, to the cell identifiers can be retrieved. Using these identifiers, information on the position and extend of cells can be retrieved. Assuming that the DataPlacementControlInterface is declared as dpc in the dpcChangedSlot method of the visualization class, the code for retrieving the mapping would be as follows:
// mapping of metadata values to cell IDs on each FacetZoom level QMap<int, QMap<ComparableVariant, ulong> > levelAndValueToIdMap = dpc->getLevelAndValueToIdMap(); const int level = dpc->getDominantLevel(); // metadata to cell ID mapping on the focused level QMap<ComparableVariant, ulong> valueToIdMap = levelAndValueToIdMap[level];
The ComparableVariant class extends Qt’s QVariant with a < comparison operator. For non time-based metadata, a ComparableVariant is constructed from a QString corresponding to a cell in the FacetZoom instance. With these mappings, the cell ID and thus position and extent corresponding to an item’s (e.g. photo’s) metadata can be retrieved and applied to a QGraphicsItem item in the visualization:
const ulong cellId = valueToIdMap.value(facetValue); QRectF dataSceneRect = dpc->toSceneRect(cellId); // set the item's position item->setPos(dataSceneRect.x(), item->y()); // set the item's scale item->scale(dataSceneRect.width() / item->width(), 1);
Updating the visualization view
With the items placed according to the FacetZoom instance, the data visualization’s view needs to be realigned to match the FacetZoom widget’s view. This is done by matching the screen position of the visualization view’s scene origin to the FacetZoom view’s scene origin. The DataPlacementControlInterface provides methods for retrieving the required information. However, due to a limitation of Qt’s QGraphicsView implementation (views are automatically constrained to their scene’s sceneRect()), the visualization view’s scene rectangle must be first grown to at least the size of the FacetZoom’s scene rectangle.
// grow visualization scene this->setSceneRect(this->sceneRect() | dpc->getSceneRect()); // get screen coordinate of the dpc's scene position 0 float dpcViewPos = dpc->getViewOriginPosX(); QTransform vizmatTrans = this->transform(); // calculate new view position from difference to dpc dpcViewPos = vizmatTrans.dx() + (dpcViewPos - mapFromScene(0,0).rx()); // update the view's transformation matrix vizmatTrans.setMatrix( vizmatTrans.m11(), vizmatTrans.m12(), vizmatTrans.m13(), vizmatTrans.m21(), vizmatTrans.m22(), vizmatTrans.m23(), dpcViewPos, vizmatTrans.dy(), vizmatTrans.m33()); // apply the transformation matrix this->setTransform(vizmatTrans);
The resulting application could then look as follows:
For any implementation questions or suggestions, please leave a comment below.