From 2dd0ce937551fe15e09df535e099ac9b35772b78 Mon Sep 17 00:00:00 2001 From: Alexander Vieth Date: Fri, 29 Aug 2025 09:31:54 +0200 Subject: [PATCH 1/6] Color by selection mapping (#186) * WIP: color by selection mapping * Add explanations * Delay check * Better be safe * Add Reverse mapping as well * Fix false parent * Move new functions to own file * Centralize & inline check * Simpler return types and checks * Single swap * WIP: map means to HSNE embedding * Fix mapping from source of position to color by linked data * Remove debug prints * Delay surjection check for faster UI response --- CMakeLists.txt | 2 + src/MappingUtils.cpp | 109 ++++++++++++++++++++++++++ src/MappingUtils.h | 59 ++++++++++++++ src/ScatterplotPlugin.cpp | 158 ++++++++++++++++++++++++++++++-------- 4 files changed, 294 insertions(+), 34 deletions(-) create mode 100644 src/MappingUtils.cpp create mode 100644 src/MappingUtils.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c590a4d..b9674d0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,6 +40,8 @@ find_package(ManiVault COMPONENTS Core PointData ClusterData ColorData ImageData set(PLUGIN src/ScatterplotPlugin.h src/ScatterplotPlugin.cpp + src/MappingUtils.h + src/MappingUtils.cpp ) set(UI diff --git a/src/MappingUtils.cpp b/src/MappingUtils.cpp new file mode 100644 index 0000000..e238064 --- /dev/null +++ b/src/MappingUtils.cpp @@ -0,0 +1,109 @@ +#include "MappingUtils.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +std::pair getSelectionMapping(const mv::Dataset& source, const mv::Dataset& target, LinkedDataCondition checkMapping) { + const std::vector& linkedDatas = source->getLinkedData(); + + if (linkedDatas.empty()) + return { nullptr, 0 } ; + + // find linked data between source and target OR source and target's parent, if target is derived and they have the same number of points + if (const auto result = std::ranges::find_if( + linkedDatas, + [&target, &checkMapping](const mv::LinkedData& linkedData) -> bool { + return checkMapping(linkedData, target); + }); + result != linkedDatas.end()) + { + return {&(*result), target->getNumPoints() }; + } + + return { nullptr, 0 }; +} + +std::pair getSelectionMappingColorsToPositions(const mv::Dataset& colors, const mv::Dataset& positions) { + auto testTargetAndParent = [](const mv::LinkedData& linkedData, const mv::Dataset& positions) -> bool { + const mv::Dataset mapTargetData = linkedData.getTargetDataset(); + return mapTargetData == positions || parentHasSameNumPoints(mapTargetData, positions); + }; + + return getSelectionMapping(colors, positions, testTargetAndParent); +} + +std::pair getSelectionMappingPositionsToColors(const mv::Dataset& positions, const mv::Dataset& colors) { + auto testTarget = [](const mv::LinkedData& linkedData, const mv::Dataset& colors) -> bool { + return linkedData.getTargetDataset() == colors; + }; + + auto [mapping, numTargetPoints] = getSelectionMapping(positions, colors, testTarget); + + if (mapping && parentHasSameNumPoints(positions, positions)) { + const auto positionsParent = positions->getParent(); + std::tie(mapping, numTargetPoints) = getSelectionMapping(positionsParent, colors, testTarget); + } + + return { mapping, numTargetPoints }; +} + +std::pair getSelectionMappingPositionSourceToColors(const mv::Dataset& positions, const mv::Dataset& colors) { + if (!positions->isDerivedData()) + return { nullptr, 0 }; + + const auto fullSourceData = positions->getSourceDataset()->getFullDataset(); + + if(!fullSourceData.isValid()) + return { nullptr, 0 }; + + return getSelectionMappingPositionsToColors(fullSourceData, colors); +} + +bool checkSurjectiveMapping(const mv::LinkedData& linkedData, const std::uint32_t numPointsInTarget) { + const std::map>& linkedMap = linkedData.getMapping().getMap(); + + std::vector found(numPointsInTarget, false); + std::uint32_t count = 0; + + for (const auto& [key, vec] : linkedMap) { + for (std::uint32_t val : vec) { + if (val >= numPointsInTarget) continue; // Skip values that are too large + + if (!found[val]) { + found[val] = true; + if (++count == numPointsInTarget) + return true; + } + } + } + + return false; // The previous loop would have returned early if the entire taget set was covered +} + +bool checkSelectionMapping(const mv::Dataset& colors, const mv::Dataset& positions) { + + // Check if there is a mapping + auto [mapping, numTargetPoints] = getSelectionMappingColorsToPositions(colors, positions); + + if (!mapping) + std::tie(mapping, numTargetPoints) = getSelectionMappingPositionsToColors(positions, colors); + + if (!mapping) + std::tie(mapping, numTargetPoints) = getSelectionMappingPositionSourceToColors(positions, colors); + + if (!mapping) + return false; + + return true; +} diff --git a/src/MappingUtils.h b/src/MappingUtils.h new file mode 100644 index 0000000..62a4cba --- /dev/null +++ b/src/MappingUtils.h @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include +#include + +// This only checks the immedeate parent and is deliberately not recursive +// We might consider the latter in the future, but might need to cover edge cases +inline bool parentHasSameNumPoints(const mv::Dataset data, const mv::Dataset& other) { + if (!data->isDerivedData()) + return false; + + const auto parent = data->getParent(); + if (parent->getDataType() != PointType) + return false; + + const auto parentPoints = mv::Dataset(parent); + return parentPoints->getNumPoints() == other->getNumPoints(); +} + +// Is the data derived and does it's full source data have same number of points as the other data +inline bool fullSourceHasSameNumPoints(const mv::Dataset data, const mv::Dataset& other) { + if (!data->isDerivedData()) + return false; + + return data->getSourceDataset()->getFullDataset()->getNumPoints() == other->getNumPoints(); +} + +using LinkedDataCondition = std::function& target)>; + +/* Returns a mapping (linked data) from source that fulfils a given condition based on target, e.g. + auto checkMapping = [](const mv::LinkedData& linkedData, const mv::Dataset& target) -> bool { + return linkedData.getTargetDataset() == target; + }; + This function will return the first match of the condition +*/ +std::pair getSelectionMapping(const mv::Dataset& source, const mv::Dataset& target, LinkedDataCondition checkMapping); + +// Returns a mapping (linked data) from colors whose target is positions or whose target's parent has the same number of points as positions +std::pair getSelectionMappingColorsToPositions(const mv::Dataset& colors, const mv::Dataset& positions); + +// Returns a mapping (linked data) from positions whose target is colors or +// a mapping from positions' parent whose target is colors if the number of data points match +std::pair getSelectionMappingPositionsToColors(const mv::Dataset& positions, const mv::Dataset& colors); + +// Returns a mapping (linked data) from positions' source data whose target is colors +std::pair getSelectionMappingPositionSourceToColors(const mv::Dataset& positions, const mv::Dataset& colors); + +// Check if the mapping is surjective, i.e. hits all elements in the target +bool checkSurjectiveMapping(const mv::LinkedData& linkedData, const std::uint32_t numPointsInTarget); + +// returns whether there is a selection map from colors to positions or positions to colors (or respective parents) +// checks whether the mapping covers all elements in the target +bool checkSelectionMapping(const mv::Dataset& colors, const mv::Dataset& positions); diff --git a/src/ScatterplotPlugin.cpp b/src/ScatterplotPlugin.cpp index 6a3136e..c1b8b4a 100644 --- a/src/ScatterplotPlugin.cpp +++ b/src/ScatterplotPlugin.cpp @@ -1,5 +1,6 @@ #include "ScatterplotPlugin.h" +#include "MappingUtils.h" #include "ScatterplotWidget.h" #include @@ -31,6 +32,9 @@ #include #include +#include +#include +#include #include #define VIEW_SAMPLING_HTML @@ -172,17 +176,26 @@ ScatterplotPlugin::ScatterplotPlugin(const PluginFactory* factory) : }); } - // Accept both data with the same number if points and data which is derived from - // a parent that has the same number of points (e.g. for HSNE embeddings) + // Accept for recoloring: + // 1. data with the same number of points + // 2. data which is derived from a parent that has the same number of points (e.g. for HSNE embeddings), where we can use global indices for mapping + // 3. data which has a fully-covering selection mapping, that we can use for setting colors. Mapping in order of preference: + // a) from color (or it's parent) to position + // b) from color to position (or it's parent) + // c) from source of position to color + + // [1. Same number of points] const auto numPointsCandidate = candidateDataset->getNumPoints(); const auto numPointsPosition = _positionDataset->getNumPoints(); - const bool sameNumPoints = numPointsPosition == numPointsCandidate; - const bool sameNumPointsAsFull = - /*if*/ _positionDataset->isDerivedData() ? - /*then*/ _positionDataset->getSourceDataset()->getFullDataset()->getNumPoints() == numPointsCandidate : - /*else*/ false; + const bool hasSameNumPoints = numPointsPosition == numPointsCandidate; + + // [2. Derived from a parent] + const bool hasSameNumPointsAsFull = fullSourceHasSameNumPoints(_positionDataset, candidateDataset); - if (sameNumPoints || sameNumPointsAsFull) { + // [3. Full selection mapping] + const bool hasSelectionMapping = checkSelectionMapping(candidateDataset, _positionDataset); + + if (hasSameNumPoints || hasSameNumPointsAsFull || hasSelectionMapping) { // Offer the option to use the points dataset as source for points colors dropRegions << new DropWidget::DropRegion(this, "Point color", QString("Colorize %1 points with %2").arg(_positionDataset->text(), candidateDataset->text()), "palette", true, [this, candidateDataset]() { _settingsAction.getColoringAction().setCurrentColorDataset(candidateDataset); // calls addColorDataset internally @@ -190,7 +203,8 @@ ScatterplotPlugin::ScatterplotPlugin(const PluginFactory* factory) : } - if (sameNumPoints) { + // Accept for resizing and opacity: Only data with the same number of points + if (hasSameNumPoints) { // Offer the option to use the points dataset as source for points size dropRegions << new DropWidget::DropRegion(this, "Point size", QString("Size %1 points with %2").arg(_positionDataset->text(), candidateDataset->text()), "ruler-horizontal", true, [this, candidateDataset]() { _settingsAction.getPlotAction().getPointPlotAction().setCurrentPointSizeDataset(candidateDataset); @@ -647,49 +661,125 @@ void ScatterplotPlugin::positionDatasetChanged() updateData(); } -void ScatterplotPlugin::loadColors(const Dataset& points, const std::uint32_t& dimensionIndex) +void ScatterplotPlugin::loadColors(const Dataset& pointsColor, const std::uint32_t& dimensionIndex) { // Only proceed with valid points dataset - if (!points.isValid()) + if (!pointsColor.isValid()) return; - // Generate point scalars for color mapping - std::vector scalars; + const auto numColorPoints = pointsColor->getNumPoints(); - points->extractDataForDimension(scalars, dimensionIndex); + // Generate point colorScalars for color mapping + std::vector colorScalars = {}; + pointsColor->extractDataForDimension(colorScalars, dimensionIndex); - const auto numColorPoints = points->getNumPoints(); + // If number of points do not match, use a mapping + // prefer global IDs (for derived data) over selection mapping + // prefer color to position over position to color over source of position to color + if (numColorPoints != _numPoints) { + std::vector mappedColorScalars(_numPoints, std::numeric_limits::lowest()); - if (numColorPoints != _numPoints) { + try { + const bool hasSameNumPointsAsFull = fullSourceHasSameNumPoints(_positionDataset, pointsColor); - const bool sameNumPointsAsFull = - /*if*/ _positionDataset->isDerivedData() ? - /*then*/ _positionSourceDataset->getFullDataset()->getNumPoints() == numColorPoints : - /*else*/ false; + if (hasSameNumPointsAsFull) { + std::vector globalIndices = {}; + _positionDataset->getGlobalIndices(globalIndices); - if (sameNumPointsAsFull) { - std::vector globalIndices; - _positionDataset->getGlobalIndices(globalIndices); + for (std::int32_t localIndex = 0; localIndex < globalIndices.size(); localIndex++) { + mappedColorScalars[localIndex] = colorScalars[globalIndices[localIndex]]; + } - std::vector localScalars(_numPoints, 0); - std::int32_t localColorIndex = 0; + } + else if ( // mapping from color data set to position data set + const auto [selectionMapping, numPointsTarget] = getSelectionMappingColorsToPositions(pointsColor, _positionDataset); + /* check if valid */ + selectionMapping != nullptr && + numPointsTarget == _numPoints && + checkSurjectiveMapping(*selectionMapping, numPointsTarget) + ) + { + // Map values like selection + const mv::SelectionMap::Map& mapColorsToPositions = selectionMapping->getMapping().getMap(); - for (const auto& globalIndex : globalIndices) - localScalars[localColorIndex++] = scalars[globalIndex]; + for (const auto& [fromColorID, vecOfPositionIDs] : mapColorsToPositions) { + for (std::uint32_t toPositionID : vecOfPositionIDs) { + mappedColorScalars[toPositionID] = colorScalars[fromColorID]; + } + } - std::swap(localScalars, scalars); - } - else { - qWarning("Number of points used for coloring does not match number of points in data, aborting attempt to color plot"); + } + else if ( // mapping from position data set to color data set + const auto [selectionMapping, numPointsTarget] = getSelectionMappingPositionsToColors(_positionDataset, pointsColor); + /* check if valid */ + selectionMapping != nullptr && + numPointsTarget == numColorPoints && + checkSurjectiveMapping(*selectionMapping, numPointsTarget) + ) + { + // Map values like selection (in reverse, use first value that occurs) + const mv::SelectionMap::Map& mapPositionsToColors = selectionMapping->getMapping().getMap(); + + for (const auto& [fromPositionID, vecOfColorIDs] : mapPositionsToColors) { + if (mappedColorScalars[fromPositionID] != std::numeric_limits::lowest()) + continue; + for (std::uint32_t toColorID : vecOfColorIDs) { + mappedColorScalars[fromPositionID] = colorScalars[toColorID]; + } + } + + } + else if ( // mapping from source of position data set to color data set + const auto [selectionMapping, numPointsTarget] = getSelectionMappingPositionSourceToColors(_positionDataset, pointsColor); + /* check if valid */ + selectionMapping != nullptr && + numPointsTarget == numColorPoints && + checkSurjectiveMapping(*selectionMapping, numPointsTarget) + ) + { + // the selection map is from full source data of positions data to pointsColor + // we need to use both the global indices of the positions (i.e. in the source) and the linked data mapping + const mv::SelectionMap::Map& mapGlobalToColors = selectionMapping->getMapping().getMap(); + std::vector globalIndices = {}; + _positionDataset->getGlobalIndices(globalIndices); + + for (std::int32_t localIndex = 0; localIndex < globalIndices.size(); localIndex++) { + + if (mappedColorScalars[localIndex] != std::numeric_limits::lowest()) + continue; + + const auto& indxColors = mapGlobalToColors.at(globalIndices[localIndex]); // from full source (parent) to colorDataset + + for (const auto& indColors : indxColors) { + mappedColorScalars[localIndex] = colorScalars[indColors]; + } + } + + } + else { + throw std::runtime_error("Coloring data set does not match position data set in a known way, aborting attempt to color plot"); + } + + } + catch (const std::exception& e) { + qDebug() << "ScatterplotPlugin::loadColors: mapping failed -> " << e.what(); + _settingsAction.getColoringAction().getColorByAction().setCurrentIndex(0); // reset to color by constant return; } + catch (...) { + qDebug() << "ScatterplotPlugin::loadColors: mapping failed for an unknown reason."; + _settingsAction.getColoringAction().getColorByAction().setCurrentIndex(0); // reset to color by constant + return; + } + + std::swap(mappedColorScalars, colorScalars); } - assert(scalars.size() == _numPoints); + assert(colorScalars.size() == _numPoints); - // Assign scalars and scalar effect - _scatterPlotWidget->setScalars(scalars); + // Assign colorScalars and scalar effect + _scatterPlotWidget->setScalars(colorScalars); _scatterPlotWidget->setScalarEffect(PointEffect::Color); _settingsAction.getColoringAction().updateColorMapActionScalarRange(); From c1a20231aa3a50bcae2865ec7c37a13a9bde430c Mon Sep 17 00:00:00 2001 From: Thomas Kroes Date: Wed, 3 Sep 2025 09:41:26 +0200 Subject: [PATCH 2/6] Simplify navigation toolbar creation (#191) --- src/ScatterplotPlugin.cpp | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/src/ScatterplotPlugin.cpp b/src/ScatterplotPlugin.cpp index c1b8b4a..6f9702f 100644 --- a/src/ScatterplotPlugin.cpp +++ b/src/ScatterplotPlugin.cpp @@ -277,28 +277,10 @@ void ScatterplotPlugin::init() layout->addWidget(_primaryToolbarAction.createWidget(&getWidget())); layout->addWidget(_scatterPlotWidget, 100); - auto navigationWidget = new QWidget(); - auto navigationLayout = new QHBoxLayout(); - - navigationLayout->setContentsMargins(4, 4, 4, 4); - - navigationLayout->addStretch(1); - { - auto renderersNavigationGroupAction = new HorizontalGroupAction(this, "Navigation"); - - renderersNavigationGroupAction->setShowLabels(false); - - renderersNavigationGroupAction->addAction(const_cast(&_scatterPlotWidget->getPointRendererNavigator().getNavigationAction())); - - _scatterPlotWidget->getPointRendererNavigator().getNavigationAction().setParent(&_settingsAction); - - navigationLayout->addWidget(renderersNavigationGroupAction->createWidget(&getWidget())); + if (auto navigationWidget = _scatterPlotWidget->getPointRendererNavigator().getNavigationAction().createWidget(&getWidget())) { + layout->addWidget(navigationWidget); + layout->setAlignment(navigationWidget, Qt::AlignCenter); } - navigationLayout->addStretch(1); - - navigationWidget->setLayout(navigationLayout); - - layout->addWidget(navigationWidget); getWidget().setLayout(layout); From 1beaf71742f69cdfbe35a9926de545b4ca9f014b Mon Sep 17 00:00:00 2001 From: Thomas Kroes Date: Wed, 3 Sep 2025 11:56:03 +0200 Subject: [PATCH 3/6] Use new core view plugin HUD functionality (#192) --- src/ScatterplotPlugin.cpp | 29 +++++++++++++++++++++++++++++ src/ScatterplotPlugin.h | 2 ++ 2 files changed, 31 insertions(+) diff --git a/src/ScatterplotPlugin.cpp b/src/ScatterplotPlugin.cpp index 6f9702f..e4582f6 100644 --- a/src/ScatterplotPlugin.cpp +++ b/src/ScatterplotPlugin.cpp @@ -262,6 +262,10 @@ ScatterplotPlugin::ScatterplotPlugin(const PluginFactory* factory) : getSamplerAction().getEnabledAction().setChecked(false); getLearningCenterAction().addVideos(QStringList({ "Practitioner", "Developer" })); + + setOverlayActionsTargetWidget(_scatterPlotWidget); + + } ScatterplotPlugin::~ScatterplotPlugin() @@ -270,6 +274,8 @@ ScatterplotPlugin::~ScatterplotPlugin() void ScatterplotPlugin::init() { + getWidget().setMouseTracking(true); + auto layout = new QVBoxLayout(); layout->setContentsMargins(0, 0, 0, 0); @@ -357,6 +363,13 @@ void ScatterplotPlugin::init() return pointIndicesTableWidget; }); #endif + + updateHeadsUpDisplay(); + + connect(&_positionDataset, &Dataset<>::changed, this, &ScatterplotPlugin::updateHeadsUpDisplay); + connect(&_positionDataset, &Dataset<>::guiNameChanged, this, &ScatterplotPlugin::updateHeadsUpDisplay); + connect(&_settingsAction.getColoringAction(), &ColoringAction::currentColorDatasetChanged, this, &ScatterplotPlugin::updateHeadsUpDisplay); + connect(&_settingsAction.getColoringAction().getColorByAction(), &OptionAction::currentIndexChanged, this, &ScatterplotPlugin::updateHeadsUpDisplay); } void ScatterplotPlugin::loadData(const Datasets& datasets) @@ -951,6 +964,22 @@ void ScatterplotPlugin::updateSelection() } } +void ScatterplotPlugin::updateHeadsUpDisplay() +{ + getHeadsUpDisplayAction().removeAllHeadsUpDisplayItems(); + + if (_positionDataset.isValid()) { + auto datasetsItem = getHeadsUpDisplayAction().addHeadsUpDisplayItem("Datasets", "", ""); + + getHeadsUpDisplayAction().addHeadsUpDisplayItem("Position by:", _positionDataset->getGuiName(), "", datasetsItem); + + if (_settingsAction.getColoringAction().getCurrentColorDataset().isValid()) + getHeadsUpDisplayAction().addHeadsUpDisplayItem("Color by:", _settingsAction.getColoringAction().getCurrentColorDataset()->getGuiName(), "", datasetsItem); + } else { + getHeadsUpDisplayAction().addHeadsUpDisplayItem("No datasets loaded", "", ""); + } +} + void ScatterplotPlugin::fromVariantMap(const QVariantMap& variantMap) { ViewPlugin::fromVariantMap(variantMap); diff --git a/src/ScatterplotPlugin.h b/src/ScatterplotPlugin.h index d1264fc..4ef3c3f 100644 --- a/src/ScatterplotPlugin.h +++ b/src/ScatterplotPlugin.h @@ -94,6 +94,8 @@ class ScatterplotPlugin : public ViewPlugin void updateData(); void updateSelection(); + void updateHeadsUpDisplay(); + public: // Serialization /** From bd3f1f5029b866a82da7f8205e5eae23581bb44a Mon Sep 17 00:00:00 2001 From: Thomas Kroes Date: Thu, 4 Sep 2025 12:47:40 +0200 Subject: [PATCH 4/6] Set correct navigation action parent --- src/ScatterplotPlugin.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ScatterplotPlugin.cpp b/src/ScatterplotPlugin.cpp index e4582f6..669b92c 100644 --- a/src/ScatterplotPlugin.cpp +++ b/src/ScatterplotPlugin.cpp @@ -283,9 +283,13 @@ void ScatterplotPlugin::init() layout->addWidget(_primaryToolbarAction.createWidget(&getWidget())); layout->addWidget(_scatterPlotWidget, 100); - if (auto navigationWidget = _scatterPlotWidget->getPointRendererNavigator().getNavigationAction().createWidget(&getWidget())) { + auto& navigationAction = _scatterPlotWidget->getPointRendererNavigator().getNavigationAction(); + + if (auto navigationWidget = navigationAction.createWidget(&getWidget())) { layout->addWidget(navigationWidget); layout->setAlignment(navigationWidget, Qt::AlignCenter); + + navigationAction.setParent(&_settingsAction); } getWidget().setLayout(layout); From 1e6ae0db7436ee9ef3067e1d4249f8837c110e42 Mon Sep 17 00:00:00 2001 From: Alexander Vieth Date: Thu, 4 Sep 2025 17:14:11 +0200 Subject: [PATCH 5/6] Add size and opacity to HUD (#193) --- src/ScatterplotPlugin.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/ScatterplotPlugin.cpp b/src/ScatterplotPlugin.cpp index 669b92c..bb043cc 100644 --- a/src/ScatterplotPlugin.cpp +++ b/src/ScatterplotPlugin.cpp @@ -374,6 +374,8 @@ void ScatterplotPlugin::init() connect(&_positionDataset, &Dataset<>::guiNameChanged, this, &ScatterplotPlugin::updateHeadsUpDisplay); connect(&_settingsAction.getColoringAction(), &ColoringAction::currentColorDatasetChanged, this, &ScatterplotPlugin::updateHeadsUpDisplay); connect(&_settingsAction.getColoringAction().getColorByAction(), &OptionAction::currentIndexChanged, this, &ScatterplotPlugin::updateHeadsUpDisplay); + connect(&_settingsAction.getPlotAction().getPointPlotAction().getSizeAction(), &ScalarAction::sourceDataChanged, this, &ScatterplotPlugin::updateHeadsUpDisplay); + connect(&_settingsAction.getPlotAction().getPointPlotAction().getOpacityAction(), &ScalarAction::sourceDataChanged, this, &ScatterplotPlugin::updateHeadsUpDisplay); } void ScatterplotPlugin::loadData(const Datasets& datasets) @@ -973,12 +975,19 @@ void ScatterplotPlugin::updateHeadsUpDisplay() getHeadsUpDisplayAction().removeAllHeadsUpDisplayItems(); if (_positionDataset.isValid()) { - auto datasetsItem = getHeadsUpDisplayAction().addHeadsUpDisplayItem("Datasets", "", ""); + const auto datasetsItem = getHeadsUpDisplayAction().addHeadsUpDisplayItem("Datasets", "", ""); getHeadsUpDisplayAction().addHeadsUpDisplayItem("Position by:", _positionDataset->getGuiName(), "", datasetsItem); - if (_settingsAction.getColoringAction().getCurrentColorDataset().isValid()) - getHeadsUpDisplayAction().addHeadsUpDisplayItem("Color by:", _settingsAction.getColoringAction().getCurrentColorDataset()->getGuiName(), "", datasetsItem); + auto addMetaDataToHeadsUpDisplay = [this](const QString& metaDataName, const Dataset<> data, const util::HeadsUpDisplayItemSharedPtr& itemPtr) { + if (data.isValid()) + getHeadsUpDisplayAction().addHeadsUpDisplayItem(QString("%1 by:").arg(metaDataName), data->getGuiName(), "", itemPtr); + }; + + addMetaDataToHeadsUpDisplay("Color", _settingsAction.getColoringAction().getCurrentColorDataset(), datasetsItem); + addMetaDataToHeadsUpDisplay("Size", _settingsAction.getPlotAction().getPointPlotAction().getSizeAction().getCurrentDataset(), datasetsItem); + addMetaDataToHeadsUpDisplay("Opacity", _settingsAction.getPlotAction().getPointPlotAction().getOpacityAction().getCurrentDataset(), datasetsItem); + } else { getHeadsUpDisplayAction().addHeadsUpDisplayItem("No datasets loaded", "", ""); } From ec35367a17a8e1e7036440b16c21cbf176f59824 Mon Sep 17 00:00:00 2001 From: Alexander Vieth Date: Fri, 5 Sep 2025 15:00:24 +0200 Subject: [PATCH 6/6] Call updateHeadsUpDisplay in fromVariantMap (#194) --- src/ScatterplotPlugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ScatterplotPlugin.cpp b/src/ScatterplotPlugin.cpp index bb043cc..a2dd6aa 100644 --- a/src/ScatterplotPlugin.cpp +++ b/src/ScatterplotPlugin.cpp @@ -1006,7 +1006,7 @@ void ScatterplotPlugin::fromVariantMap(const QVariantMap& variantMap) _primaryToolbarAction.fromParentVariantMap(variantMap); _settingsAction.fromParentVariantMap(variantMap); - + updateHeadsUpDisplay(); if (pointRenderer.getNavigator().getNavigationAction().getSerializationCountFrom() == 0) { qDebug() << "Resetting view";