From 74ef47fef5280cc0c7b4a7d46b5e720bccc98325 Mon Sep 17 00:00:00 2001 From: Julian Thijssen Date: Tue, 16 Dec 2025 13:52:09 +0100 Subject: [PATCH 01/33] Add missing actions to serialization --- src/SettingsAction.cpp | 6 +++++- src/SettingsAction.h | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/SettingsAction.cpp b/src/SettingsAction.cpp index 740da78..03d0f82 100644 --- a/src/SettingsAction.cpp +++ b/src/SettingsAction.cpp @@ -70,12 +70,14 @@ void SettingsAction::fromVariantMap(const QVariantMap& variantMap) _plotAction.fromParentVariantMap(variantMap); _positionAction.fromParentVariantMap(variantMap); _coloringAction.fromParentVariantMap(variantMap); + _subsetAction.fromParentVariantMap(variantMap); + _clusteringAction.fromParentVariantMap(variantMap); _renderModeAction.fromParentVariantMap(variantMap); _selectionAction.fromParentVariantMap(variantMap); _miscellaneousAction.fromParentVariantMap(variantMap); if (variantMap.contains("PointRendererNavigation")) - _scatterplotPlugin->getScatterplotWidget().getPointRendererNavigator().getNavigationAction().fromVariantMap(variantMap["PointRendererNavigation"].toMap()); + _scatterplotPlugin->getScatterplotWidget().getPointRendererNavigator().getNavigationAction().fromVariantMap(variantMap["PointRendererNavigation"].toMap()); if (variantMap.contains("DensityRendererNavigation")) _scatterplotPlugin->getScatterplotWidget().getDensityRendererNavigator().getNavigationAction().fromVariantMap(variantMap["DensityRendererNavigation"].toMap()); @@ -90,6 +92,8 @@ QVariantMap SettingsAction::toVariantMap() const _plotAction.insertIntoVariantMap(variantMap); _positionAction.insertIntoVariantMap(variantMap); _coloringAction.insertIntoVariantMap(variantMap); + _subsetAction.insertIntoVariantMap(variantMap); + _clusteringAction.insertIntoVariantMap(variantMap); _selectionAction.insertIntoVariantMap(variantMap); _miscellaneousAction.insertIntoVariantMap(variantMap); diff --git a/src/SettingsAction.h b/src/SettingsAction.h index 99a22f7..a6b0ba9 100644 --- a/src/SettingsAction.h +++ b/src/SettingsAction.h @@ -80,4 +80,4 @@ class SettingsAction : public GroupAction ExportAction _exportAction; /** Action for exporting */ MiscellaneousAction _miscellaneousAction; /** Action for miscellaneous settings */ DatasetsAction _datasetsAction; /** Action for picking dataset(s) */ -}; \ No newline at end of file +}; From e58acaafd5f21cf8be457fec8aa6408476468649 Mon Sep 17 00:00:00 2001 From: Alexander Vieth Date: Tue, 10 Feb 2026 11:22:23 +0100 Subject: [PATCH 02/33] Fix HUD connection race (#223) * Remove duplicate connection * Prevent possible race * Group all positionDataset connections * Refactor: Group connections --- src/ScatterplotPlugin.cpp | 63 +++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/src/ScatterplotPlugin.cpp b/src/ScatterplotPlugin.cpp index 1e35b7c..94d4826 100644 --- a/src/ScatterplotPlugin.cpp +++ b/src/ScatterplotPlugin.cpp @@ -334,18 +334,46 @@ void ScatterplotPlugin::init() connect(&getSamplerAction(), &ViewPluginSamplerAction::sampleContextRequested, this, &ScatterplotPlugin::samplePoints); connect(&_positionDataset, &Dataset::changed, this, &ScatterplotPlugin::positionDatasetChanged); - connect(&_positionDataset, &Dataset::dataChanged, this, &ScatterplotPlugin::updateData); + connect(&_positionDataset, &Dataset::dataChanged, this, [this]() -> void { + updateData(); + updateHeadsUpDisplay(); + }); connect(&_positionDataset, &Dataset::dataSelectionChanged, this, &ScatterplotPlugin::updateSelection); + connect(&_positionDataset, &Dataset<>::guiNameChanged, this, &ScatterplotPlugin::updateHeadsUpDisplay); - _scatterPlotWidget->installEventFilter(this); + const auto currentColorDatasetChanged = [this](Dataset currentColorDataset) -> void { + if (_colorDataset == currentColorDataset) + return; - getLearningCenterAction().getViewPluginOverlayWidget()->setTargetWidget(_scatterPlotWidget); + if (_colorDataset.isValid()) + disconnect(&_colorDataset, &Dataset<>::guiNameChanged, this, nullptr); + + _colorDataset = currentColorDataset; + + connect(&_colorDataset, &Dataset<>::guiNameChanged, this, &ScatterplotPlugin::updateHeadsUpDisplay); + + updateHeadsUpDisplay(); + }; + + connect(&_settingsAction.getColoringAction(), &ColoringAction::currentColorDatasetChanged, this, currentColorDatasetChanged); + connect(&_settingsAction.getColoringAction().getColorByAction(), &OptionAction::currentIndexChanged, this, [this, currentColorDatasetChanged](const std::int32_t& currentIndex) -> void { + currentColorDatasetChanged(_settingsAction.getColoringAction().getCurrentColorDataset()); + }); + + connect(&_settingsAction.getPlotAction().getPointPlotAction().getSizeAction(), &ScalarAction::sourceDataChanged, this, &ScatterplotPlugin::updateHeadsUpDisplay); + connect(&_settingsAction.getPlotAction().getPointPlotAction().getOpacityAction(), &ScalarAction::sourceDataChanged, this, &ScatterplotPlugin::updateHeadsUpDisplay); + + connect(&_settingsAction.getMiscellaneousAction().getBackgroundColorAction(), &ColorAction::colorChanged, this, &ScatterplotPlugin::updateHeadsUpDisplayTextColor); connect(&getScatterplotWidget().getPointRendererNavigator().getNavigationAction().getZoomSelectionAction(), &TriggerAction::triggered, this, [this]() -> void { if (_selectionBoundaries.isValid()) _scatterPlotWidget->getPointRendererNavigator().setZoomRectangleWorld(_selectionBoundaries); }); + _scatterPlotWidget->installEventFilter(this); + + getLearningCenterAction().getViewPluginOverlayWidget()->setTargetWidget(_scatterPlotWidget); + #ifdef VIEW_SAMPLING_HTML getSamplerAction().setHtmlViewGeneratorFunction([this](const ViewPluginSamplerAction::SampleContext& toolTipContext) -> QString { QStringList localPointIndices, globalPointIndices; @@ -391,36 +419,7 @@ void ScatterplotPlugin::init() #endif updateHeadsUpDisplay(); - - connect(&_positionDataset, &Dataset<>::changed, this, &ScatterplotPlugin::updateHeadsUpDisplay); - connect(&_positionDataset, &Dataset<>::guiNameChanged, this, &ScatterplotPlugin::updateHeadsUpDisplay); - connect(&_positionDataset, &Dataset<>::guiNameChanged, this, &ScatterplotPlugin::updateHeadsUpDisplay); - - const auto currentColorDatasetChanged = [this](Dataset currentColorDataset) -> void { - if (_colorDataset == currentColorDataset) - return; - - if (_colorDataset.isValid()) - disconnect(&_colorDataset, &Dataset<>::guiNameChanged, this, nullptr); - - _colorDataset = currentColorDataset; - - connect(&_colorDataset, &Dataset<>::guiNameChanged, this, &ScatterplotPlugin::updateHeadsUpDisplay); - - updateHeadsUpDisplay(); - }; - - connect(&_settingsAction.getColoringAction(), &ColoringAction::currentColorDatasetChanged, this, currentColorDatasetChanged); - connect(&_settingsAction.getColoringAction().getColorByAction(), &OptionAction::currentIndexChanged, this, [this, currentColorDatasetChanged](const std::int32_t& currentIndex) -> void { - currentColorDatasetChanged(_settingsAction.getColoringAction().getCurrentColorDataset()); - }); - - connect(&_settingsAction.getPlotAction().getPointPlotAction().getSizeAction(), &ScalarAction::sourceDataChanged, this, &ScatterplotPlugin::updateHeadsUpDisplay); - connect(&_settingsAction.getPlotAction().getPointPlotAction().getOpacityAction(), &ScalarAction::sourceDataChanged, this, &ScatterplotPlugin::updateHeadsUpDisplay); - updateHeadsUpDisplayTextColor(); - - connect(&_settingsAction.getMiscellaneousAction().getBackgroundColorAction(), &ColorAction::colorChanged, this, &ScatterplotPlugin::updateHeadsUpDisplayTextColor); } void ScatterplotPlugin::loadData(const Datasets& datasets) From 69024fcdb615f6433cd9724327e6f3a593ebfc83 Mon Sep 17 00:00:00 2001 From: Thomas Kroes Date: Thu, 19 Feb 2026 11:08:13 +0100 Subject: [PATCH 03/33] Feature/Fix gui name rename crash (#230) * Refactor scalar source model and update ScalarAction Convert ScalarSourceModel from QAbstractListModel to QStandardItemModel and introduce typed row items (NameItem, IdItem, Item) and a two-column layout (Name/ID). Datasets are now stored as rows (with helper Row class) and looked up by dataset ID via matching; getDatasets(), getDataset(), add/remove dataset and removeAllDatasets were adapted accordingly. Added getRowIndex() and removed the old per-dataset vector/updateData machinery. Update GUI data handling into Item/NameItem/IdItem (decoration, display and tooltip roles) and connect GUI name changes to emit updates. In ScalarAction: use getRowIndex() instead of rowIndex(), changed setCurrentSourceIndex parameter to std::int32_t, simplified getCurrentDataset() return, and left a TODO-commented dataset connection block. These changes centralize model data in QStandardItem rows, enable multi-column metadata, and simplify dataset lookup/removal by ID. * Pass model reference to Item/Row Give Item/NameItem/Row access to their parent ScalarSourceModel by adding a const ScalarSourceModel& parameter and storing it in Item::_scalarSourceModel. Update construction sites (appendRow/Row) to pass *this. Use the model reference in Item::data() to respect getShowFullPathName() and use getGuiName() for display. Add getScalarSourceModel() accessor. Also change getDatasets() to return by value and remove some dead/commented code. These changes allow items to query model state reliably and simplify row construction. --- src/ScalarAction.cpp | 9 +- src/ScalarAction.h | 2 +- src/ScalarSourceModel.cpp | 288 +++++++++++++++++++++----------------- src/ScalarSourceModel.h | 178 ++++++++++++++++++----- 4 files changed, 311 insertions(+), 166 deletions(-) diff --git a/src/ScalarAction.cpp b/src/ScalarAction.cpp index 0c8ef8d..f151703 100644 --- a/src/ScalarAction.cpp +++ b/src/ScalarAction.cpp @@ -43,6 +43,7 @@ void ScalarAction::addDataset(const Dataset& dataset) sourceModel.addDataset(dataset); + /* TODO: this connection is not removed when the dataset is removed from the model, but that should not cause any issues since the dataset will be invalid and the connection will not do anything in that case connect(&sourceModel.getDatasets().last(), &Dataset::dataChanged, this, [this, dataset]() { const auto currentDataset = getCurrentDataset(); @@ -55,6 +56,8 @@ void ScalarAction::addDataset(const Dataset& dataset) emit sourceDataChanged(dataset); }); +*/ + connect(&_magnitudeAction, &DecimalAction::valueChanged, this, [this, dataset](const float& value) { emit magnitudeChanged(value); }); @@ -72,20 +75,20 @@ Dataset ScalarAction::getCurrentDataset() const auto currentSourceIndex = _sourceAction.getPickerAction().getCurrentIndex(); if (currentSourceIndex < ScalarSourceModel::DefaultRow::DatasetStart) - return Dataset(); + return {}; return scalarSourceModel.getDataset(currentSourceIndex); } void ScalarAction::setCurrentDataset(const Dataset& dataset) { - const auto datasetRowIndex = _sourceAction.getModel().rowIndex(dataset); + const auto datasetRowIndex = _sourceAction.getModel().getRowIndex(dataset); if (datasetRowIndex >= 0) _sourceAction.getPickerAction().setCurrentIndex(datasetRowIndex); } -void ScalarAction::setCurrentSourceIndex(bool sourceIndex) +void ScalarAction::setCurrentSourceIndex(std::int32_t sourceIndex) { _sourceAction.getPickerAction().setCurrentIndex(sourceIndex); } diff --git a/src/ScalarAction.h b/src/ScalarAction.h index 39ee126..b24f375 100644 --- a/src/ScalarAction.h +++ b/src/ScalarAction.h @@ -51,7 +51,7 @@ class ScalarAction : public GroupAction * Set the current source index * @param sourceIndex Source index */ - void setCurrentSourceIndex(bool sourceIndex); + void setCurrentSourceIndex(std::int32_t sourceIndex); /** Determines whether the scalar source is a constant */ bool isSourceConstant() const; diff --git a/src/ScalarSourceModel.cpp b/src/ScalarSourceModel.cpp index d3220ce..3d0657d 100644 --- a/src/ScalarSourceModel.cpp +++ b/src/ScalarSourceModel.cpp @@ -8,189 +8,236 @@ using namespace mv; using namespace mv::util; ScalarSourceModel::ScalarSourceModel(QObject* parent /*= nullptr*/) : - QAbstractListModel(parent), + QStandardItemModel(parent), _showFullPathName(true) { + appendRow(Row(*this, {})); // Constant source + appendRow(Row(*this, {})); // Selection source } -int ScalarSourceModel::rowCount(const QModelIndex& parent /*= QModelIndex()*/) const +ScalarSourceModel::Item::Item(const ScalarSourceModel& scalarSourceModel, const mv::Dataset<>& scalarDataset) : + _scalarSourceModel(scalarSourceModel), + _scalarDataset(scalarDataset) { - // Constant point size option plus the number of available datasets - return _datasets.count() + DefaultRow::DatasetStart; } -int ScalarSourceModel::rowIndex(const Dataset& dataset) const +QVariant ScalarSourceModel::Item::data(int role) const { - // Only proceed if we have a valid dataset - if (!dataset.isValid()) - return -1; + const auto rowIndex = row(); + + switch (role) + { + // Return ruler icon for constant point size and dataset icon otherwise + case Qt::DecorationRole: + { + if (rowIndex == DefaultRow::Constant) + return StyledIcon("ruler"); + + if (rowIndex == DefaultRow::Selection) + return StyledIcon("mouse-pointer"); + + if (rowIndex >= DefaultRow::DatasetStart) + return _scalarDataset->icon(); + + break; + } - // Return the index of the dataset and add one for the constant point size option - return _datasets.indexOf(dataset) + DefaultRow::DatasetStart; + // Return 'Constant' for constant point size and dataset (full path) GUI name otherwise + case Qt::DisplayRole: + { + if (rowIndex >= DefaultRow::DatasetStart) + { + if (rowIndex == 2) + return _scalarDataset->text(); + else + return getScalarSourceModel().getShowFullPathName() ? getScalarDataset()->getLocation() : getScalarDataset()->getGuiName(); + } + else { + if (rowIndex == DefaultRow::Constant) + return "Constant"; + + if (rowIndex == DefaultRow::Selection) + return "Selection"; + } + } + + default: + break; + } + + return {}; } -int ScalarSourceModel::columnCount(const QModelIndex& parent /*= QModelIndex()*/) const +const ScalarSourceModel& ScalarSourceModel::Item::getScalarSourceModel() const { - return 1; + return _scalarSourceModel; } -QVariant ScalarSourceModel::data(const QModelIndex& index, int role) const +const mv::Dataset<>& ScalarSourceModel::Item::getScalarDataset() const { - // Get row/column of and smart pointer to the dataset - const auto row = index.row(); - const auto column = index.column(); - const auto scalarDataset = getDataset(row); + return _scalarDataset; +} + +ScalarSourceModel::NameItem::NameItem(const ScalarSourceModel& scalarSourceModel, const mv::Dataset<>& scalarDataset) : + Item(scalarSourceModel, scalarDataset) +{ + connect(&const_cast&>(getScalarDataset()), &Dataset<>::guiNameChanged, this, [this]() { + emitDataChanged(); + }); +} + +QVariant ScalarSourceModel::NameItem::data(int role) const +{ + const auto rowIndex = row(); switch (role) { - // Return ruler icon for constant point size and dataset icon otherwise - case Qt::DecorationRole: - { - if (row == DefaultRow::Constant) - return StyledIcon("ruler"); + case Qt::DisplayRole: + case Qt::EditRole: + { + if (rowIndex == DefaultRow::Constant) + return "Constant"; - if (row == DefaultRow::Selection) - return StyledIcon("mouse-pointer"); + if (rowIndex == DefaultRow::Selection) + return "Selection"; - if (row >= DefaultRow::DatasetStart) - return scalarDataset->icon(); + if (rowIndex >= DefaultRow::DatasetStart) + return getScalarDataset()->getGuiName(); break; - } + } - // Return 'Constant' for constant point size and dataset (full path) GUI name otherwise - case Qt::DisplayRole: + case Qt::ToolTipRole: { - if (row >= DefaultRow::DatasetStart) - { - if (row == 2) - return scalarDataset->text(); - else - return _showFullPathName ? scalarDataset->getLocation() : scalarDataset->text(); - } else { - if (row == DefaultRow::Constant) - return "Constant"; - - if (row == DefaultRow::Selection) - return "Selection"; - } + if (rowIndex == DefaultRow::Constant) + return "Constant"; + + if (rowIndex == DefaultRow::Selection) + return "Selection"; + + if (rowIndex >= DefaultRow::DatasetStart) + return getScalarDataset()->getLocation(); break; } - default: - break; + default: + break; } - return QVariant(); + return Item::data(role); } -void ScalarSourceModel::addDataset(const Dataset& dataset) +QVariant ScalarSourceModel::IdItem::data(int role) const { - // Avoid duplicates - if (hasDataset(dataset)) - return; + const auto rowIndex = row(); - // Insert row into model - beginInsertRows(QModelIndex(), rowCount(), rowCount()); + switch (role) { - // Add the dataset - _datasets << dataset; - } - endInsertRows(); + case Qt::DisplayRole: + case Qt::EditRole: + { + if (rowIndex == DefaultRow::Constant || rowIndex == DefaultRow::Selection) + return ""; - // Get smart pointer to last added dataset - auto& addedDataset = _datasets.last(); + if (rowIndex >= DefaultRow::DatasetStart) + return getScalarDataset()->getId(); - // Remove a dataset from the model when it is about to be deleted - connect(&addedDataset, &Dataset::aboutToBeRemoved, this, [this, &addedDataset]() { - removeDataset(addedDataset); - }); + break; + } - // Notify others that the model has updated when the dataset GUI name changes - connect(addedDataset.get(), &DatasetImpl::textChanged, this, [this, &addedDataset]() { + case Qt::ToolTipRole: + { + if (rowIndex == DefaultRow::Constant || rowIndex == DefaultRow::Selection) + return ""; - // Get row index of the dataset - const auto colorDatasetRowIndex = rowIndex(addedDataset); + if (rowIndex >= DefaultRow::DatasetStart) + return "ID: " + getScalarDataset()->getId(); - // Only proceed if we found a valid row index - if (colorDatasetRowIndex < 0) - return; + break; + } - // Establish model index - const auto modelIndex = index(colorDatasetRowIndex, 0); + default: + break; + } - // Only proceed if we have a valid model index - if (!modelIndex.isValid()) - return; + return Item::data(role); +} - // Notify others that the data changed - emit dataChanged(modelIndex, modelIndex); - }); +void ScalarSourceModel::addDataset(const Dataset& dataset) +{ + if (hasDataset(dataset)) + return; + + appendRow(Row(*this, dataset)); } bool ScalarSourceModel::hasDataset(const Dataset& dataset) const { - return rowIndex(dataset) >= DefaultRow::DatasetStart; + if (!dataset.isValid()) + return false; + else + return !match(index(0, static_cast(Column::Id)), Qt::EditRole, dataset->getId(), 1, Qt::MatchExactly).isEmpty(); } void ScalarSourceModel::removeDataset(const Dataset& dataset) { - // Get row index of the dataset - const auto datasetRowIndex = rowIndex(dataset); + if (!hasDataset(dataset)) + return; - // Update model - beginRemoveRows(QModelIndex(), datasetRowIndex, datasetRowIndex); - { - // Remove dataset from internal vector - _datasets.removeOne(dataset); - } - endRemoveRows(); + const auto matches = match(index(0, static_cast(Column::Id)), Qt::EditRole, dataset->getId(), 1, Qt::MatchExactly); + + if (!matches.isEmpty()) + removeRow(matches.first().row()); } void ScalarSourceModel::removeAllDatasets() { - // Remove row from model - beginRemoveRows(QModelIndex(), 0, rowCount() - DefaultRow::DatasetStart); - { - // Remove all datasets - _datasets.clear(); - } - endRemoveRows(); - - // And update model data with altered datasets - updateData(); + removeRows(DefaultRow::DatasetStart, rowCount() - DefaultRow::DatasetStart); } -const Datasets& ScalarSourceModel::getDatasets() const +Datasets ScalarSourceModel::getDatasets() const { - return _datasets; + Datasets datasets; + + for (int rowIndex = DefaultRow::DatasetStart; rowIndex < rowCount(); ++rowIndex) + { + if (auto item = dynamic_cast(itemFromIndex(index(rowIndex, 0)))) { + const auto dataset = item->getScalarDataset(); + + if (dataset.isValid()) + datasets.append(dataset); + } + } + + return datasets; } Dataset ScalarSourceModel::getDataset(const std::int32_t& rowIndex) const { - // Return empty smart pointer when out of range - if (rowIndex < DefaultRow::DatasetStart || rowIndex > (DefaultRow::DatasetStart + _datasets.count())) - return Dataset(); + if (auto item = dynamic_cast(itemFromIndex(index(rowIndex, 0)))) + return item->getScalarDataset(); - // Subtract the constant point size row - return _datasets[rowIndex - DefaultRow::DatasetStart]; + return {}; } void ScalarSourceModel::setDatasets(const Datasets& datasets) { - // Notify others that the model layout is about to be changed - emit layoutAboutToBeChanged(); - - // Add datasets for (const auto& dataset : datasets) addDataset(dataset); +} + +std::int32_t ScalarSourceModel::getRowIndex(const Dataset& dataset) const +{ + if (!dataset.isValid()) + return -1; + + const auto matches = match(index(0, static_cast(Column::Id)), Qt::EditRole, dataset->getId(), 1, Qt::MatchExactly); - // Notify others that the model layout is changed - emit layoutChanged(); + if (!matches.isEmpty()) + return matches.first().row(); - // And update model data with datasets - updateData(); + return -1; } bool ScalarSourceModel::getShowFullPathName() const @@ -201,23 +248,4 @@ bool ScalarSourceModel::getShowFullPathName() const void ScalarSourceModel::setShowFullPathName(const bool& showFullPathName) { _showFullPathName = showFullPathName; - - updateData(); -} - -void ScalarSourceModel::updateData() -{ - // Update the datasets string list model - for (auto dataset : _datasets) { - - // Continue if the dataset is not valid - if (!dataset.isValid()) - continue; - - // Get dataset model index - const auto datasetModelIndex = index(_datasets.indexOf(dataset), 0); - - // Notify others that the data changed - emit dataChanged(datasetModelIndex, datasetModelIndex); - } } diff --git a/src/ScalarSourceModel.h b/src/ScalarSourceModel.h index 9fd86e7..b6eb766 100644 --- a/src/ScalarSourceModel.h +++ b/src/ScalarSourceModel.h @@ -2,7 +2,7 @@ #include "Dataset.h" -#include +#include using namespace mv; @@ -13,7 +13,7 @@ using namespace mv; * * @author Thomas Kroes */ -class ScalarSourceModel : public QAbstractListModel +class ScalarSourceModel : public QStandardItemModel { protected: @@ -21,6 +21,7 @@ class ScalarSourceModel : public QAbstractListModel ScalarSourceModel(QObject* parent = nullptr); public: + /** Default scalar options */ enum DefaultRow { Constant, /** Scale by constant */ @@ -28,36 +29,127 @@ class ScalarSourceModel : public QAbstractListModel DatasetStart /** Start row of the dataset(s) */ }; -public: + /** Model columns */ + enum class Column { + Name, /** Scalar dataset name */ + Id, /** Globally unique scalar dataset identifier */ - /** - * Get the number of row - * @param parent Parent model index - * @return Number of rows in the model - */ - int rowCount(const QModelIndex& parent = QModelIndex()) const; + Count + }; - /** - * Get the row index of a dataset - * @param parent Parent model index - * @return Row index of the dataset - */ - int rowIndex(const Dataset& dataset) const; +protected: - /** - * Get the number of columns - * @param parent Parent model index - * @return Number of columns in the model - */ - int columnCount(const QModelIndex& parent = QModelIndex()) const; + /** Base standard model item class for a dataset */ + class Item : public QStandardItem { + public: + + /** + * Construct with reference to \p scalarSourceModel and pointer to \p scalarDataset + * @param scalarSourceModel Reference to the scalar source model + * @param scalarDataset Pointer to scalar dataset (maybe nullptr) + */ + Item(const ScalarSourceModel& scalarSourceModel, const mv::Dataset& scalarDataset); + + /** + * Get model data for \p role + * @return Data for \p role in variant form + */ + QVariant data(int role = Qt::UserRole + 1) const override; + + /** + * Get reference to the scalar source model + * @return Reference to the scalar source model + */ + const ScalarSourceModel& getScalarSourceModel() const; + + /** + * Get the scalar dataset associated with this item (if any) + * @return Pointer to scalar dataset (maybe nullptr) + */ + const mv::Dataset<>& getScalarDataset() const; + + private: + const ScalarSourceModel& _scalarSourceModel; /** Reference to the scalar source model */ + mv::Dataset<> _scalarDataset; /** Pointer to scalar dataset (maybe nullptr) */ + }; - /** - * Get data - * @param index Model index to query - * @param role Data role - * @return Data - */ - QVariant data(const QModelIndex& index, int role) const; + /** Standard model item class for displaying the dataset GUI name */ + class NameItem final : public Item, public QObject { + public: + + /** + * Construct with reference to \p scalarSourceModel and pointer to \p scalarDataset + * @param scalarSourceModel Reference to the scalar source model + * @param scalarDataset Pointer to scalar dataset (maybe nullptr) + */ + NameItem(const ScalarSourceModel& scalarSourceModel, const mv::Dataset<>& scalarDataset); + + /** + * Get model data for \p role + * @return Data for \p role in variant form + */ + QVariant data(int role = Qt::UserRole + 1) const override; + + /** + * Get header data for \p orientation and \p role + * @param orientation Horizontal/vertical + * @param role Data role + * @return Header data + */ + static QVariant headerData(Qt::Orientation orientation, int role) { + switch (role) { + case Qt::DisplayRole: + case Qt::EditRole: + return "Name"; + + case Qt::ToolTipRole: + return "Dataset name"; + + default: + break; + } + + return {}; + } + }; + + /** Standard model item class for displaying the dataset GUI ID */ + class IdItem final : public Item { + public: + + /** No need for specialized constructor */ + using Item::Item; + + /** + * Get model data for \p role + * @return Data for \p role in variant form + */ + QVariant data(int role = Qt::UserRole + 1) const override; + + /** + * Get header data for \p orientation and \p role + * @param orientation Horizontal/vertical + * @param role Data role + * @return Header data + */ + static QVariant headerData(Qt::Orientation orientation, int role) { + switch (role) { + case Qt::DisplayRole: + case Qt::EditRole: + return "ID"; + + case Qt::ToolTipRole: + return "Dataset unique identifier"; + + default: + break; + } + + return {}; + } + }; + +public: /** * Add a dataset @@ -85,7 +177,7 @@ class ScalarSourceModel : public QAbstractListModel * Get datasets * @return Vector of smart pointers to datasets */ - const Datasets& getDatasets() const; + Datasets getDatasets() const; /** * Get dataset at the specified row index @@ -100,6 +192,13 @@ class ScalarSourceModel : public QAbstractListModel */ void setDatasets(const Datasets& datasets); + /** + * Get row index of the specified \p dataset + * @param dataset Smart pointer to dataset + * @return Row index of the dataset, or -1 if the dataset is not in the model + */ + std::int32_t getRowIndex(const Dataset& dataset) const; + /** Get whether to show the full path name in the GUI */ bool getShowFullPathName() const; @@ -109,11 +208,26 @@ class ScalarSourceModel : public QAbstractListModel */ void setShowFullPathName(const bool& showFullPathName); - /** Updates the model from the datasets */ - void updateData(); +protected: + + /** Convenience class for combining items in a row */ + class Row final : public QList + { + public: + + /** + * Construct with pointer to \p scalarDataset + * @param scalarDataset Pointer to scalar dataset (maybe nullptr) + */ + Row(const ScalarSourceModel& scalarSourceModel, const mv::Dataset<>& scalarDataset) : + QList() + { + append(new NameItem(scalarSourceModel, scalarDataset)); + append(new IdItem(scalarSourceModel, scalarDataset)); + } + }; protected: - Datasets _datasets; /** Datasets used to size the scatter plot points with */ bool _showFullPathName; /** Whether to show the full path name in the GUI */ friend class ScalarAction; From 8fbbe5fee4f4525c2615abad89c92db7559d2466 Mon Sep 17 00:00:00 2001 From: Thomas Kroes Date: Mon, 16 Feb 2026 14:59:40 +0100 Subject: [PATCH 04/33] Do not update the HUD during de-serialization, do it post-de-serialization. Put guard in updateHeadsUpDisplay --- src/ScatterplotPlugin.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/ScatterplotPlugin.cpp b/src/ScatterplotPlugin.cpp index 94d4826..5357bbc 100644 --- a/src/ScatterplotPlugin.cpp +++ b/src/ScatterplotPlugin.cpp @@ -288,6 +288,8 @@ ScatterplotPlugin::ScatterplotPlugin(const PluginFactory* factory) : getLearningCenterAction().addVideos(QStringList({ "Practitioner", "Developer" })); setOverlayActionsTargetWidget(_scatterPlotWidget); + + connect(&mv::projects(), &AbstractProjectManager::projectOpened, this, &ScatterplotPlugin::updateHeadsUpDisplay); } ScatterplotPlugin::~ScatterplotPlugin() @@ -1016,6 +1018,9 @@ void ScatterplotPlugin::updateSelection() void ScatterplotPlugin::updateHeadsUpDisplay() { + if (mv::projects().isOpeningProject()) + return; + getHeadsUpDisplayAction().removeAllHeadsUpDisplayItems(); if (_positionDataset.isValid()) { @@ -1037,6 +1042,7 @@ void ScatterplotPlugin::updateHeadsUpDisplay() } else { getHeadsUpDisplayAction().addHeadsUpDisplayItem("No datasets loaded", "", ""); } + } void ScatterplotPlugin::updateHeadsUpDisplayTextColor() @@ -1065,12 +1071,7 @@ void ScatterplotPlugin::fromVariantMap(const QVariantMap& variantMap) _primaryToolbarAction.fromParentVariantMap(variantMap); _settingsAction.fromParentVariantMap(variantMap); - updateHeadsUpDisplay(); - if (pointRenderer.getNavigator().getNavigationAction().getSerializationCountFrom() == 0) { - qDebug() << "Resetting view"; - - _scatterPlotWidget->update(); } From c1c8e06ee33e29bd5764b7320643ebf89dad96f2 Mon Sep 17 00:00:00 2001 From: Thomas Kroes Date: Mon, 16 Feb 2026 16:28:04 +0100 Subject: [PATCH 05/33] Properly sync HUD with loaded color, point size and point opacity dataset(s) --- src/ScatterplotPlugin.cpp | 36 +++++++++++++++++++++++++++++------- src/ScatterplotPlugin.h | 4 +++- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src/ScatterplotPlugin.cpp b/src/ScatterplotPlugin.cpp index 5357bbc..9adb621 100644 --- a/src/ScatterplotPlugin.cpp +++ b/src/ScatterplotPlugin.cpp @@ -344,9 +344,6 @@ void ScatterplotPlugin::init() connect(&_positionDataset, &Dataset<>::guiNameChanged, this, &ScatterplotPlugin::updateHeadsUpDisplay); const auto currentColorDatasetChanged = [this](Dataset currentColorDataset) -> void { - if (_colorDataset == currentColorDataset) - return; - if (_colorDataset.isValid()) disconnect(&_colorDataset, &Dataset<>::guiNameChanged, this, nullptr); @@ -362,8 +359,35 @@ void ScatterplotPlugin::init() currentColorDatasetChanged(_settingsAction.getColoringAction().getCurrentColorDataset()); }); - connect(&_settingsAction.getPlotAction().getPointPlotAction().getSizeAction(), &ScalarAction::sourceDataChanged, this, &ScatterplotPlugin::updateHeadsUpDisplay); - connect(&_settingsAction.getPlotAction().getPointPlotAction().getOpacityAction(), &ScalarAction::sourceDataChanged, this, &ScatterplotPlugin::updateHeadsUpDisplay); + const auto currentPointSizeDatasetChanged = [this]() -> void { + auto currentPointSizeDataset = _settingsAction.getPlotAction().getPointPlotAction().getSizeAction().getCurrentDataset(); + + if (_pointSizeDataset.isValid()) + disconnect(&_pointSizeDataset, &Dataset<>::guiNameChanged, this, nullptr); + + _pointSizeDataset = currentPointSizeDataset; + + connect(&_pointSizeDataset, &Dataset<>::guiNameChanged, this, &ScatterplotPlugin::updateHeadsUpDisplay); + + updateHeadsUpDisplay(); + }; + + connect(&_settingsAction.getPlotAction().getPointPlotAction().getSizeAction(), &ScalarAction::sourceSelectionChanged, this, currentPointSizeDatasetChanged); + + const auto currentPointOpacityDatasetChanged = [this]() -> void { + auto currentPointOpacityDataset = _settingsAction.getPlotAction().getPointPlotAction().getOpacityAction().getCurrentDataset(); + + if (_pointOpacityDataset.isValid()) + disconnect(&_pointOpacityDataset, &Dataset<>::guiNameChanged, this, nullptr); + + _pointOpacityDataset = currentPointOpacityDataset; + + connect(&_pointOpacityDataset, &Dataset<>::guiNameChanged, this, &ScatterplotPlugin::updateHeadsUpDisplay); + + updateHeadsUpDisplay(); + }; + + connect(&_settingsAction.getPlotAction().getPointPlotAction().getOpacityAction(), &ScalarAction::sourceSelectionChanged, this, currentPointOpacityDatasetChanged); connect(&_settingsAction.getMiscellaneousAction().getBackgroundColorAction(), &ColorAction::colorChanged, this, &ScatterplotPlugin::updateHeadsUpDisplayTextColor); @@ -1038,11 +1062,9 @@ void ScatterplotPlugin::updateHeadsUpDisplay() addMetaDataToHeadsUpDisplay("Size", _settingsAction.getPlotAction().getPointPlotAction().getSizeAction().getCurrentDataset(), datasetsItem); addMetaDataToHeadsUpDisplay("Opacity", _settingsAction.getPlotAction().getPointPlotAction().getOpacityAction().getCurrentDataset(), datasetsItem); - } else { getHeadsUpDisplayAction().addHeadsUpDisplayItem("No datasets loaded", "", ""); } - } void ScatterplotPlugin::updateHeadsUpDisplayTextColor() diff --git a/src/ScatterplotPlugin.h b/src/ScatterplotPlugin.h index 4155039..301364d 100644 --- a/src/ScatterplotPlugin.h +++ b/src/ScatterplotPlugin.h @@ -116,7 +116,9 @@ class ScatterplotPlugin : public ViewPlugin ScatterplotWidget* _scatterPlotWidget; /** The visualization widget */ Dataset _positionDataset; /** Smart pointer to points dataset for point position */ Dataset _positionSourceDataset; /** Smart pointer to source of the points dataset for point position (if any) */ - Dataset _colorDataset; /** Smart pointer to dataset used for coloring (if any) */ + Dataset _colorDataset; /** Smart pointer to dataset used for coloring (if any) */ + Dataset _pointSizeDataset; /** Smart pointer to dataset for driving point size (if any) */ + Dataset _pointOpacityDataset; /** Smart pointer to dataset for driving point opacity (if any) */ std::vector _positions; /** Point positions */ unsigned int _numPoints; /** Number of point positions */ SettingsAction _settingsAction; /** Group action for all settings */ From bfcab8b3df806adb630404df0dbeb2fe9e1624f1 Mon Sep 17 00:00:00 2001 From: Thomas Kroes Date: Mon, 16 Feb 2026 14:59:40 +0100 Subject: [PATCH 06/33] Do not update the HUD during de-serialization, do it post-de-serialization. Put guard in updateHeadsUpDisplay --- src/ScatterplotPlugin.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ScatterplotPlugin.cpp b/src/ScatterplotPlugin.cpp index 9adb621..2975b63 100644 --- a/src/ScatterplotPlugin.cpp +++ b/src/ScatterplotPlugin.cpp @@ -1065,6 +1065,7 @@ void ScatterplotPlugin::updateHeadsUpDisplay() } else { getHeadsUpDisplayAction().addHeadsUpDisplayItem("No datasets loaded", "", ""); } + } void ScatterplotPlugin::updateHeadsUpDisplayTextColor() From ec29018eba40cff549d4e7c0f31e9dc90278a20c Mon Sep 17 00:00:00 2001 From: Thomas Kroes Date: Mon, 16 Feb 2026 16:28:04 +0100 Subject: [PATCH 07/33] Properly sync HUD with loaded color, point size and point opacity dataset(s) --- src/ScatterplotPlugin.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ScatterplotPlugin.cpp b/src/ScatterplotPlugin.cpp index 2975b63..9adb621 100644 --- a/src/ScatterplotPlugin.cpp +++ b/src/ScatterplotPlugin.cpp @@ -1065,7 +1065,6 @@ void ScatterplotPlugin::updateHeadsUpDisplay() } else { getHeadsUpDisplayAction().addHeadsUpDisplayItem("No datasets loaded", "", ""); } - } void ScatterplotPlugin::updateHeadsUpDisplayTextColor() From 8a70eaac334a1c9fac449f34d434a822dfd4aa67 Mon Sep 17 00:00:00 2001 From: Thomas Kroes Date: Tue, 17 Feb 2026 09:55:00 +0100 Subject: [PATCH 08/33] Addd point size and opacity dataset picker actions and implement the synchronization logic --- src/DatasetsAction.cpp | 55 +++++++++++++++++++++++++++++++++++++++--- src/DatasetsAction.h | 4 +++ 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/src/DatasetsAction.cpp b/src/DatasetsAction.cpp index 3cc6381..247f338 100644 --- a/src/DatasetsAction.cpp +++ b/src/DatasetsAction.cpp @@ -14,7 +14,9 @@ DatasetsAction::DatasetsAction(QObject* parent, const QString& title) : GroupAction(parent, title), _scatterplotPlugin(dynamic_cast(parent->parent())), _positionDatasetPickerAction(this, "Position"), - _colorDatasetPickerAction(this, "Color") + _colorDatasetPickerAction(this, "Color"), + _pointSizeDatasetPickerAction(this, "Point size"), + _pointOpacityDatasetPickerAction(this, "Point opacity") { setIconByName("database"); setToolTip("Manage loaded datasets for position and color"); @@ -23,15 +25,24 @@ DatasetsAction::DatasetsAction(QObject* parent, const QString& title) : addAction(&_positionDatasetPickerAction); addAction(&_colorDatasetPickerAction); + addAction(&_pointSizeDatasetPickerAction); + addAction(&_pointOpacityDatasetPickerAction); _positionDatasetPickerAction.setFilterFunction([this](mv::Dataset dataset) -> bool { return dataset->getDataType() == PointType; - }); + }); _colorDatasetPickerAction.setFilterFunction([this](mv::Dataset dataset) -> bool { return (dataset->getDataType() == PointType || dataset->getDataType() == ColorType || dataset->getDataType() == ClusterType); - }); + }); + + _pointSizeDatasetPickerAction.setFilterFunction([this](mv::Dataset dataset) -> bool { + return dataset->getDataType() == PointType; + }); + _pointOpacityDatasetPickerAction.setFilterFunction([this](mv::Dataset dataset) -> bool { + return dataset->getDataType() == PointType; + }); auto scatterplotPlugin = dynamic_cast(parent->parent()); @@ -53,6 +64,44 @@ DatasetsAction::DatasetsAction(QObject* parent, const QString& title) : connect(&scatterplotPlugin->getSettingsAction().getColoringAction(), &ColoringAction::currentColorDatasetChanged, this, [this](Dataset currentColorDataset) -> void { _colorDatasetPickerAction.setCurrentDataset(currentColorDataset); }); + + auto& pointPlotAction = scatterplotPlugin->getSettingsAction().getPlotAction().getPointPlotAction(); + auto& pointSizeAction = pointPlotAction.getSizeAction(); + auto& pointOpacityAction = pointPlotAction.getOpacityAction(); + + const auto pointSizeSourceChanged = [this, &pointSizeAction]() -> void { + _pointSizeDatasetPickerAction.setCurrentDataset(pointSizeAction.isSourceDataset() ? pointSizeAction.getCurrentDataset() : nullptr); + + if (!pointSizeAction.isSourceDataset()) + _pointSizeDatasetPickerAction.setCurrentIndex(-1); + }; + + connect(&pointSizeAction, &ScalarAction::sourceSelectionChanged, this, pointSizeSourceChanged); + connect(&pointSizeAction, &ScalarAction::sourceDataChanged, this, pointSizeSourceChanged); + + connect(&_pointSizeDatasetPickerAction, &DatasetPickerAction::datasetPicked, this, [this, &pointSizeAction](Dataset dataset) -> void { + if (!dataset.isValid()) + return; + + pointSizeAction.setCurrentDataset(dataset); + }); + + const auto pointOpacitySourceChanged = [this, &pointOpacityAction]() -> void { + _pointOpacityDatasetPickerAction.setCurrentDataset(pointOpacityAction.isSourceDataset() ? pointOpacityAction.getCurrentDataset() : nullptr); + + if (!pointOpacityAction.isSourceDataset()) + _pointOpacityDatasetPickerAction.setCurrentIndex(-1); + }; + + connect(&pointOpacityAction, &ScalarAction::sourceSelectionChanged, this, pointOpacitySourceChanged); + connect(&pointOpacityAction, &ScalarAction::sourceDataChanged, this, pointOpacitySourceChanged); + + connect(&_pointOpacityDatasetPickerAction, &DatasetPickerAction::datasetPicked, this, [this, &pointOpacityAction](Dataset dataset) -> void { + if (!dataset.isValid()) + return; + + pointOpacityAction.setCurrentDataset(dataset); + }); } void DatasetsAction::connectToPublicAction(WidgetAction* publicAction, bool recursive) diff --git a/src/DatasetsAction.h b/src/DatasetsAction.h index 1730a06..db10a3e 100644 --- a/src/DatasetsAction.h +++ b/src/DatasetsAction.h @@ -53,11 +53,15 @@ class DatasetsAction : public GroupAction DatasetPickerAction& getPositionDatasetPickerAction() { return _positionDatasetPickerAction; } DatasetPickerAction& getColorDatasetPickerAction() { return _colorDatasetPickerAction; } + DatasetPickerAction& getPointSizeDatasetPickerAction() { return _pointSizeDatasetPickerAction; } + DatasetPickerAction& getPointOpacityDatasetPickerAction() { return _pointOpacityDatasetPickerAction; } private: ScatterplotPlugin* _scatterplotPlugin; /** Pointer to scatter plot plugin */ DatasetPickerAction _positionDatasetPickerAction; /** Dataset picker action for position dataset */ DatasetPickerAction _colorDatasetPickerAction; /** Dataset picker action for color dataset */ + DatasetPickerAction _pointSizeDatasetPickerAction; /** Dataset picker action for point size */ + DatasetPickerAction _pointOpacityDatasetPickerAction; /** Dataset picker action for point opacity */ friend class mv::AbstractActionsManager; }; From 3c65a7e6cf743bc2dfb3f52b2c3eeaeb4b89bda5 Mon Sep 17 00:00:00 2001 From: Thomas Kroes Date: Tue, 17 Feb 2026 09:56:21 +0100 Subject: [PATCH 09/33] Add connection logic --- src/DatasetsAction.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/DatasetsAction.cpp b/src/DatasetsAction.cpp index 247f338..095442d 100644 --- a/src/DatasetsAction.cpp +++ b/src/DatasetsAction.cpp @@ -116,6 +116,8 @@ void DatasetsAction::connectToPublicAction(WidgetAction* publicAction, bool recu if (recursive) { actions().connectPrivateActionToPublicAction(&_positionDatasetPickerAction, &publicDatasetsAction->getPositionDatasetPickerAction(), recursive); actions().connectPrivateActionToPublicAction(&_colorDatasetPickerAction, &publicDatasetsAction->getColorDatasetPickerAction(), recursive); + actions().connectPrivateActionToPublicAction(&_pointSizeDatasetPickerAction, &publicDatasetsAction->getPointSizeDatasetPickerAction(), recursive); + actions().connectPrivateActionToPublicAction(&_pointOpacityDatasetPickerAction, &publicDatasetsAction->getPointOpacityDatasetPickerAction(), recursive); } GroupAction::connectToPublicAction(publicAction, recursive); @@ -129,6 +131,8 @@ void DatasetsAction::disconnectFromPublicAction(bool recursive) if (recursive) { actions().disconnectPrivateActionFromPublicAction(&_positionDatasetPickerAction, recursive); actions().disconnectPrivateActionFromPublicAction(&_colorDatasetPickerAction, recursive); + actions().disconnectPrivateActionFromPublicAction(&_pointSizeDatasetPickerAction, recursive); + actions().disconnectPrivateActionFromPublicAction(&_pointOpacityDatasetPickerAction, recursive); } GroupAction::disconnectFromPublicAction(recursive); From 18c339ea846409271d52dc777f5d5358e064c2ea Mon Sep 17 00:00:00 2001 From: Thomas Kroes Date: Tue, 17 Feb 2026 10:37:42 +0100 Subject: [PATCH 10/33] Fix point size and opacity dataset selection reset logic --- src/DatasetsAction.cpp | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/DatasetsAction.cpp b/src/DatasetsAction.cpp index 095442d..e77793e 100644 --- a/src/DatasetsAction.cpp +++ b/src/DatasetsAction.cpp @@ -28,6 +28,10 @@ DatasetsAction::DatasetsAction(QObject* parent, const QString& title) : addAction(&_pointSizeDatasetPickerAction); addAction(&_pointOpacityDatasetPickerAction); + _colorDatasetPickerAction.setDefaultWidgetFlag(OptionAction::Clearable); + _pointSizeDatasetPickerAction.setDefaultWidgetFlag(OptionAction::Clearable); + _pointOpacityDatasetPickerAction.setDefaultWidgetFlag(OptionAction::Clearable); + _positionDatasetPickerAction.setFilterFunction([this](mv::Dataset dataset) -> bool { return dataset->getDataType() == PointType; }); @@ -56,9 +60,12 @@ DatasetsAction::DatasetsAction(QObject* parent, const QString& title) : connect(&scatterplotPlugin->getPositionDataset(), &Dataset::changed, this, [this](DatasetImpl* dataset) -> void { _positionDatasetPickerAction.setCurrentDataset(dataset); }); - - connect(&_colorDatasetPickerAction, &DatasetPickerAction::datasetPicked, [this, scatterplotPlugin](Dataset pickedDataset) -> void { - scatterplotPlugin->getSettingsAction().getColoringAction().setCurrentColorDataset(pickedDataset); + + auto& coloringAction = scatterplotPlugin->getSettingsAction().getColoringAction(); + + connect(&_colorDatasetPickerAction, &DatasetPickerAction::datasetPicked, [this, &coloringAction](Dataset pickedDataset) -> void { + coloringAction.getColorByAction().setCurrentIndex(pickedDataset.isValid() ? 2 : 0); + coloringAction.setCurrentColorDataset(pickedDataset); }); connect(&scatterplotPlugin->getSettingsAction().getColoringAction(), &ColoringAction::currentColorDatasetChanged, this, [this](Dataset currentColorDataset) -> void { @@ -79,11 +86,11 @@ DatasetsAction::DatasetsAction(QObject* parent, const QString& title) : connect(&pointSizeAction, &ScalarAction::sourceSelectionChanged, this, pointSizeSourceChanged); connect(&pointSizeAction, &ScalarAction::sourceDataChanged, this, pointSizeSourceChanged); - connect(&_pointSizeDatasetPickerAction, &DatasetPickerAction::datasetPicked, this, [this, &pointSizeAction](Dataset dataset) -> void { - if (!dataset.isValid()) - return; + connect(&_pointSizeDatasetPickerAction, &DatasetPickerAction::currentIndexChanged, this, [this, &pointSizeAction](const int32_t& currentIndex) -> void { + pointSizeAction.setCurrentDataset(_pointSizeDatasetPickerAction.getCurrentDataset()); - pointSizeAction.setCurrentDataset(dataset); + if (currentIndex < 0) + pointSizeAction.setCurrentSourceIndex(ScalarSourceModel::DefaultRow::Constant); }); const auto pointOpacitySourceChanged = [this, &pointOpacityAction]() -> void { @@ -96,11 +103,11 @@ DatasetsAction::DatasetsAction(QObject* parent, const QString& title) : connect(&pointOpacityAction, &ScalarAction::sourceSelectionChanged, this, pointOpacitySourceChanged); connect(&pointOpacityAction, &ScalarAction::sourceDataChanged, this, pointOpacitySourceChanged); - connect(&_pointOpacityDatasetPickerAction, &DatasetPickerAction::datasetPicked, this, [this, &pointOpacityAction](Dataset dataset) -> void { - if (!dataset.isValid()) - return; + connect(&_pointOpacityDatasetPickerAction, &DatasetPickerAction::currentIndexChanged, this, [this, &pointOpacityAction](const int32_t& currentIndex) -> void { + pointOpacityAction.setCurrentDataset(_pointOpacityDatasetPickerAction.getCurrentDataset()); - pointOpacityAction.setCurrentDataset(dataset); + if (currentIndex < 0) + pointOpacityAction.setCurrentSourceIndex(ScalarSourceModel::DefaultRow::Constant); }); } From c5929319b7588858e099d2ba2ada589da98c9b2b Mon Sep 17 00:00:00 2001 From: Thomas Kroes Date: Tue, 17 Feb 2026 16:29:18 +0100 Subject: [PATCH 11/33] Avoid setting color dataset during project open Only call setCurrentColorDataset when the picked dataset is valid and a project is not currently opening. Previously the code unconditionally updated the current color dataset in the DatasetPickerAction handler; this change prevents dataset changes from being applied while a project load is in progress, avoiding potential race conditions or invalid state during project open. --- src/DatasetsAction.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/DatasetsAction.cpp b/src/DatasetsAction.cpp index e77793e..c4ffbe0 100644 --- a/src/DatasetsAction.cpp +++ b/src/DatasetsAction.cpp @@ -65,7 +65,9 @@ DatasetsAction::DatasetsAction(QObject* parent, const QString& title) : connect(&_colorDatasetPickerAction, &DatasetPickerAction::datasetPicked, [this, &coloringAction](Dataset pickedDataset) -> void { coloringAction.getColorByAction().setCurrentIndex(pickedDataset.isValid() ? 2 : 0); - coloringAction.setCurrentColorDataset(pickedDataset); + + if (pickedDataset.isValid() && !mv::projects().isOpeningProject()) + coloringAction.setCurrentColorDataset(pickedDataset); }); connect(&scatterplotPlugin->getSettingsAction().getColoringAction(), &ColoringAction::currentColorDatasetChanged, this, [this](Dataset currentColorDataset) -> void { From ed65894a423133a2ac4b5c5764502b7e5d14e234 Mon Sep 17 00:00:00 2001 From: Thomas Kroes Date: Wed, 18 Feb 2026 10:52:29 +0100 Subject: [PATCH 12/33] Move dataset tracking into DatasetsAction Refactor dataset handling by moving color/size/opacity dataset setup and tracking out of ScatterplotPlugin into DatasetsAction. Add setup helper methods, getters, serialization (from/to QVariantMap), and connect/disconnect logic for public actions. Update ScatterplotPlugin to use the SettingsAction's coloring state when building the HUD and remove redundant dataset members and signal handlers from the plugin. This improves separation of concerns and centralizes dataset-related UI wiring in DatasetsAction. --- src/DatasetsAction.cpp | 205 ++++++++++++++++++++++++++------------ src/DatasetsAction.h | 26 +++-- src/ScatterplotPlugin.cpp | 52 +--------- src/ScatterplotPlugin.h | 3 - 4 files changed, 165 insertions(+), 121 deletions(-) diff --git a/src/DatasetsAction.cpp b/src/DatasetsAction.cpp index c4ffbe0..4b8f02a 100644 --- a/src/DatasetsAction.cpp +++ b/src/DatasetsAction.cpp @@ -32,27 +32,74 @@ DatasetsAction::DatasetsAction(QObject* parent, const QString& title) : _pointSizeDatasetPickerAction.setDefaultWidgetFlag(OptionAction::Clearable); _pointOpacityDatasetPickerAction.setDefaultWidgetFlag(OptionAction::Clearable); - _positionDatasetPickerAction.setFilterFunction([this](mv::Dataset dataset) -> bool { - return dataset->getDataType() == PointType; - }); + auto scatterplotPlugin = dynamic_cast(parent->parent()); - _colorDatasetPickerAction.setFilterFunction([this](mv::Dataset dataset) -> bool { - return (dataset->getDataType() == PointType || dataset->getDataType() == ColorType || dataset->getDataType() == ClusterType); - }); + Q_ASSERT(scatterplotPlugin); - _pointSizeDatasetPickerAction.setFilterFunction([this](mv::Dataset dataset) -> bool { - return dataset->getDataType() == PointType; - }); + if (scatterplotPlugin == nullptr) + return; - _pointOpacityDatasetPickerAction.setFilterFunction([this](mv::Dataset dataset) -> bool { - return dataset->getDataType() == PointType; - }); + setupPositionDatasetPickerAction(scatterplotPlugin); +} - auto scatterplotPlugin = dynamic_cast(parent->parent()); +void DatasetsAction::connectToPublicAction(WidgetAction* publicAction, bool recursive) +{ + auto publicDatasetsAction = dynamic_cast(publicAction); - if (scatterplotPlugin == nullptr) + Q_ASSERT(publicDatasetsAction != nullptr); + + if (publicDatasetsAction == nullptr) + return; + + if (recursive) { + actions().connectPrivateActionToPublicAction(&_positionDatasetPickerAction, &publicDatasetsAction->getPositionDatasetPickerAction(), recursive); + actions().connectPrivateActionToPublicAction(&_colorDatasetPickerAction, &publicDatasetsAction->getColorDatasetPickerAction(), recursive); + actions().connectPrivateActionToPublicAction(&_pointSizeDatasetPickerAction, &publicDatasetsAction->getPointSizeDatasetPickerAction(), recursive); + actions().connectPrivateActionToPublicAction(&_pointOpacityDatasetPickerAction, &publicDatasetsAction->getPointOpacityDatasetPickerAction(), recursive); + } + + GroupAction::connectToPublicAction(publicAction, recursive); +} + +void DatasetsAction::disconnectFromPublicAction(bool recursive) +{ + if (!isConnected()) return; + if (recursive) { + actions().disconnectPrivateActionFromPublicAction(&_positionDatasetPickerAction, recursive); + actions().disconnectPrivateActionFromPublicAction(&_colorDatasetPickerAction, recursive); + actions().disconnectPrivateActionFromPublicAction(&_pointSizeDatasetPickerAction, recursive); + actions().disconnectPrivateActionFromPublicAction(&_pointOpacityDatasetPickerAction, recursive); + } + + GroupAction::disconnectFromPublicAction(recursive); +} + +void DatasetsAction::fromVariantMap(const QVariantMap& variantMap) +{ + GroupAction::fromVariantMap(variantMap); + + _positionDatasetPickerAction.fromParentVariantMap(variantMap); + _colorDatasetPickerAction.fromParentVariantMap(variantMap); +} + +QVariantMap DatasetsAction::toVariantMap() const +{ + auto variantMap = GroupAction::toVariantMap(); + + _positionDatasetPickerAction.insertIntoVariantMap(variantMap); + _colorDatasetPickerAction.insertIntoVariantMap(variantMap); + + return variantMap; +} + +void DatasetsAction::setupPositionDatasetPickerAction(ScatterplotPlugin* scatterplotPlugin) +{ + _positionDatasetPickerAction.setFilterFunction([this](mv::Dataset dataset) -> bool { + return dataset->getDataType() == PointType; + }); + connect(&_positionDatasetPickerAction, &DatasetPickerAction::datasetPicked, [this, scatterplotPlugin](Dataset pickedDataset) -> void { scatterplotPlugin->getPositionDataset() = pickedDataset; }); @@ -60,30 +107,70 @@ DatasetsAction::DatasetsAction(QObject* parent, const QString& title) : connect(&scatterplotPlugin->getPositionDataset(), &Dataset::changed, this, [this](DatasetImpl* dataset) -> void { _positionDatasetPickerAction.setCurrentDataset(dataset); }); +} - auto& coloringAction = scatterplotPlugin->getSettingsAction().getColoringAction(); +void DatasetsAction::setupColorDatasetPickerAction(ScatterplotPlugin* scatterplotPlugin) +{ + auto& settingsAction = scatterplotPlugin->getSettingsAction(); + + _colorDatasetPickerAction.setFilterFunction([this](mv::Dataset dataset) -> bool { + return (dataset->getDataType() == PointType || dataset->getDataType() == ColorType || dataset->getDataType() == ClusterType); + }); + + /* + auto& coloringAction = settingsAction.getColoringAction(); connect(&_colorDatasetPickerAction, &DatasetPickerAction::datasetPicked, [this, &coloringAction](Dataset pickedDataset) -> void { coloringAction.getColorByAction().setCurrentIndex(pickedDataset.isValid() ? 2 : 0); if (pickedDataset.isValid() && !mv::projects().isOpeningProject()) - coloringAction.setCurrentColorDataset(pickedDataset); - }); - - connect(&scatterplotPlugin->getSettingsAction().getColoringAction(), &ColoringAction::currentColorDatasetChanged, this, [this](Dataset currentColorDataset) -> void { + coloringAction.setCurrentColorDataset(pickedDataset); + }); + + connect(&settingsAction.getColoringAction(), &ColoringAction::currentColorDatasetChanged, this, [this](Dataset currentColorDataset) -> void { _colorDatasetPickerAction.setCurrentDataset(currentColorDataset); + }); + + const auto currentColorDatasetChanged = [this](Dataset currentColorDataset) -> void { + if (_colorDataset.isValid()) + disconnect(&_colorDataset, &Dataset<>::guiNameChanged, this, nullptr); + + _colorDataset = currentColorDataset; + + connect(&_colorDataset, &Dataset<>::guiNameChanged, this, &ScatterplotPlugin::updateHeadsUpDisplay); + + //updateHeadsUpDisplay(); + }; + + connect(&settingsAction.getColoringAction(), &ColoringAction::currentColorDatasetChanged, this, currentColorDatasetChanged); + + connect(&settingsAction.getColoringAction().getColorByAction(), &OptionAction::currentIndexChanged, this, [this, &settingsAction, currentColorDatasetChanged](const std::int32_t& currentIndex) -> void { + _colorDataset = settingsAction.getColoringAction().getCurrentColorDataset(); + + updateHeadsUpDisplay(); }); + */ +} - auto& pointPlotAction = scatterplotPlugin->getSettingsAction().getPlotAction().getPointPlotAction(); - auto& pointSizeAction = pointPlotAction.getSizeAction(); - auto& pointOpacityAction = pointPlotAction.getOpacityAction(); +void DatasetsAction::setupPointSizeDataset(ScatterplotPlugin* scatterplotPlugin) +{ + auto& settingsAction = scatterplotPlugin->getSettingsAction(); + + _pointSizeDatasetPickerAction.setFilterFunction([this](mv::Dataset dataset) -> bool { + return dataset->getDataType() == PointType; + }); + + /* + auto& pointPlotAction = scatterplotPlugin->getSettingsAction().getPlotAction().getPointPlotAction(); + auto& pointSizeAction = pointPlotAction.getSizeAction(); + auto& pointOpacityAction = pointPlotAction.getOpacityAction(); const auto pointSizeSourceChanged = [this, &pointSizeAction]() -> void { _pointSizeDatasetPickerAction.setCurrentDataset(pointSizeAction.isSourceDataset() ? pointSizeAction.getCurrentDataset() : nullptr); if (!pointSizeAction.isSourceDataset()) _pointSizeDatasetPickerAction.setCurrentIndex(-1); - }; + }; connect(&pointSizeAction, &ScalarAction::sourceSelectionChanged, this, pointSizeSourceChanged); connect(&pointSizeAction, &ScalarAction::sourceDataChanged, this, pointSizeSourceChanged); @@ -93,14 +180,14 @@ DatasetsAction::DatasetsAction(QObject* parent, const QString& title) : if (currentIndex < 0) pointSizeAction.setCurrentSourceIndex(ScalarSourceModel::DefaultRow::Constant); - }); + }); const auto pointOpacitySourceChanged = [this, &pointOpacityAction]() -> void { _pointOpacityDatasetPickerAction.setCurrentDataset(pointOpacityAction.isSourceDataset() ? pointOpacityAction.getCurrentDataset() : nullptr); if (!pointOpacityAction.isSourceDataset()) _pointOpacityDatasetPickerAction.setCurrentIndex(-1); - }; + }; connect(&pointOpacityAction, &ScalarAction::sourceSelectionChanged, this, pointOpacitySourceChanged); connect(&pointOpacityAction, &ScalarAction::sourceDataChanged, this, pointOpacitySourceChanged); @@ -110,57 +197,47 @@ DatasetsAction::DatasetsAction(QObject* parent, const QString& title) : if (currentIndex < 0) pointOpacityAction.setCurrentSourceIndex(ScalarSourceModel::DefaultRow::Constant); - }); -} + }); -void DatasetsAction::connectToPublicAction(WidgetAction* publicAction, bool recursive) -{ - auto publicDatasetsAction = dynamic_cast(publicAction); - Q_ASSERT(publicDatasetsAction != nullptr); - if (publicDatasetsAction == nullptr) - return; + const auto currentPointSizeDatasetChanged = [this]() -> void { + auto currentPointSizeDataset = _settingsAction.getPlotAction().getPointPlotAction().getSizeAction().getCurrentDataset(); - if (recursive) { - actions().connectPrivateActionToPublicAction(&_positionDatasetPickerAction, &publicDatasetsAction->getPositionDatasetPickerAction(), recursive); - actions().connectPrivateActionToPublicAction(&_colorDatasetPickerAction, &publicDatasetsAction->getColorDatasetPickerAction(), recursive); - actions().connectPrivateActionToPublicAction(&_pointSizeDatasetPickerAction, &publicDatasetsAction->getPointSizeDatasetPickerAction(), recursive); - actions().connectPrivateActionToPublicAction(&_pointOpacityDatasetPickerAction, &publicDatasetsAction->getPointOpacityDatasetPickerAction(), recursive); - } + if (_pointSizeDataset.isValid()) + disconnect(&_pointSizeDataset, &Dataset<>::guiNameChanged, this, nullptr); - GroupAction::connectToPublicAction(publicAction, recursive); -} + _pointSizeDataset = currentPointSizeDataset; -void DatasetsAction::disconnectFromPublicAction(bool recursive) -{ - if (!isConnected()) - return; + connect(&_pointSizeDataset, &Dataset<>::guiNameChanged, this, &ScatterplotPlugin::updateHeadsUpDisplay); - if (recursive) { - actions().disconnectPrivateActionFromPublicAction(&_positionDatasetPickerAction, recursive); - actions().disconnectPrivateActionFromPublicAction(&_colorDatasetPickerAction, recursive); - actions().disconnectPrivateActionFromPublicAction(&_pointSizeDatasetPickerAction, recursive); - actions().disconnectPrivateActionFromPublicAction(&_pointOpacityDatasetPickerAction, recursive); - } + updateHeadsUpDisplay(); + }; - GroupAction::disconnectFromPublicAction(recursive); -} + connect(&_settingsAction.getPlotAction().getPointPlotAction().getSizeAction(), &ScalarAction::sourceSelectionChanged, this, currentPointSizeDatasetChanged); -void DatasetsAction::fromVariantMap(const QVariantMap& variantMap) -{ - GroupAction::fromVariantMap(variantMap); + const auto currentPointOpacityDatasetChanged = [this]() -> void { + auto currentPointOpacityDataset = _settingsAction.getPlotAction().getPointPlotAction().getOpacityAction().getCurrentDataset(); - _positionDatasetPickerAction.fromParentVariantMap(variantMap); - _colorDatasetPickerAction.fromParentVariantMap(variantMap); + if (_pointOpacityDataset.isValid()) + disconnect(&_pointOpacityDataset, &Dataset<>::guiNameChanged, this, nullptr); + + _pointOpacityDataset = currentPointOpacityDataset; + + connect(&_pointOpacityDataset, &Dataset<>::guiNameChanged, this, &ScatterplotPlugin::updateHeadsUpDisplay); + + updateHeadsUpDisplay(); + }; + + connect(&_settingsAction.getPlotAction().getPointPlotAction().getOpacityAction(), &ScalarAction::sourceSelectionChanged, this, currentPointOpacityDatasetChanged);*/ } -QVariantMap DatasetsAction::toVariantMap() const +void DatasetsAction::setupPointOpacityDataset(ScatterplotPlugin* scatterplotPlugin) { - auto variantMap = GroupAction::toVariantMap(); + auto& settingsAction = scatterplotPlugin->getSettingsAction(); - _positionDatasetPickerAction.insertIntoVariantMap(variantMap); - _colorDatasetPickerAction.insertIntoVariantMap(variantMap); + _pointOpacityDatasetPickerAction.setFilterFunction([this](mv::Dataset dataset) -> bool { + return dataset->getDataType() == PointType; + }); - return variantMap; -} \ No newline at end of file +} diff --git a/src/DatasetsAction.h b/src/DatasetsAction.h index db10a3e..921965e 100644 --- a/src/DatasetsAction.h +++ b/src/DatasetsAction.h @@ -20,6 +20,10 @@ class DatasetsAction : public GroupAction */ Q_INVOKABLE DatasetsAction(QObject* parent, const QString& title); + mv::Dataset getColorDataset() { return _colorDataset; } + mv::Dataset getPointSizeDataset() { return _pointSizeDataset; } + mv::Dataset getPointOpacityDataset() { return _pointOpacityDataset; } + protected: // Linking /** @@ -39,7 +43,7 @@ class DatasetsAction : public GroupAction /** * Load widget action from variant map - * @param Variant map representation of the widget action + * @param variantMap Variant map representation of the widget action */ void fromVariantMap(const QVariantMap& variantMap) override; @@ -49,6 +53,13 @@ class DatasetsAction : public GroupAction */ QVariantMap toVariantMap() const override; +private: + + void setupPositionDatasetPickerAction(ScatterplotPlugin* scatterplotPlugin); /** Set up the position dataset picker action */ + void setupColorDatasetPickerAction(ScatterplotPlugin* scatterplotPlugin); /** Set up the color dataset picker action */ + void setupPointSizeDataset(ScatterplotPlugin* scatterplotPlugin); /** Set up the point size dataset picker action */ + void setupPointOpacityDataset(ScatterplotPlugin* scatterplotPlugin); /** Set up the point opacity dataset picker action */ + public: // Action getters DatasetPickerAction& getPositionDatasetPickerAction() { return _positionDatasetPickerAction; } @@ -57,11 +68,14 @@ class DatasetsAction : public GroupAction DatasetPickerAction& getPointOpacityDatasetPickerAction() { return _pointOpacityDatasetPickerAction; } private: - ScatterplotPlugin* _scatterplotPlugin; /** Pointer to scatter plot plugin */ - DatasetPickerAction _positionDatasetPickerAction; /** Dataset picker action for position dataset */ - DatasetPickerAction _colorDatasetPickerAction; /** Dataset picker action for color dataset */ - DatasetPickerAction _pointSizeDatasetPickerAction; /** Dataset picker action for point size */ - DatasetPickerAction _pointOpacityDatasetPickerAction; /** Dataset picker action for point opacity */ + ScatterplotPlugin* _scatterplotPlugin; /** Pointer to scatter plot plugin */ + DatasetPickerAction _positionDatasetPickerAction; /** Dataset picker action for position dataset */ + DatasetPickerAction _colorDatasetPickerAction; /** Dataset picker action for color dataset */ + DatasetPickerAction _pointSizeDatasetPickerAction; /** Dataset picker action for point size */ + DatasetPickerAction _pointOpacityDatasetPickerAction; /** Dataset picker action for point opacity */ + mv::Dataset _colorDataset; /** Smart pointer to dataset used for coloring (if any) */ + mv::Dataset _pointSizeDataset; /** Smart pointer to dataset for driving point size (if any) */ + mv::Dataset _pointOpacityDataset; /** Smart pointer to dataset for driving point opacity (if any) */ friend class mv::AbstractActionsManager; }; diff --git a/src/ScatterplotPlugin.cpp b/src/ScatterplotPlugin.cpp index 9adb621..8bf3f94 100644 --- a/src/ScatterplotPlugin.cpp +++ b/src/ScatterplotPlugin.cpp @@ -343,52 +343,6 @@ void ScatterplotPlugin::init() connect(&_positionDataset, &Dataset::dataSelectionChanged, this, &ScatterplotPlugin::updateSelection); connect(&_positionDataset, &Dataset<>::guiNameChanged, this, &ScatterplotPlugin::updateHeadsUpDisplay); - const auto currentColorDatasetChanged = [this](Dataset currentColorDataset) -> void { - if (_colorDataset.isValid()) - disconnect(&_colorDataset, &Dataset<>::guiNameChanged, this, nullptr); - - _colorDataset = currentColorDataset; - - connect(&_colorDataset, &Dataset<>::guiNameChanged, this, &ScatterplotPlugin::updateHeadsUpDisplay); - - updateHeadsUpDisplay(); - }; - - connect(&_settingsAction.getColoringAction(), &ColoringAction::currentColorDatasetChanged, this, currentColorDatasetChanged); - connect(&_settingsAction.getColoringAction().getColorByAction(), &OptionAction::currentIndexChanged, this, [this, currentColorDatasetChanged](const std::int32_t& currentIndex) -> void { - currentColorDatasetChanged(_settingsAction.getColoringAction().getCurrentColorDataset()); - }); - - const auto currentPointSizeDatasetChanged = [this]() -> void { - auto currentPointSizeDataset = _settingsAction.getPlotAction().getPointPlotAction().getSizeAction().getCurrentDataset(); - - if (_pointSizeDataset.isValid()) - disconnect(&_pointSizeDataset, &Dataset<>::guiNameChanged, this, nullptr); - - _pointSizeDataset = currentPointSizeDataset; - - connect(&_pointSizeDataset, &Dataset<>::guiNameChanged, this, &ScatterplotPlugin::updateHeadsUpDisplay); - - updateHeadsUpDisplay(); - }; - - connect(&_settingsAction.getPlotAction().getPointPlotAction().getSizeAction(), &ScalarAction::sourceSelectionChanged, this, currentPointSizeDatasetChanged); - - const auto currentPointOpacityDatasetChanged = [this]() -> void { - auto currentPointOpacityDataset = _settingsAction.getPlotAction().getPointPlotAction().getOpacityAction().getCurrentDataset(); - - if (_pointOpacityDataset.isValid()) - disconnect(&_pointOpacityDataset, &Dataset<>::guiNameChanged, this, nullptr); - - _pointOpacityDataset = currentPointOpacityDataset; - - connect(&_pointOpacityDataset, &Dataset<>::guiNameChanged, this, &ScatterplotPlugin::updateHeadsUpDisplay); - - updateHeadsUpDisplay(); - }; - - connect(&_settingsAction.getPlotAction().getPointPlotAction().getOpacityAction(), &ScalarAction::sourceSelectionChanged, this, currentPointOpacityDatasetChanged); - connect(&_settingsAction.getMiscellaneousAction().getBackgroundColorAction(), &ColorAction::colorChanged, this, &ScatterplotPlugin::updateHeadsUpDisplayTextColor); connect(&getScatterplotWidget().getPointRendererNavigator().getNavigationAction().getZoomSelectionAction(), &TriggerAction::triggered, this, [this]() -> void { @@ -1047,6 +1001,8 @@ void ScatterplotPlugin::updateHeadsUpDisplay() getHeadsUpDisplayAction().removeAllHeadsUpDisplayItems(); + auto& coloringAction = _settingsAction.getColoringAction(); + if (_positionDataset.isValid()) { const auto datasetsItem = getHeadsUpDisplayAction().addHeadsUpDisplayItem("Datasets", "", ""); @@ -1057,8 +1013,8 @@ void ScatterplotPlugin::updateHeadsUpDisplay() getHeadsUpDisplayAction().addHeadsUpDisplayItem(QString("%1 by:").arg(metaDataName), data->getGuiName(), "", itemPtr); }; - if (_settingsAction.getColoringAction().getColorByAction().getCurrentIndex() >= 2) - addMetaDataToHeadsUpDisplay("Color", _colorDataset, datasetsItem); + if (coloringAction.getColorByAction().getCurrentIndex() >= 2) + addMetaDataToHeadsUpDisplay("Color", coloringAction.getCurrentColorDataset(), datasetsItem); addMetaDataToHeadsUpDisplay("Size", _settingsAction.getPlotAction().getPointPlotAction().getSizeAction().getCurrentDataset(), datasetsItem); addMetaDataToHeadsUpDisplay("Opacity", _settingsAction.getPlotAction().getPointPlotAction().getOpacityAction().getCurrentDataset(), datasetsItem); diff --git a/src/ScatterplotPlugin.h b/src/ScatterplotPlugin.h index 301364d..e795c72 100644 --- a/src/ScatterplotPlugin.h +++ b/src/ScatterplotPlugin.h @@ -116,9 +116,6 @@ class ScatterplotPlugin : public ViewPlugin ScatterplotWidget* _scatterPlotWidget; /** The visualization widget */ Dataset _positionDataset; /** Smart pointer to points dataset for point position */ Dataset _positionSourceDataset; /** Smart pointer to source of the points dataset for point position (if any) */ - Dataset _colorDataset; /** Smart pointer to dataset used for coloring (if any) */ - Dataset _pointSizeDataset; /** Smart pointer to dataset for driving point size (if any) */ - Dataset _pointOpacityDataset; /** Smart pointer to dataset for driving point opacity (if any) */ std::vector _positions; /** Point positions */ unsigned int _numPoints; /** Number of point positions */ SettingsAction _settingsAction; /** Group action for all settings */ From 3c63e44373f0033bbc92498be982f6c21118d1dc Mon Sep 17 00:00:00 2001 From: Thomas Kroes Date: Wed, 18 Feb 2026 10:54:35 +0100 Subject: [PATCH 13/33] Connect dataset pickers to HUD update Hook dataset picker changes to the heads-up display so the HUD updates immediately when datasets are picked. Adds connections from position, color, point size, and point opacity DatasetPickerAction::datasetPicked signals to ScatterplotPlugin::updateHeadsUpDisplay in init() (src/ScatterplotPlugin.cpp). --- src/ScatterplotPlugin.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ScatterplotPlugin.cpp b/src/ScatterplotPlugin.cpp index 8bf3f94..4504d54 100644 --- a/src/ScatterplotPlugin.cpp +++ b/src/ScatterplotPlugin.cpp @@ -398,6 +398,11 @@ void ScatterplotPlugin::init() }); #endif + connect(&_settingsAction.getDatasetsAction().getPositionDatasetPickerAction(), &DatasetPickerAction::datasetPicked, this, &ScatterplotPlugin::updateHeadsUpDisplay); + connect(&_settingsAction.getDatasetsAction().getColorDatasetPickerAction(), &DatasetPickerAction::datasetPicked, this, &ScatterplotPlugin::updateHeadsUpDisplay); + connect(&_settingsAction.getDatasetsAction().getPointSizeDatasetPickerAction(), &DatasetPickerAction::datasetPicked, this, &ScatterplotPlugin::updateHeadsUpDisplay); + connect(&_settingsAction.getDatasetsAction().getPointOpacityDatasetPickerAction(), &DatasetPickerAction::datasetPicked, this, &ScatterplotPlugin::updateHeadsUpDisplay); + updateHeadsUpDisplay(); updateHeadsUpDisplayTextColor(); } From 361e6d5a6367a9cd43c10fab204fbb8746b9ea07 Mon Sep 17 00:00:00 2001 From: Thomas Kroes Date: Thu, 19 Feb 2026 08:12:58 +0100 Subject: [PATCH 14/33] Refactor dataset pickers and HUD updates Add and wire up dataset picker actions for color, point size and point opacity; rename setup helpers to *PickerAction variants and re-run setup when a project is opened. Replace older datasetPicked connections with currentIndexChanged/sourceSelectionChanged signals for more reliable heads-up-display (HUD) updates, and connect dataset guiNameChanged directly to ScatterplotPlugin::updateHeadsUpDisplay. Add filter checks to ensure point-size/opacity datasets match position dataset sizes. Clean up commented/old code and move updateHeadsUpDisplayTextColor visibility to align with the refactor. --- src/DatasetsAction.cpp | 137 ++++++++++++++++---------------------- src/DatasetsAction.h | 8 +-- src/ScatterplotPlugin.cpp | 18 +++-- src/ScatterplotPlugin.h | 4 +- 4 files changed, 76 insertions(+), 91 deletions(-) diff --git a/src/DatasetsAction.cpp b/src/DatasetsAction.cpp index 4b8f02a..ea0b704 100644 --- a/src/DatasetsAction.cpp +++ b/src/DatasetsAction.cpp @@ -40,6 +40,16 @@ DatasetsAction::DatasetsAction(QObject* parent, const QString& title) : return; setupPositionDatasetPickerAction(scatterplotPlugin); + setupColorDatasetPickerAction(scatterplotPlugin); + setupPointSizeDatasetPickerAction(scatterplotPlugin); + setupPointOpacityDatasetPickerAction(scatterplotPlugin); + + connect(&mv::projects(), &AbstractProjectManager::projectOpened, this, [this, scatterplotPlugin]() -> void { + setupPositionDatasetPickerAction(scatterplotPlugin); + setupColorDatasetPickerAction(scatterplotPlugin); + setupPointSizeDatasetPickerAction(scatterplotPlugin); + setupPointOpacityDatasetPickerAction(scatterplotPlugin); + }); } void DatasetsAction::connectToPublicAction(WidgetAction* publicAction, bool recursive) @@ -117,42 +127,28 @@ void DatasetsAction::setupColorDatasetPickerAction(ScatterplotPlugin* scatterplo return (dataset->getDataType() == PointType || dataset->getDataType() == ColorType || dataset->getDataType() == ClusterType); }); - /* auto& coloringAction = settingsAction.getColoringAction(); - connect(&_colorDatasetPickerAction, &DatasetPickerAction::datasetPicked, [this, &coloringAction](Dataset pickedDataset) -> void { - coloringAction.getColorByAction().setCurrentIndex(pickedDataset.isValid() ? 2 : 0); - - if (pickedDataset.isValid() && !mv::projects().isOpeningProject()) - coloringAction.setCurrentColorDataset(pickedDataset); - }); - - connect(&settingsAction.getColoringAction(), &ColoringAction::currentColorDatasetChanged, this, [this](Dataset currentColorDataset) -> void { - _colorDatasetPickerAction.setCurrentDataset(currentColorDataset); - }); - - const auto currentColorDatasetChanged = [this](Dataset currentColorDataset) -> void { + connect(&_colorDatasetPickerAction, &DatasetPickerAction::datasetPicked, [this, &coloringAction, scatterplotPlugin](Dataset pickedDataset) -> void { if (_colorDataset.isValid()) disconnect(&_colorDataset, &Dataset<>::guiNameChanged, this, nullptr); - _colorDataset = currentColorDataset; - - connect(&_colorDataset, &Dataset<>::guiNameChanged, this, &ScatterplotPlugin::updateHeadsUpDisplay); + _colorDataset = pickedDataset; - //updateHeadsUpDisplay(); - }; + connect(&_colorDataset, &Dataset<>::guiNameChanged, scatterplotPlugin, &ScatterplotPlugin::updateHeadsUpDisplay); - connect(&settingsAction.getColoringAction(), &ColoringAction::currentColorDatasetChanged, this, currentColorDatasetChanged); + coloringAction.setCurrentColorDataset(pickedDataset); - connect(&settingsAction.getColoringAction().getColorByAction(), &OptionAction::currentIndexChanged, this, [this, &settingsAction, currentColorDatasetChanged](const std::int32_t& currentIndex) -> void { - _colorDataset = settingsAction.getColoringAction().getCurrentColorDataset(); + if (!pickedDataset.isValid()) + coloringAction.getColorByAction().setCurrentIndex(0); + }); - updateHeadsUpDisplay(); + connect(&settingsAction.getColoringAction(), &ColoringAction::currentColorDatasetChanged, this, [this](Dataset currentColorDataset) -> void { + _colorDatasetPickerAction.setCurrentDataset(currentColorDataset); }); - */ } -void DatasetsAction::setupPointSizeDataset(ScatterplotPlugin* scatterplotPlugin) +void DatasetsAction::setupPointSizeDatasetPickerAction(ScatterplotPlugin* scatterplotPlugin) { auto& settingsAction = scatterplotPlugin->getSettingsAction(); @@ -160,84 +156,65 @@ void DatasetsAction::setupPointSizeDataset(ScatterplotPlugin* scatterplotPlugin) return dataset->getDataType() == PointType; }); - /* - auto& pointPlotAction = scatterplotPlugin->getSettingsAction().getPlotAction().getPointPlotAction(); - auto& pointSizeAction = pointPlotAction.getSizeAction(); - auto& pointOpacityAction = pointPlotAction.getOpacityAction(); + auto& pointPlotAction = settingsAction.getPlotAction().getPointPlotAction(); + auto& pointSizeAction = pointPlotAction.getSizeAction(); + + + connect(&pointSizeAction, &ScalarAction::sourceSelectionChanged, this, [this, &pointSizeAction, scatterplotPlugin](const uint32_t& sourceSelectionIndex) -> void { + if (_pointSizeDataset.isValid()) + disconnect(&_pointSizeDataset, &Dataset<>::guiNameChanged, this, nullptr); + + _pointSizeDataset = pointSizeAction.getCurrentDataset(); + + connect(&_pointSizeDataset, &Dataset<>::guiNameChanged, scatterplotPlugin, &ScatterplotPlugin::updateHeadsUpDisplay); - const auto pointSizeSourceChanged = [this, &pointSizeAction]() -> void { _pointSizeDatasetPickerAction.setCurrentDataset(pointSizeAction.isSourceDataset() ? pointSizeAction.getCurrentDataset() : nullptr); if (!pointSizeAction.isSourceDataset()) _pointSizeDatasetPickerAction.setCurrentIndex(-1); - }; - - connect(&pointSizeAction, &ScalarAction::sourceSelectionChanged, this, pointSizeSourceChanged); - connect(&pointSizeAction, &ScalarAction::sourceDataChanged, this, pointSizeSourceChanged); + }); connect(&_pointSizeDatasetPickerAction, &DatasetPickerAction::currentIndexChanged, this, [this, &pointSizeAction](const int32_t& currentIndex) -> void { pointSizeAction.setCurrentDataset(_pointSizeDatasetPickerAction.getCurrentDataset()); if (currentIndex < 0) pointSizeAction.setCurrentSourceIndex(ScalarSourceModel::DefaultRow::Constant); - }); - - const auto pointOpacitySourceChanged = [this, &pointOpacityAction]() -> void { - _pointOpacityDatasetPickerAction.setCurrentDataset(pointOpacityAction.isSourceDataset() ? pointOpacityAction.getCurrentDataset() : nullptr); - - if (!pointOpacityAction.isSourceDataset()) - _pointOpacityDatasetPickerAction.setCurrentIndex(-1); - }; - - connect(&pointOpacityAction, &ScalarAction::sourceSelectionChanged, this, pointOpacitySourceChanged); - connect(&pointOpacityAction, &ScalarAction::sourceDataChanged, this, pointOpacitySourceChanged); - - connect(&_pointOpacityDatasetPickerAction, &DatasetPickerAction::currentIndexChanged, this, [this, &pointOpacityAction](const int32_t& currentIndex) -> void { - pointOpacityAction.setCurrentDataset(_pointOpacityDatasetPickerAction.getCurrentDataset()); - - if (currentIndex < 0) - pointOpacityAction.setCurrentSourceIndex(ScalarSourceModel::DefaultRow::Constant); - }); - - + }); +} - const auto currentPointSizeDatasetChanged = [this]() -> void { - auto currentPointSizeDataset = _settingsAction.getPlotAction().getPointPlotAction().getSizeAction().getCurrentDataset(); +void DatasetsAction::setupPointOpacityDatasetPickerAction(ScatterplotPlugin* scatterplotPlugin) +{ + auto& settingsAction = scatterplotPlugin->getSettingsAction(); + auto& pointPlotAction = settingsAction.getPlotAction().getPointPlotAction(); + auto& pointOpacityAction = pointPlotAction.getOpacityAction(); - if (_pointSizeDataset.isValid()) - disconnect(&_pointSizeDataset, &Dataset<>::guiNameChanged, this, nullptr); + _pointOpacityDatasetPickerAction.setFilterFunction([this, scatterplotPlugin](mv::Dataset dataset) -> bool { + if (dataset->getDataType() != PointType) + return false; + + const auto positionDataset = scatterplotPlugin->getPositionSourceDataset(); - _pointSizeDataset = currentPointSizeDataset; + if (!positionDataset.isValid()) + return false; - connect(&_pointSizeDataset, &Dataset<>::guiNameChanged, this, &ScatterplotPlugin::updateHeadsUpDisplay); + const mv::Dataset candidatePoints(dataset); - updateHeadsUpDisplay(); - }; + if (candidatePoints->getNumPoints() != positionDataset->getNumPoints()) + return false; - connect(&_settingsAction.getPlotAction().getPointPlotAction().getSizeAction(), &ScalarAction::sourceSelectionChanged, this, currentPointSizeDatasetChanged); + return true; + }); - const auto currentPointOpacityDatasetChanged = [this]() -> void { - auto currentPointOpacityDataset = _settingsAction.getPlotAction().getPointPlotAction().getOpacityAction().getCurrentDataset(); + connect(&_pointOpacityDatasetPickerAction, &DatasetPickerAction::datasetPicked, this, [this, &pointPlotAction, &pointOpacityAction, scatterplotPlugin](mv::Dataset<> pickedDataset) -> void { + const auto& pointOpacityDataset = _pointOpacityDatasetPickerAction.getCurrentDataset(); - if (_pointOpacityDataset.isValid()) + if (pointOpacityDataset.isValid()) disconnect(&_pointOpacityDataset, &Dataset<>::guiNameChanged, this, nullptr); - _pointOpacityDataset = currentPointOpacityDataset; + _pointOpacityDataset = pointOpacityDataset; - connect(&_pointOpacityDataset, &Dataset<>::guiNameChanged, this, &ScatterplotPlugin::updateHeadsUpDisplay); - - updateHeadsUpDisplay(); - }; - - connect(&_settingsAction.getPlotAction().getPointPlotAction().getOpacityAction(), &ScalarAction::sourceSelectionChanged, this, currentPointOpacityDatasetChanged);*/ -} + connect(&_pointOpacityDataset, &Dataset<>::guiNameChanged, scatterplotPlugin, &ScatterplotPlugin::updateHeadsUpDisplay); -void DatasetsAction::setupPointOpacityDataset(ScatterplotPlugin* scatterplotPlugin) -{ - auto& settingsAction = scatterplotPlugin->getSettingsAction(); - - _pointOpacityDatasetPickerAction.setFilterFunction([this](mv::Dataset dataset) -> bool { - return dataset->getDataType() == PointType; + pointPlotAction.setCurrentPointOpacityDataset(_pointOpacityDataset); }); - } diff --git a/src/DatasetsAction.h b/src/DatasetsAction.h index 921965e..cc23218 100644 --- a/src/DatasetsAction.h +++ b/src/DatasetsAction.h @@ -55,10 +55,10 @@ class DatasetsAction : public GroupAction private: - void setupPositionDatasetPickerAction(ScatterplotPlugin* scatterplotPlugin); /** Set up the position dataset picker action */ - void setupColorDatasetPickerAction(ScatterplotPlugin* scatterplotPlugin); /** Set up the color dataset picker action */ - void setupPointSizeDataset(ScatterplotPlugin* scatterplotPlugin); /** Set up the point size dataset picker action */ - void setupPointOpacityDataset(ScatterplotPlugin* scatterplotPlugin); /** Set up the point opacity dataset picker action */ + void setupPositionDatasetPickerAction(ScatterplotPlugin* scatterplotPlugin); /** Set up the position dataset picker action */ + void setupColorDatasetPickerAction(ScatterplotPlugin* scatterplotPlugin); /** Set up the color dataset picker action */ + void setupPointSizeDatasetPickerAction(ScatterplotPlugin* scatterplotPlugin); /** Set up the point size dataset picker action */ + void setupPointOpacityDatasetPickerAction(ScatterplotPlugin* scatterplotPlugin); /** Set up the point opacity dataset picker action */ public: // Action getters diff --git a/src/ScatterplotPlugin.cpp b/src/ScatterplotPlugin.cpp index 4504d54..63a9e7b 100644 --- a/src/ScatterplotPlugin.cpp +++ b/src/ScatterplotPlugin.cpp @@ -398,10 +398,13 @@ void ScatterplotPlugin::init() }); #endif - connect(&_settingsAction.getDatasetsAction().getPositionDatasetPickerAction(), &DatasetPickerAction::datasetPicked, this, &ScatterplotPlugin::updateHeadsUpDisplay); - connect(&_settingsAction.getDatasetsAction().getColorDatasetPickerAction(), &DatasetPickerAction::datasetPicked, this, &ScatterplotPlugin::updateHeadsUpDisplay); - connect(&_settingsAction.getDatasetsAction().getPointSizeDatasetPickerAction(), &DatasetPickerAction::datasetPicked, this, &ScatterplotPlugin::updateHeadsUpDisplay); - connect(&_settingsAction.getDatasetsAction().getPointOpacityDatasetPickerAction(), &DatasetPickerAction::datasetPicked, this, &ScatterplotPlugin::updateHeadsUpDisplay); + auto& datasetsAction = _settingsAction.getDatasetsAction(); + auto& pointPlotAction = _settingsAction.getPlotAction().getPointPlotAction(); + + connect(&datasetsAction.getPositionDatasetPickerAction(), &DatasetPickerAction::currentIndexChanged, this, &ScatterplotPlugin::updateHeadsUpDisplay); + connect(&datasetsAction.getColorDatasetPickerAction(), &DatasetPickerAction::currentIndexChanged, this, &ScatterplotPlugin::updateHeadsUpDisplay); + connect(&datasetsAction.getPointSizeDatasetPickerAction(), &DatasetPickerAction::currentIndexChanged, this, &ScatterplotPlugin::updateHeadsUpDisplay); + connect(&pointPlotAction.getOpacityAction(), &ScalarAction::sourceSelectionChanged, this, &ScatterplotPlugin::updateHeadsUpDisplay); updateHeadsUpDisplay(); updateHeadsUpDisplayTextColor(); @@ -1021,8 +1024,11 @@ void ScatterplotPlugin::updateHeadsUpDisplay() if (coloringAction.getColorByAction().getCurrentIndex() >= 2) addMetaDataToHeadsUpDisplay("Color", coloringAction.getCurrentColorDataset(), datasetsItem); - addMetaDataToHeadsUpDisplay("Size", _settingsAction.getPlotAction().getPointPlotAction().getSizeAction().getCurrentDataset(), datasetsItem); - addMetaDataToHeadsUpDisplay("Opacity", _settingsAction.getPlotAction().getPointPlotAction().getOpacityAction().getCurrentDataset(), datasetsItem); + auto& pointPlotAction = _settingsAction.getPlotAction().getPointPlotAction(); + + //qDebug() << "ScatterplotPlugin::updateHeadsUpDisplay: point size dataset: " << pointPlotAction.getSizeAction().getCurrentDataset().isValid() << ", opacity dataset: " << pointPlotAction.getOpacityAction().getCurrentDataset().isValid(); + addMetaDataToHeadsUpDisplay("Size", pointPlotAction.getSizeAction().getCurrentDataset(), datasetsItem); + addMetaDataToHeadsUpDisplay("Opacity", pointPlotAction.getOpacityAction().getCurrentDataset(), datasetsItem); } else { getHeadsUpDisplayAction().addHeadsUpDisplayItem("No datasets loaded", "", ""); } diff --git a/src/ScatterplotPlugin.h b/src/ScatterplotPlugin.h index e795c72..a3966c5 100644 --- a/src/ScatterplotPlugin.h +++ b/src/ScatterplotPlugin.h @@ -93,9 +93,11 @@ class ScatterplotPlugin : public ViewPlugin private: void updateData(); void updateSelection(); + void updateHeadsUpDisplayTextColor(); + +public: void updateHeadsUpDisplay(); - void updateHeadsUpDisplayTextColor(); public: // Serialization From 5ae5fa96bd7bc741c7e77630493ed03fd92a6e5d Mon Sep 17 00:00:00 2001 From: Thomas Kroes Date: Thu, 19 Feb 2026 08:26:25 +0100 Subject: [PATCH 15/33] Refactor dataset picker setup and opacity sync Extracted repeated dataset picker initializations into setupDatasetPickerActions and call it on construction and when a project opens to reduce duplication. Switched the point opacity picker connection to currentIndexChanged and added logic to synchronize the picker with the ScalarAction (set default Constant source when no dataset, update picker when sourceSelection changes, clear index when not using a dataset). Added descriptive comments for the new helper and existing setup methods in the header. --- src/DatasetsAction.cpp | 32 ++++++++++++++++++++++---------- src/DatasetsAction.h | 35 ++++++++++++++++++++++++++++++----- 2 files changed, 52 insertions(+), 15 deletions(-) diff --git a/src/DatasetsAction.cpp b/src/DatasetsAction.cpp index ea0b704..9ef2cd4 100644 --- a/src/DatasetsAction.cpp +++ b/src/DatasetsAction.cpp @@ -39,17 +39,11 @@ DatasetsAction::DatasetsAction(QObject* parent, const QString& title) : if (scatterplotPlugin == nullptr) return; - setupPositionDatasetPickerAction(scatterplotPlugin); - setupColorDatasetPickerAction(scatterplotPlugin); - setupPointSizeDatasetPickerAction(scatterplotPlugin); - setupPointOpacityDatasetPickerAction(scatterplotPlugin); + setupDatasetPickerActions(scatterplotPlugin); connect(&mv::projects(), &AbstractProjectManager::projectOpened, this, [this, scatterplotPlugin]() -> void { - setupPositionDatasetPickerAction(scatterplotPlugin); - setupColorDatasetPickerAction(scatterplotPlugin); - setupPointSizeDatasetPickerAction(scatterplotPlugin); - setupPointOpacityDatasetPickerAction(scatterplotPlugin); - }); + setupDatasetPickerActions(scatterplotPlugin); + }); } void DatasetsAction::connectToPublicAction(WidgetAction* publicAction, bool recursive) @@ -104,6 +98,14 @@ QVariantMap DatasetsAction::toVariantMap() const return variantMap; } +void DatasetsAction::setupDatasetPickerActions(ScatterplotPlugin* scatterplotPlugin) +{ + setupPositionDatasetPickerAction(scatterplotPlugin); + setupColorDatasetPickerAction(scatterplotPlugin); + setupPointSizeDatasetPickerAction(scatterplotPlugin); + setupPointOpacityDatasetPickerAction(scatterplotPlugin); +} + void DatasetsAction::setupPositionDatasetPickerAction(ScatterplotPlugin* scatterplotPlugin) { _positionDatasetPickerAction.setFilterFunction([this](mv::Dataset dataset) -> bool { @@ -205,7 +207,7 @@ void DatasetsAction::setupPointOpacityDatasetPickerAction(ScatterplotPlugin* sca return true; }); - connect(&_pointOpacityDatasetPickerAction, &DatasetPickerAction::datasetPicked, this, [this, &pointPlotAction, &pointOpacityAction, scatterplotPlugin](mv::Dataset<> pickedDataset) -> void { + connect(&_pointOpacityDatasetPickerAction, &DatasetPickerAction::currentIndexChanged, this, [this, &pointPlotAction, &pointOpacityAction, scatterplotPlugin](const int32_t& currentIndex) -> void { const auto& pointOpacityDataset = _pointOpacityDatasetPickerAction.getCurrentDataset(); if (pointOpacityDataset.isValid()) @@ -216,5 +218,15 @@ void DatasetsAction::setupPointOpacityDatasetPickerAction(ScatterplotPlugin* sca connect(&_pointOpacityDataset, &Dataset<>::guiNameChanged, scatterplotPlugin, &ScatterplotPlugin::updateHeadsUpDisplay); pointPlotAction.setCurrentPointOpacityDataset(_pointOpacityDataset); + + if (!_pointOpacityDataset.isValid()) + pointOpacityAction.setCurrentSourceIndex(ScalarSourceModel::DefaultRow::Constant); + }); + + connect(&pointOpacityAction, &ScalarAction::sourceSelectionChanged, this, [this, &pointOpacityAction](const uint32_t& sourceSelectionIndex) -> void { + _pointOpacityDatasetPickerAction.setCurrentDataset(pointOpacityAction.isSourceDataset() ? pointOpacityAction.getCurrentDataset() : nullptr); + + if (!pointOpacityAction.isSourceDataset()) + _pointOpacityDatasetPickerAction.setCurrentIndex(-1); }); } diff --git a/src/DatasetsAction.h b/src/DatasetsAction.h index cc23218..82bd03c 100644 --- a/src/DatasetsAction.h +++ b/src/DatasetsAction.h @@ -53,12 +53,37 @@ class DatasetsAction : public GroupAction */ QVariantMap toVariantMap() const override; -private: +private: // Dataset picker action setup + + /** + * Set up the dataset picker actions with the datasets from the scatter plot plugin + * @param scatterplotPlugin Pointer to scatter plot plugin whose datasets are used to populate the dataset picker actions + */ + void setupDatasetPickerActions(ScatterplotPlugin* scatterplotPlugin); - void setupPositionDatasetPickerAction(ScatterplotPlugin* scatterplotPlugin); /** Set up the position dataset picker action */ - void setupColorDatasetPickerAction(ScatterplotPlugin* scatterplotPlugin); /** Set up the color dataset picker action */ - void setupPointSizeDatasetPickerAction(ScatterplotPlugin* scatterplotPlugin); /** Set up the point size dataset picker action */ - void setupPointOpacityDatasetPickerAction(ScatterplotPlugin* scatterplotPlugin); /** Set up the point opacity dataset picker action */ + /** + * Set up the position dataset picker action with the position datasets from the scatter plot plugin + * @param scatterplotPlugin Pointer to scatter plot plugin whose position datasets are used to populate the dataset picker action + */ + void setupPositionDatasetPickerAction(ScatterplotPlugin* scatterplotPlugin); + + /** + * Set up the color dataset picker action with the color datasets from the scatter plot plugin + * @param scatterplotPlugin Pointer to scatter plot plugin whose color datasets are used to populate the dataset picker action + */ + void setupColorDatasetPickerAction(ScatterplotPlugin* scatterplotPlugin); + + /** + * Set up the point size dataset picker action with the point size datasets from the scatter plot plugin + * @param scatterplotPlugin Pointer to scatter plot plugin whose point size datasets are used to populate the dataset picker action + */ + void setupPointSizeDatasetPickerAction(ScatterplotPlugin* scatterplotPlugin); + + /** + * Set up the point opacity dataset picker action with the point opacity datasets from the scatter plot plugin + * @param scatterplotPlugin Pointer to scatter plot plugin whose point opacity datasets are used to populate the dataset picker action + */ + void setupPointOpacityDatasetPickerAction(ScatterplotPlugin* scatterplotPlugin); public: // Action getters From ec9315b2f33d438107aa0c956d2eef1ea3e65fc4 Mon Sep 17 00:00:00 2001 From: Thomas Kroes Date: Thu, 19 Feb 2026 08:47:42 +0100 Subject: [PATCH 16/33] Move dataset picker setup into ScatterplotPlugin Move control of dataset picker initialization from DatasetsAction to ScatterplotPlugin so the plugin can defer or re-run setup during project open. Make setupDatasetPickerActions protected and add ScatterplotPlugin as a friend so the plugin can call it. Refactor point-size dataset picker: tighten the filter to require PointType, a valid position dataset, and matching point counts; rework signal wiring to keep the DatasetPickerAction and ScalarAction synchronized and to set a default constant source when no dataset is selected. Minor debug logging added to aid troubleshooting. --- src/DatasetsAction.cpp | 54 ++++++++++++++++++++++++--------------- src/DatasetsAction.h | 3 ++- src/ScatterplotPlugin.cpp | 8 ++++++ 3 files changed, 44 insertions(+), 21 deletions(-) diff --git a/src/DatasetsAction.cpp b/src/DatasetsAction.cpp index 9ef2cd4..64915f4 100644 --- a/src/DatasetsAction.cpp +++ b/src/DatasetsAction.cpp @@ -40,10 +40,6 @@ DatasetsAction::DatasetsAction(QObject* parent, const QString& title) : return; setupDatasetPickerActions(scatterplotPlugin); - - connect(&mv::projects(), &AbstractProjectManager::projectOpened, this, [this, scatterplotPlugin]() -> void { - setupDatasetPickerActions(scatterplotPlugin); - }); } void DatasetsAction::connectToPublicAction(WidgetAction* publicAction, bool recursive) @@ -152,35 +148,53 @@ void DatasetsAction::setupColorDatasetPickerAction(ScatterplotPlugin* scatterplo void DatasetsAction::setupPointSizeDatasetPickerAction(ScatterplotPlugin* scatterplotPlugin) { - auto& settingsAction = scatterplotPlugin->getSettingsAction(); + auto& settingsAction = scatterplotPlugin->getSettingsAction(); + auto& pointPlotAction = settingsAction.getPlotAction().getPointPlotAction(); + auto& pointSizeAction = pointPlotAction.getSizeAction(); - _pointSizeDatasetPickerAction.setFilterFunction([this](mv::Dataset dataset) -> bool { - return dataset->getDataType() == PointType; - }); + _pointSizeDatasetPickerAction.setFilterFunction([this, scatterplotPlugin](mv::Dataset dataset) -> bool { + if (dataset->getDataType() != PointType) + return false; - auto& pointPlotAction = settingsAction.getPlotAction().getPointPlotAction(); - auto& pointSizeAction = pointPlotAction.getSizeAction(); + qDebug() << dataset->getGuiName() << "A"; + const auto positionDataset = scatterplotPlugin->getPositionSourceDataset(); + + if (!positionDataset.isValid()) + return false; + qDebug() << dataset->getGuiName() << "B"; - connect(&pointSizeAction, &ScalarAction::sourceSelectionChanged, this, [this, &pointSizeAction, scatterplotPlugin](const uint32_t& sourceSelectionIndex) -> void { - if (_pointSizeDataset.isValid()) + const mv::Dataset candidatePoints(dataset); + + if (candidatePoints->getNumPoints() != positionDataset->getNumPoints()) + return false; + + qDebug() << dataset->getGuiName() << "C"; + + return true; + }); + + connect(&_pointSizeDatasetPickerAction, &DatasetPickerAction::currentIndexChanged, this, [this, &pointPlotAction, &pointSizeAction, scatterplotPlugin](const int32_t& currentIndex) -> void { + const auto& pointSizeDataset = _pointSizeDatasetPickerAction.getCurrentDataset(); + + if (pointSizeDataset.isValid()) disconnect(&_pointSizeDataset, &Dataset<>::guiNameChanged, this, nullptr); - _pointSizeDataset = pointSizeAction.getCurrentDataset(); + _pointSizeDataset = pointSizeDataset; connect(&_pointSizeDataset, &Dataset<>::guiNameChanged, scatterplotPlugin, &ScatterplotPlugin::updateHeadsUpDisplay); + pointPlotAction.setCurrentPointSizeDataset(_pointSizeDataset); + + if (!_pointSizeDataset.isValid()) + pointSizeAction.setCurrentSourceIndex(ScalarSourceModel::DefaultRow::Constant); + }); + + connect(&pointSizeAction, &ScalarAction::sourceSelectionChanged, this, [this, &pointSizeAction](const uint32_t& sourceSelectionIndex) -> void { _pointSizeDatasetPickerAction.setCurrentDataset(pointSizeAction.isSourceDataset() ? pointSizeAction.getCurrentDataset() : nullptr); if (!pointSizeAction.isSourceDataset()) _pointSizeDatasetPickerAction.setCurrentIndex(-1); - }); - - connect(&_pointSizeDatasetPickerAction, &DatasetPickerAction::currentIndexChanged, this, [this, &pointSizeAction](const int32_t& currentIndex) -> void { - pointSizeAction.setCurrentDataset(_pointSizeDatasetPickerAction.getCurrentDataset()); - - if (currentIndex < 0) - pointSizeAction.setCurrentSourceIndex(ScalarSourceModel::DefaultRow::Constant); }); } diff --git a/src/DatasetsAction.h b/src/DatasetsAction.h index 82bd03c..aa6ab87 100644 --- a/src/DatasetsAction.h +++ b/src/DatasetsAction.h @@ -53,7 +53,7 @@ class DatasetsAction : public GroupAction */ QVariantMap toVariantMap() const override; -private: // Dataset picker action setup +protected: // Dataset picker action setup /** * Set up the dataset picker actions with the datasets from the scatter plot plugin @@ -103,6 +103,7 @@ class DatasetsAction : public GroupAction mv::Dataset _pointOpacityDataset; /** Smart pointer to dataset for driving point opacity (if any) */ friend class mv::AbstractActionsManager; + friend class ScatterplotPlugin; }; Q_DECLARE_METATYPE(DatasetsAction) diff --git a/src/ScatterplotPlugin.cpp b/src/ScatterplotPlugin.cpp index 63a9e7b..03dcdbc 100644 --- a/src/ScatterplotPlugin.cpp +++ b/src/ScatterplotPlugin.cpp @@ -408,6 +408,14 @@ void ScatterplotPlugin::init() updateHeadsUpDisplay(); updateHeadsUpDisplayTextColor(); + + if (mv::projects().isOpeningProject()) { + connect(&mv::projects(), &AbstractProjectManager::projectOpened, this, [this, &datasetsAction]() -> void { + datasetsAction.setupDatasetPickerActions(this); + }); + } else { + datasetsAction.setupDatasetPickerActions(this); + } } void ScatterplotPlugin::loadData(const Datasets& datasets) From ede22db3e0f6516923c91492b140edf3486998ef Mon Sep 17 00:00:00 2001 From: Thomas Kroes Date: Thu, 19 Feb 2026 08:50:32 +0100 Subject: [PATCH 17/33] Invalidate dataset picker filters Add DatasetsAction::invalidateDatasetPickerActionFilters() (declared in the header and implemented) which calls invalidateFilter() on all dataset picker actions (_position, _color, _pointSize, _pointOpacity). Update ScatterplotPlugin::init() to call this new method instead of setupDatasetPickerActions() when initializing so picker filters are refreshed to reflect the current datasets without reinitializing the actions. --- src/DatasetsAction.cpp | 8 ++++++++ src/DatasetsAction.h | 3 +++ src/ScatterplotPlugin.cpp | 4 ++-- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/DatasetsAction.cpp b/src/DatasetsAction.cpp index 64915f4..82a6fc6 100644 --- a/src/DatasetsAction.cpp +++ b/src/DatasetsAction.cpp @@ -244,3 +244,11 @@ void DatasetsAction::setupPointOpacityDatasetPickerAction(ScatterplotPlugin* sca _pointOpacityDatasetPickerAction.setCurrentIndex(-1); }); } + +void DatasetsAction::invalidateDatasetPickerActionFilters() +{ + _positionDatasetPickerAction.invalidateFilter(); + _colorDatasetPickerAction.invalidateFilter(); + _pointSizeDatasetPickerAction.invalidateFilter(); + _pointOpacityDatasetPickerAction.invalidateFilter(); +} diff --git a/src/DatasetsAction.h b/src/DatasetsAction.h index aa6ab87..c71acbf 100644 --- a/src/DatasetsAction.h +++ b/src/DatasetsAction.h @@ -85,6 +85,9 @@ class DatasetsAction : public GroupAction */ void setupPointOpacityDatasetPickerAction(ScatterplotPlugin* scatterplotPlugin); + /** Update the filters of the dataset picker actions based on the current datasets in the scatter plot plugin */ + void invalidateDatasetPickerActionFilters(); + public: // Action getters DatasetPickerAction& getPositionDatasetPickerAction() { return _positionDatasetPickerAction; } diff --git a/src/ScatterplotPlugin.cpp b/src/ScatterplotPlugin.cpp index 03dcdbc..12cfe97 100644 --- a/src/ScatterplotPlugin.cpp +++ b/src/ScatterplotPlugin.cpp @@ -411,10 +411,10 @@ void ScatterplotPlugin::init() if (mv::projects().isOpeningProject()) { connect(&mv::projects(), &AbstractProjectManager::projectOpened, this, [this, &datasetsAction]() -> void { - datasetsAction.setupDatasetPickerActions(this); + datasetsAction.invalidateDatasetPickerActionFilters(); }); } else { - datasetsAction.setupDatasetPickerActions(this); + datasetsAction.invalidateDatasetPickerActionFilters(); } } From dc8d623cbafa5621c15a66d4389444586e24a805 Mon Sep 17 00:00:00 2001 From: Thomas Kroes Date: Thu, 19 Feb 2026 09:17:25 +0100 Subject: [PATCH 18/33] Clear color dataset and refresh dependent pickers When the position dataset changes, clear the current color dataset and refresh dependent dataset pickers (color, point size, point opacity). Mark the position picker as Clearable, connect its datasetPicked signal to invalidate the other pickers, and invalidate them initially. Update filter callbacks to require a valid position dataset (and replace getPositionSourceDataset() calls with getPositionDataset()). This ensures pickers reflect the current position dataset and prevents stale/invalid color selections after position changes. --- src/DatasetsAction.cpp | 27 +++++++++++++++++++++++---- src/ScatterplotPlugin.cpp | 8 +++++--- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/DatasetsAction.cpp b/src/DatasetsAction.cpp index 82a6fc6..ee7d215 100644 --- a/src/DatasetsAction.cpp +++ b/src/DatasetsAction.cpp @@ -28,6 +28,7 @@ DatasetsAction::DatasetsAction(QObject* parent, const QString& title) : addAction(&_pointSizeDatasetPickerAction); addAction(&_pointOpacityDatasetPickerAction); + _positionDatasetPickerAction.setDefaultWidgetFlag(OptionAction::Clearable); _colorDatasetPickerAction.setDefaultWidgetFlag(OptionAction::Clearable); _pointSizeDatasetPickerAction.setDefaultWidgetFlag(OptionAction::Clearable); _pointOpacityDatasetPickerAction.setDefaultWidgetFlag(OptionAction::Clearable); @@ -40,6 +41,16 @@ DatasetsAction::DatasetsAction(QObject* parent, const QString& title) : return; setupDatasetPickerActions(scatterplotPlugin); + + connect(&_positionDatasetPickerAction, &DatasetPickerAction::datasetPicked, [this, scatterplotPlugin](Dataset pickedDataset) -> void { + _colorDatasetPickerAction.invalidateFilter(); + _pointSizeDatasetPickerAction.invalidateFilter(); + _pointOpacityDatasetPickerAction.invalidateFilter(); + }); + + _colorDatasetPickerAction.invalidateFilter(); + _pointSizeDatasetPickerAction.invalidateFilter(); + _pointOpacityDatasetPickerAction.invalidateFilter(); } void DatasetsAction::connectToPublicAction(WidgetAction* publicAction, bool recursive) @@ -121,8 +132,16 @@ void DatasetsAction::setupColorDatasetPickerAction(ScatterplotPlugin* scatterplo { auto& settingsAction = scatterplotPlugin->getSettingsAction(); - _colorDatasetPickerAction.setFilterFunction([this](mv::Dataset dataset) -> bool { - return (dataset->getDataType() == PointType || dataset->getDataType() == ColorType || dataset->getDataType() == ClusterType); + _colorDatasetPickerAction.setFilterFunction([this, scatterplotPlugin](mv::Dataset dataset) -> bool { + if (!(dataset->getDataType() == PointType || dataset->getDataType() == ColorType || dataset->getDataType() == ClusterType)) + return false; + + const auto positionDataset = scatterplotPlugin->getPositionDataset(); + + if (!positionDataset.isValid()) + return false; + + return true; }); auto& coloringAction = settingsAction.getColoringAction(); @@ -157,7 +176,7 @@ void DatasetsAction::setupPointSizeDatasetPickerAction(ScatterplotPlugin* scatte return false; qDebug() << dataset->getGuiName() << "A"; - const auto positionDataset = scatterplotPlugin->getPositionSourceDataset(); + const auto positionDataset = scatterplotPlugin->getPositionDataset(); if (!positionDataset.isValid()) return false; @@ -208,7 +227,7 @@ void DatasetsAction::setupPointOpacityDatasetPickerAction(ScatterplotPlugin* sca if (dataset->getDataType() != PointType) return false; - const auto positionDataset = scatterplotPlugin->getPositionSourceDataset(); + const auto positionDataset = scatterplotPlugin->getPositionDataset(); if (!positionDataset.isValid()) return false; diff --git a/src/ScatterplotPlugin.cpp b/src/ScatterplotPlugin.cpp index 12cfe97..62b4d77 100644 --- a/src/ScatterplotPlugin.cpp +++ b/src/ScatterplotPlugin.cpp @@ -165,7 +165,8 @@ ScatterplotPlugin::ScatterplotPlugin(const PluginFactory* factory) : // Load as point positions when no dataset is currently loaded dropRegions << new DropWidget::DropRegion(this, "Point position", description, "map-marker-alt", true, [this, candidateDataset]() { _positionDataset = candidateDataset; - }); + _settingsAction.getColoringAction().setCurrentColorDataset(nullptr); + }); } else { if (_positionDataset != candidateDataset && candidateDataset->getNumDimensions() >= 2) { @@ -173,8 +174,9 @@ ScatterplotPlugin::ScatterplotPlugin(const PluginFactory* factory) : // The number of points is equal, so offer the option to replace the existing points dataset dropRegions << new DropWidget::DropRegion(this, "Point position", description, "map-marker-alt", true, [this, candidateDataset]() { _positionDataset = candidateDataset; - }); - } + _settingsAction.getColoringAction().setCurrentColorDataset(nullptr); + }); + } // Accept for recoloring: // 1. data with the same number of points From ae308cb12853abc3ea6e92eeda88d26883b82bba Mon Sep 17 00:00:00 2001 From: Thomas Kroes Date: Fri, 20 Feb 2026 13:27:16 +0100 Subject: [PATCH 19/33] Remove debug qDebug logs in dataset check Remove leftover qDebug() calls from DatasetsAction::setupPointSizeDatasetPickerAction to clean up noisy debug output during dataset validation. The changes eliminate three intermediate debug prints while preserving the validation logic that ensures the dataset is of PointType and matches the position dataset point count. --- src/DatasetsAction.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/DatasetsAction.cpp b/src/DatasetsAction.cpp index ee7d215..e31ce00 100644 --- a/src/DatasetsAction.cpp +++ b/src/DatasetsAction.cpp @@ -175,21 +175,16 @@ void DatasetsAction::setupPointSizeDatasetPickerAction(ScatterplotPlugin* scatte if (dataset->getDataType() != PointType) return false; - qDebug() << dataset->getGuiName() << "A"; const auto positionDataset = scatterplotPlugin->getPositionDataset(); if (!positionDataset.isValid()) return false; - qDebug() << dataset->getGuiName() << "B"; - const mv::Dataset candidatePoints(dataset); if (candidatePoints->getNumPoints() != positionDataset->getNumPoints()) return false; - qDebug() << dataset->getGuiName() << "C"; - return true; }); From 08e712d7e9d57ce81ac3ba3885d38ed81e9193a4 Mon Sep 17 00:00:00 2001 From: Thomas Kroes Date: Wed, 25 Feb 2026 09:57:21 +0100 Subject: [PATCH 20/33] Refactor dataset picker invalidation and reset (#232) Introduce helper lambdas to centralize dataset picker state updates: invalidateFilters() to invalidate dependent filters and resetAuxiliaryDatasets() to clear aux picker selections (setCurrentIndex(-1)). Connect the position picker signal to run the invalidation and connect position dataset change signals to reset auxiliary pickers so color/size/opacity selections are cleared when position datasets change. This removes duplicated calls and ensures auxiliary pickers don't hold stale selections. --- src/DatasetsAction.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/DatasetsAction.cpp b/src/DatasetsAction.cpp index e31ce00..06bd0d0 100644 --- a/src/DatasetsAction.cpp +++ b/src/DatasetsAction.cpp @@ -42,15 +42,24 @@ DatasetsAction::DatasetsAction(QObject* parent, const QString& title) : setupDatasetPickerActions(scatterplotPlugin); - connect(&_positionDatasetPickerAction, &DatasetPickerAction::datasetPicked, [this, scatterplotPlugin](Dataset pickedDataset) -> void { + const auto invalidateFilters = [this, scatterplotPlugin]() -> void { _colorDatasetPickerAction.invalidateFilter(); _pointSizeDatasetPickerAction.invalidateFilter(); _pointOpacityDatasetPickerAction.invalidateFilter(); + }; + + connect(&_positionDatasetPickerAction, &DatasetPickerAction::datasetPicked, [this, scatterplotPlugin, invalidateFilters](Dataset pickedDataset) -> void { + invalidateFilters(); }); - _colorDatasetPickerAction.invalidateFilter(); - _pointSizeDatasetPickerAction.invalidateFilter(); - _pointOpacityDatasetPickerAction.invalidateFilter(); + const auto resetAuxilliaryDatasets = [this]() -> void { + _colorDatasetPickerAction.setCurrentIndex(-1); + _pointSizeDatasetPickerAction.setCurrentIndex(-1); + _pointOpacityDatasetPickerAction.setCurrentIndex(-1); + }; + + connect(&scatterplotPlugin->getPositionDataset(), &Dataset::changed, this, resetAuxilliaryDatasets); + connect(&scatterplotPlugin->getPositionSourceDataset(), &Dataset::changed, this, resetAuxilliaryDatasets); } void DatasetsAction::connectToPublicAction(WidgetAction* publicAction, bool recursive) From 227f92f4d869fc509948bb33230a4f3a1c0967ef Mon Sep 17 00:00:00 2001 From: Thomas Kroes Date: Wed, 25 Feb 2026 10:27:47 +0100 Subject: [PATCH 21/33] Remove point size restriction (#234) * Remove point size restriction * Check scalar source dataset point count When the source picker index points to a dataset, verify that the selected scalar source dataset has the same number of points as the ScatterplotPlugin's position dataset. If the counts differ, suppress emitting sourceSelectionChanged and add a user notification explaining the mismatch to prevent invalid selection. Also fix a parameter name in ScalarSourceAction.h's doc comment (variantMap). * Show point counts in dataset mismatch warning Cache the number of points for the scalar source and position datasets and use those values in the mismatch notification. This avoids repeated getNumPoints() calls and provides a clearer notification message (includes numPositions and numScalars) when the two datasets have different sizes. --- src/DatasetsAction.cpp | 24 +++++------------------- src/ScalarAction.cpp | 20 +++++++++++++++++++- src/ScalarSourceAction.h | 2 +- 3 files changed, 25 insertions(+), 21 deletions(-) diff --git a/src/DatasetsAction.cpp b/src/DatasetsAction.cpp index 06bd0d0..90d8824 100644 --- a/src/DatasetsAction.cpp +++ b/src/DatasetsAction.cpp @@ -181,17 +181,10 @@ void DatasetsAction::setupPointSizeDatasetPickerAction(ScatterplotPlugin* scatte auto& pointSizeAction = pointPlotAction.getSizeAction(); _pointSizeDatasetPickerAction.setFilterFunction([this, scatterplotPlugin](mv::Dataset dataset) -> bool { - if (dataset->getDataType() != PointType) + if (!scatterplotPlugin->getPositionDataset().isValid()) return false; - const auto positionDataset = scatterplotPlugin->getPositionDataset(); - - if (!positionDataset.isValid()) - return false; - - const mv::Dataset candidatePoints(dataset); - - if (candidatePoints->getNumPoints() != positionDataset->getNumPoints()) + if (dataset->getDataType() != PointType) return false; return true; @@ -228,19 +221,12 @@ void DatasetsAction::setupPointOpacityDatasetPickerAction(ScatterplotPlugin* sca auto& pointOpacityAction = pointPlotAction.getOpacityAction(); _pointOpacityDatasetPickerAction.setFilterFunction([this, scatterplotPlugin](mv::Dataset dataset) -> bool { - if (dataset->getDataType() != PointType) - return false; - - const auto positionDataset = scatterplotPlugin->getPositionDataset(); - - if (!positionDataset.isValid()) + if (!scatterplotPlugin->getPositionDataset().isValid()) return false; - const mv::Dataset candidatePoints(dataset); - - if (candidatePoints->getNumPoints() != positionDataset->getNumPoints()) + if (dataset->getDataType() != PointType) return false; - + return true; }); diff --git a/src/ScalarAction.cpp b/src/ScalarAction.cpp index f151703..1c8d2da 100644 --- a/src/ScalarAction.cpp +++ b/src/ScalarAction.cpp @@ -18,7 +18,25 @@ ScalarAction::ScalarAction(QObject* parent, const QString& title, const float& m addAction(&_sourceAction); connect(&_sourceAction.getPickerAction(), &OptionAction::currentIndexChanged, this, [this](const std::uint32_t& currentIndex) { - emit sourceSelectionChanged(currentIndex); + bool emitSourceSelectionChanged = true; + + if (currentIndex >= ScalarSourceModel::DefaultRow::DatasetStart) { + if (auto scatterplotPlugin = dynamic_cast(findPluginAncestor())) { + auto positionDataset = scatterplotPlugin->getPositionDataset(); + auto scalarSourcePointsDataset = Dataset(getCurrentDataset()); + const auto numScalars = scalarSourcePointsDataset->getNumPoints(); + const auto numPositions = positionDataset->getNumPoints(); + + if (numScalars != numPositions) { + emitSourceSelectionChanged = false; + + scatterplotPlugin->addNotification(QString("The number of points in the scalar source dataset does not match the number of points in the position dataset. (numPositions=%1, numScalars:%2)").arg(QString::number(numPositions), QString::number(numScalars))); + } + } + } + + if (emitSourceSelectionChanged) + emit sourceSelectionChanged(currentIndex); }); connect(&_magnitudeAction, &DecimalAction::valueChanged, this, [this](const float& value) { diff --git a/src/ScalarSourceAction.h b/src/ScalarSourceAction.h index 5387c31..c4e808a 100644 --- a/src/ScalarSourceAction.h +++ b/src/ScalarSourceAction.h @@ -54,7 +54,7 @@ class ScalarSourceAction : public GroupAction /** * Load widget action from variant map - * @param Variant map representation of the widget action + * @param variantMap Variant map representation of the widget action */ void fromVariantMap(const QVariantMap& variantMap) override; From 05df9573dd93a49245d1bc35efc3e05e9278f8cb Mon Sep 17 00:00:00 2001 From: Thomas Kroes Date: Thu, 26 Feb 2026 17:38:09 +0100 Subject: [PATCH 22/33] Fix opacity updates (#237) * Cache current dataset and handle dataChanged Add a _currentDataset member to ScalarAction and wire up its dataChanged signal when a new source is selected. Previously connections could accumulate; the code now disconnects any existing dataset signal before connecting the new one and forwards updates via a lambda that calls sourceDataChanged(getCurrentDataset()). Also update several parameter names in comments for clarity (variantMap, sourceSelectionIndex, offset). * Embed source dataset picker in ScalarAction Centralize dataset picking for scalar-driven properties by adding a DatasetPickerAction to ScalarAction and removing the separate point-size/opacity pickers from DatasetsAction. DatasetsAction no longer owns point size/opacity picker members; it instead reuses the ScalarAction pickers from the point plot settings and adjusts reset/connect logic accordingly (old filter code was commented out). ScalarAction now manages current dataset selection, connects dataset changes to emit sourceDataChanged and GUI name updates, and includes serialization for the new picker. ScatterplotPlugin was updated to listen to ScalarAction signals (sourceSelectionChanged/sourceDataChanged) for HUD updates and to refresh the scatter plot widget when scalar source data changes. Headers and getters were updated to reflect the new picker location. * Use LabelProxyAction for Size/Opacity pickers Replace direct DatasetPickerAction additions for point size and opacity with LabelProxyAction wrappers (labels "Size" and "Opacity") and include actions/LabelProxyAction.h instead of QMenu. Remove the now-unused setupPointSizeDatasetPickerAction and setupPointOpacityDatasetPickerAction declarations and implementations, and clean up related filter/invalidation code. This simplifies the dataset picker UI wiring and removes dead/commented code in DatasetsAction.cpp/.h. --- src/DatasetsAction.cpp | 125 ++++++-------------------------------- src/DatasetsAction.h | 20 ------ src/ScalarAction.cpp | 52 +++++++++++----- src/ScalarAction.h | 15 +++-- src/ScatterplotPlugin.cpp | 10 ++- 5 files changed, 75 insertions(+), 147 deletions(-) diff --git a/src/DatasetsAction.cpp b/src/DatasetsAction.cpp index 90d8824..4175033 100644 --- a/src/DatasetsAction.cpp +++ b/src/DatasetsAction.cpp @@ -5,7 +5,7 @@ #include #include -#include +#include using namespace mv; using namespace mv::gui; @@ -14,25 +14,13 @@ DatasetsAction::DatasetsAction(QObject* parent, const QString& title) : GroupAction(parent, title), _scatterplotPlugin(dynamic_cast(parent->parent())), _positionDatasetPickerAction(this, "Position"), - _colorDatasetPickerAction(this, "Color"), - _pointSizeDatasetPickerAction(this, "Point size"), - _pointOpacityDatasetPickerAction(this, "Point opacity") + _colorDatasetPickerAction(this, "Color") { setIconByName("database"); setToolTip("Manage loaded datasets for position and color"); setConfigurationFlag(WidgetAction::ConfigurationFlag::ForceCollapsedInGroup); setLabelSizingType(LabelSizingType::Auto); - addAction(&_positionDatasetPickerAction); - addAction(&_colorDatasetPickerAction); - addAction(&_pointSizeDatasetPickerAction); - addAction(&_pointOpacityDatasetPickerAction); - - _positionDatasetPickerAction.setDefaultWidgetFlag(OptionAction::Clearable); - _colorDatasetPickerAction.setDefaultWidgetFlag(OptionAction::Clearable); - _pointSizeDatasetPickerAction.setDefaultWidgetFlag(OptionAction::Clearable); - _pointOpacityDatasetPickerAction.setDefaultWidgetFlag(OptionAction::Clearable); - auto scatterplotPlugin = dynamic_cast(parent->parent()); Q_ASSERT(scatterplotPlugin); @@ -40,22 +28,33 @@ DatasetsAction::DatasetsAction(QObject* parent, const QString& title) : if (scatterplotPlugin == nullptr) return; + auto& settingsAction = scatterplotPlugin->getSettingsAction(); + auto& plotAction = settingsAction.getPlotAction(); + auto& pointPlotAction = plotAction.getPointPlotAction(); + + addAction(&_positionDatasetPickerAction); + addAction(&_colorDatasetPickerAction); + addAction(new LabelProxyAction(this, "Size", const_cast(&pointPlotAction.getSizeAction().getSourceDatasetPickerAction()))); + addAction(new LabelProxyAction(this, "Opacity", const_cast(&pointPlotAction.getOpacityAction().getSourceDatasetPickerAction()))); + + _positionDatasetPickerAction.setDefaultWidgetFlag(OptionAction::Clearable); + _colorDatasetPickerAction.setDefaultWidgetFlag(OptionAction::Clearable); + setupDatasetPickerActions(scatterplotPlugin); const auto invalidateFilters = [this, scatterplotPlugin]() -> void { _colorDatasetPickerAction.invalidateFilter(); - _pointSizeDatasetPickerAction.invalidateFilter(); - _pointOpacityDatasetPickerAction.invalidateFilter(); }; connect(&_positionDatasetPickerAction, &DatasetPickerAction::datasetPicked, [this, scatterplotPlugin, invalidateFilters](Dataset pickedDataset) -> void { invalidateFilters(); }); - const auto resetAuxilliaryDatasets = [this]() -> void { + const auto resetAuxilliaryDatasets = [this, &pointPlotAction]() -> void { _colorDatasetPickerAction.setCurrentIndex(-1); - _pointSizeDatasetPickerAction.setCurrentIndex(-1); - _pointOpacityDatasetPickerAction.setCurrentIndex(-1); + + pointPlotAction.getSizeAction().setCurrentSourceIndex(ScalarSourceModel::DefaultRow::Constant); + pointPlotAction.getOpacityAction().setCurrentSourceIndex(ScalarSourceModel::DefaultRow::Constant); }; connect(&scatterplotPlugin->getPositionDataset(), &Dataset::changed, this, resetAuxilliaryDatasets); @@ -74,8 +73,6 @@ void DatasetsAction::connectToPublicAction(WidgetAction* publicAction, bool recu if (recursive) { actions().connectPrivateActionToPublicAction(&_positionDatasetPickerAction, &publicDatasetsAction->getPositionDatasetPickerAction(), recursive); actions().connectPrivateActionToPublicAction(&_colorDatasetPickerAction, &publicDatasetsAction->getColorDatasetPickerAction(), recursive); - actions().connectPrivateActionToPublicAction(&_pointSizeDatasetPickerAction, &publicDatasetsAction->getPointSizeDatasetPickerAction(), recursive); - actions().connectPrivateActionToPublicAction(&_pointOpacityDatasetPickerAction, &publicDatasetsAction->getPointOpacityDatasetPickerAction(), recursive); } GroupAction::connectToPublicAction(publicAction, recursive); @@ -89,8 +86,6 @@ void DatasetsAction::disconnectFromPublicAction(bool recursive) if (recursive) { actions().disconnectPrivateActionFromPublicAction(&_positionDatasetPickerAction, recursive); actions().disconnectPrivateActionFromPublicAction(&_colorDatasetPickerAction, recursive); - actions().disconnectPrivateActionFromPublicAction(&_pointSizeDatasetPickerAction, recursive); - actions().disconnectPrivateActionFromPublicAction(&_pointOpacityDatasetPickerAction, recursive); } GroupAction::disconnectFromPublicAction(recursive); @@ -118,8 +113,6 @@ void DatasetsAction::setupDatasetPickerActions(ScatterplotPlugin* scatterplotPlu { setupPositionDatasetPickerAction(scatterplotPlugin); setupColorDatasetPickerAction(scatterplotPlugin); - setupPointSizeDatasetPickerAction(scatterplotPlugin); - setupPointOpacityDatasetPickerAction(scatterplotPlugin); } void DatasetsAction::setupPositionDatasetPickerAction(ScatterplotPlugin* scatterplotPlugin) @@ -174,90 +167,8 @@ void DatasetsAction::setupColorDatasetPickerAction(ScatterplotPlugin* scatterplo }); } -void DatasetsAction::setupPointSizeDatasetPickerAction(ScatterplotPlugin* scatterplotPlugin) -{ - auto& settingsAction = scatterplotPlugin->getSettingsAction(); - auto& pointPlotAction = settingsAction.getPlotAction().getPointPlotAction(); - auto& pointSizeAction = pointPlotAction.getSizeAction(); - - _pointSizeDatasetPickerAction.setFilterFunction([this, scatterplotPlugin](mv::Dataset dataset) -> bool { - if (!scatterplotPlugin->getPositionDataset().isValid()) - return false; - - if (dataset->getDataType() != PointType) - return false; - - return true; - }); - - connect(&_pointSizeDatasetPickerAction, &DatasetPickerAction::currentIndexChanged, this, [this, &pointPlotAction, &pointSizeAction, scatterplotPlugin](const int32_t& currentIndex) -> void { - const auto& pointSizeDataset = _pointSizeDatasetPickerAction.getCurrentDataset(); - - if (pointSizeDataset.isValid()) - disconnect(&_pointSizeDataset, &Dataset<>::guiNameChanged, this, nullptr); - - _pointSizeDataset = pointSizeDataset; - - connect(&_pointSizeDataset, &Dataset<>::guiNameChanged, scatterplotPlugin, &ScatterplotPlugin::updateHeadsUpDisplay); - - pointPlotAction.setCurrentPointSizeDataset(_pointSizeDataset); - - if (!_pointSizeDataset.isValid()) - pointSizeAction.setCurrentSourceIndex(ScalarSourceModel::DefaultRow::Constant); - }); - - connect(&pointSizeAction, &ScalarAction::sourceSelectionChanged, this, [this, &pointSizeAction](const uint32_t& sourceSelectionIndex) -> void { - _pointSizeDatasetPickerAction.setCurrentDataset(pointSizeAction.isSourceDataset() ? pointSizeAction.getCurrentDataset() : nullptr); - - if (!pointSizeAction.isSourceDataset()) - _pointSizeDatasetPickerAction.setCurrentIndex(-1); - }); -} - -void DatasetsAction::setupPointOpacityDatasetPickerAction(ScatterplotPlugin* scatterplotPlugin) -{ - auto& settingsAction = scatterplotPlugin->getSettingsAction(); - auto& pointPlotAction = settingsAction.getPlotAction().getPointPlotAction(); - auto& pointOpacityAction = pointPlotAction.getOpacityAction(); - - _pointOpacityDatasetPickerAction.setFilterFunction([this, scatterplotPlugin](mv::Dataset dataset) -> bool { - if (!scatterplotPlugin->getPositionDataset().isValid()) - return false; - - if (dataset->getDataType() != PointType) - return false; - - return true; - }); - - connect(&_pointOpacityDatasetPickerAction, &DatasetPickerAction::currentIndexChanged, this, [this, &pointPlotAction, &pointOpacityAction, scatterplotPlugin](const int32_t& currentIndex) -> void { - const auto& pointOpacityDataset = _pointOpacityDatasetPickerAction.getCurrentDataset(); - - if (pointOpacityDataset.isValid()) - disconnect(&_pointOpacityDataset, &Dataset<>::guiNameChanged, this, nullptr); - - _pointOpacityDataset = pointOpacityDataset; - - connect(&_pointOpacityDataset, &Dataset<>::guiNameChanged, scatterplotPlugin, &ScatterplotPlugin::updateHeadsUpDisplay); - - pointPlotAction.setCurrentPointOpacityDataset(_pointOpacityDataset); - - if (!_pointOpacityDataset.isValid()) - pointOpacityAction.setCurrentSourceIndex(ScalarSourceModel::DefaultRow::Constant); - }); - - connect(&pointOpacityAction, &ScalarAction::sourceSelectionChanged, this, [this, &pointOpacityAction](const uint32_t& sourceSelectionIndex) -> void { - _pointOpacityDatasetPickerAction.setCurrentDataset(pointOpacityAction.isSourceDataset() ? pointOpacityAction.getCurrentDataset() : nullptr); - - if (!pointOpacityAction.isSourceDataset()) - _pointOpacityDatasetPickerAction.setCurrentIndex(-1); - }); -} - void DatasetsAction::invalidateDatasetPickerActionFilters() { _positionDatasetPickerAction.invalidateFilter(); _colorDatasetPickerAction.invalidateFilter(); - _pointSizeDatasetPickerAction.invalidateFilter(); - _pointOpacityDatasetPickerAction.invalidateFilter(); } diff --git a/src/DatasetsAction.h b/src/DatasetsAction.h index c71acbf..89a666b 100644 --- a/src/DatasetsAction.h +++ b/src/DatasetsAction.h @@ -21,8 +21,6 @@ class DatasetsAction : public GroupAction Q_INVOKABLE DatasetsAction(QObject* parent, const QString& title); mv::Dataset getColorDataset() { return _colorDataset; } - mv::Dataset getPointSizeDataset() { return _pointSizeDataset; } - mv::Dataset getPointOpacityDataset() { return _pointOpacityDataset; } protected: // Linking @@ -73,18 +71,6 @@ class DatasetsAction : public GroupAction */ void setupColorDatasetPickerAction(ScatterplotPlugin* scatterplotPlugin); - /** - * Set up the point size dataset picker action with the point size datasets from the scatter plot plugin - * @param scatterplotPlugin Pointer to scatter plot plugin whose point size datasets are used to populate the dataset picker action - */ - void setupPointSizeDatasetPickerAction(ScatterplotPlugin* scatterplotPlugin); - - /** - * Set up the point opacity dataset picker action with the point opacity datasets from the scatter plot plugin - * @param scatterplotPlugin Pointer to scatter plot plugin whose point opacity datasets are used to populate the dataset picker action - */ - void setupPointOpacityDatasetPickerAction(ScatterplotPlugin* scatterplotPlugin); - /** Update the filters of the dataset picker actions based on the current datasets in the scatter plot plugin */ void invalidateDatasetPickerActionFilters(); @@ -92,18 +78,12 @@ class DatasetsAction : public GroupAction DatasetPickerAction& getPositionDatasetPickerAction() { return _positionDatasetPickerAction; } DatasetPickerAction& getColorDatasetPickerAction() { return _colorDatasetPickerAction; } - DatasetPickerAction& getPointSizeDatasetPickerAction() { return _pointSizeDatasetPickerAction; } - DatasetPickerAction& getPointOpacityDatasetPickerAction() { return _pointOpacityDatasetPickerAction; } private: ScatterplotPlugin* _scatterplotPlugin; /** Pointer to scatter plot plugin */ DatasetPickerAction _positionDatasetPickerAction; /** Dataset picker action for position dataset */ DatasetPickerAction _colorDatasetPickerAction; /** Dataset picker action for color dataset */ - DatasetPickerAction _pointSizeDatasetPickerAction; /** Dataset picker action for point size */ - DatasetPickerAction _pointOpacityDatasetPickerAction; /** Dataset picker action for point opacity */ mv::Dataset _colorDataset; /** Smart pointer to dataset used for coloring (if any) */ - mv::Dataset _pointSizeDataset; /** Smart pointer to dataset for driving point size (if any) */ - mv::Dataset _pointOpacityDataset; /** Smart pointer to dataset for driving point opacity (if any) */ friend class mv::AbstractActionsManager; friend class ScatterplotPlugin; diff --git a/src/ScalarAction.cpp b/src/ScalarAction.cpp index 1c8d2da..f500bac 100644 --- a/src/ScalarAction.cpp +++ b/src/ScalarAction.cpp @@ -9,7 +9,8 @@ using namespace mv::gui; ScalarAction::ScalarAction(QObject* parent, const QString& title, const float& minimum /*= 0.0f*/, const float& maximum /*= 100.0f*/, const float& value /*= 0.0f*/) : GroupAction(parent, title), _magnitudeAction(this, title, minimum, maximum, value), - _sourceAction(this, QString("%1 source").arg(title)) + _sourceAction(this, QString("%1 source").arg(title)), + _sourceDatasetPickerAction(this, "Source dataset") { setDefaultWidgetFlags(GroupAction::Horizontal); setShowLabels(false); @@ -17,10 +18,14 @@ ScalarAction::ScalarAction(QObject* parent, const QString& title, const float& m addAction(&_magnitudeAction); addAction(&_sourceAction); + _sourceDatasetPickerAction.setDefaultWidgetFlag(OptionAction::Clearable); + connect(&_sourceAction.getPickerAction(), &OptionAction::currentIndexChanged, this, [this](const std::uint32_t& currentIndex) { bool emitSourceSelectionChanged = true; if (currentIndex >= ScalarSourceModel::DefaultRow::DatasetStart) { + _sourceDatasetPickerAction.setCurrentDataset(_sourceAction.getModel().getDataset(currentIndex)); + if (auto scatterplotPlugin = dynamic_cast(findPluginAncestor())) { auto positionDataset = scatterplotPlugin->getPositionDataset(); auto scalarSourcePointsDataset = Dataset(getCurrentDataset()); @@ -33,12 +38,39 @@ ScalarAction::ScalarAction(QObject* parent, const QString& title, const float& m scatterplotPlugin->addNotification(QString("The number of points in the scalar source dataset does not match the number of points in the position dataset. (numPositions=%1, numScalars:%2)").arg(QString::number(numPositions), QString::number(numScalars))); } } + } else { + _sourceDatasetPickerAction.setCurrentIndex(-1); } if (emitSourceSelectionChanged) emit sourceSelectionChanged(currentIndex); }); + connect(&_sourceDatasetPickerAction, &DatasetPickerAction::datasetPicked, this, [this](const Dataset<>& dataset) { + if (_currentDataset.isValid()) { + disconnect(&_currentDataset, &Dataset<>::dataChanged, this, nullptr); + disconnect(&_currentDataset, &Dataset<>::guiNameChanged, this, nullptr); + } + + _currentDataset = dataset; + + const auto datasetRowIndex = _sourceAction.getModel().getRowIndex(_currentDataset); + + _sourceAction.getPickerAction().setCurrentIndex(_currentDataset.isValid() ? datasetRowIndex : ScalarSourceModel::DefaultRow::Constant); + + if (!_currentDataset.isValid()) + return; + + connect(&_currentDataset, &Dataset<>::dataChanged, this, [this]() -> void { + emit sourceDataChanged(getCurrentDataset()); + }); + + connect(&_currentDataset, &Dataset<>::guiNameChanged, this, [this]() -> void { + if (auto scatterplotPlugin = dynamic_cast(findPluginAncestor())) + scatterplotPlugin->updateHeadsUpDisplay(); + }); + }); + connect(&_magnitudeAction, &DecimalAction::valueChanged, this, [this](const float& value) { emit magnitudeChanged(value); }); @@ -88,22 +120,12 @@ void ScalarAction::removeAllDatasets() Dataset ScalarAction::getCurrentDataset() { - auto& scalarSourceModel = _sourceAction.getModel(); - - const auto currentSourceIndex = _sourceAction.getPickerAction().getCurrentIndex(); - - if (currentSourceIndex < ScalarSourceModel::DefaultRow::DatasetStart) - return {}; - - return scalarSourceModel.getDataset(currentSourceIndex); + return _sourceDatasetPickerAction.getCurrentDataset(); } void ScalarAction::setCurrentDataset(const Dataset& dataset) { - const auto datasetRowIndex = _sourceAction.getModel().getRowIndex(dataset); - - if (datasetRowIndex >= 0) - _sourceAction.getPickerAction().setCurrentIndex(datasetRowIndex); + _sourceDatasetPickerAction.setCurrentDataset(dataset); } void ScalarAction::setCurrentSourceIndex(std::int32_t sourceIndex) @@ -162,6 +184,7 @@ void ScalarAction::fromVariantMap(const QVariantMap& variantMap) _magnitudeAction.fromParentVariantMap(variantMap); _sourceAction.fromParentVariantMap(variantMap); + _sourceDatasetPickerAction.fromParentVariantMap(variantMap); } QVariantMap ScalarAction::toVariantMap() const @@ -170,6 +193,7 @@ QVariantMap ScalarAction::toVariantMap() const _magnitudeAction.insertIntoVariantMap(variantMap); _sourceAction.insertIntoVariantMap(variantMap); + _sourceDatasetPickerAction.insertIntoVariantMap(variantMap); return variantMap; -} \ No newline at end of file +} diff --git a/src/ScalarAction.h b/src/ScalarAction.h index b24f375..35e92bb 100644 --- a/src/ScalarAction.h +++ b/src/ScalarAction.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include "ScalarSourceAction.h" @@ -81,7 +82,7 @@ class ScalarAction : public GroupAction /** * Load widget action from variant map - * @param Variant map representation of the widget action + * @param variantMap Variant map representation of the widget action */ void fromVariantMap(const QVariantMap& variantMap) override; @@ -96,11 +97,13 @@ class ScalarAction : public GroupAction DecimalAction& getMagnitudeAction() { return _magnitudeAction; } ScalarSourceAction& getSourceAction() { return _sourceAction; } + const DatasetPickerAction& getSourceDatasetPickerAction() { return _sourceDatasetPickerAction; } + signals: /** * Signals that the source selection changed - * @param sourceIndex Index of the selected source (0 is constant, 1 is selection, above is a dataset) + * @param sourceSelectionIndex Index of the selected source (0 is constant, 1 is selection, above is a dataset) */ void sourceSelectionChanged(const std::uint32_t& sourceSelectionIndex); @@ -125,13 +128,15 @@ class ScalarAction : public GroupAction /** * Signals that the scalar offset changed - * @param magnitude Scalar magnitude + * @param offset Scalar offset */ void offsetChanged(const float& offset); private: - DecimalAction _magnitudeAction; /** Scalar magnitude action */ - ScalarSourceAction _sourceAction; /** Scalar source action */ + DatasetPickerAction _sourceDatasetPickerAction; /** Dataset picker action for source dataset selection */ + DecimalAction _magnitudeAction; /** Scalar magnitude action */ + ScalarSourceAction _sourceAction; /** Scalar source action */ + Dataset<> _currentDataset; /** Cached current dataset (if any) */ friend class mv::AbstractActionsManager; }; diff --git a/src/ScatterplotPlugin.cpp b/src/ScatterplotPlugin.cpp index 62b4d77..eaf47e0 100644 --- a/src/ScatterplotPlugin.cpp +++ b/src/ScatterplotPlugin.cpp @@ -405,7 +405,7 @@ void ScatterplotPlugin::init() connect(&datasetsAction.getPositionDatasetPickerAction(), &DatasetPickerAction::currentIndexChanged, this, &ScatterplotPlugin::updateHeadsUpDisplay); connect(&datasetsAction.getColorDatasetPickerAction(), &DatasetPickerAction::currentIndexChanged, this, &ScatterplotPlugin::updateHeadsUpDisplay); - connect(&datasetsAction.getPointSizeDatasetPickerAction(), &DatasetPickerAction::currentIndexChanged, this, &ScatterplotPlugin::updateHeadsUpDisplay); + connect(&pointPlotAction.getSizeAction(), &ScalarAction::sourceSelectionChanged, this, &ScatterplotPlugin::updateHeadsUpDisplay); connect(&pointPlotAction.getOpacityAction(), &ScalarAction::sourceSelectionChanged, this, &ScatterplotPlugin::updateHeadsUpDisplay); updateHeadsUpDisplay(); @@ -418,6 +418,14 @@ void ScatterplotPlugin::init() } else { datasetsAction.invalidateDatasetPickerActionFilters(); } + + connect(&_settingsAction.getPlotAction().getPointPlotAction().getSizeAction(), &ScalarAction::sourceDataChanged, this, [this]() -> void { + _scatterPlotWidget->update(); + }); + + connect(&_settingsAction.getPlotAction().getPointPlotAction().getOpacityAction(), &ScalarAction::sourceDataChanged, this, [this]() -> void { + _scatterPlotWidget->update(); + }); } void ScatterplotPlugin::loadData(const Datasets& datasets) From e1bac5fc4861b4e4120b268887a42a1930ae90f1 Mon Sep 17 00:00:00 2001 From: Thomas Kroes Date: Wed, 4 Mar 2026 13:03:43 +0100 Subject: [PATCH 23/33] Make SettingsAction and toolbar QPointers (#238) Convert SettingsAction and HorizontalToolbarAction members to QPointer types and create them with new, updating construction and all call sites accordingly. Update ScatterplotPlugin::getSettingsAction() to return a reference to the dereferenced QPointer. Replace direct member accesses with pointer-style (->) calls and adjust several call sites to obtain SettingsAction via dynamic_cast from parent()/parent()->parent() where appropriate (DatasetsAction, DensityPlotAction). Miscellaneous fixes: update toolbar widget creation, parent assignment, and serialization calls to use the new pointer members; small formatting and reference adjustments throughout impacted files. --- src/DatasetsAction.cpp | 4 +- src/DensityPlotAction.cpp | 6 +-- src/ScatterplotPlugin.cpp | 108 +++++++++++++++++++------------------- src/ScatterplotPlugin.h | 20 +++---- 4 files changed, 69 insertions(+), 69 deletions(-) diff --git a/src/DatasetsAction.cpp b/src/DatasetsAction.cpp index 4175033..2523689 100644 --- a/src/DatasetsAction.cpp +++ b/src/DatasetsAction.cpp @@ -28,7 +28,7 @@ DatasetsAction::DatasetsAction(QObject* parent, const QString& title) : if (scatterplotPlugin == nullptr) return; - auto& settingsAction = scatterplotPlugin->getSettingsAction(); + auto& settingsAction = *dynamic_cast(parent); auto& plotAction = settingsAction.getPlotAction(); auto& pointPlotAction = plotAction.getPointPlotAction(); @@ -132,7 +132,7 @@ void DatasetsAction::setupPositionDatasetPickerAction(ScatterplotPlugin* scatter void DatasetsAction::setupColorDatasetPickerAction(ScatterplotPlugin* scatterplotPlugin) { - auto& settingsAction = scatterplotPlugin->getSettingsAction(); + auto& settingsAction = *dynamic_cast(parent()); _colorDatasetPickerAction.setFilterFunction([this, scatterplotPlugin](mv::Dataset dataset) -> bool { if (!(dataset->getDataType() == PointType || dataset->getDataType() == ColorType || dataset->getDataType() == ClusterType)) diff --git a/src/DensityPlotAction.cpp b/src/DensityPlotAction.cpp index 114d806..00fde50 100644 --- a/src/DensityPlotAction.cpp +++ b/src/DensityPlotAction.cpp @@ -30,7 +30,7 @@ void DensityPlotAction::initialize(ScatterplotPlugin* scatterplotPlugin) _scatterplotPlugin = scatterplotPlugin; const auto computeDensity = [this]() -> void { - if (static_cast(_scatterplotPlugin->getSettingsAction().getRenderModeAction().getCurrentIndex()) == ScatterplotWidget::RenderMode::SCATTERPLOT) + if (static_cast(dynamic_cast(parent()->parent())->getRenderModeAction().getCurrentIndex()) == ScatterplotWidget::RenderMode::SCATTERPLOT) return; _scatterplotPlugin->getScatterplotWidget().setSigma(_sigmaAction.getValue()); @@ -38,7 +38,7 @@ void DensityPlotAction::initialize(ScatterplotPlugin* scatterplotPlugin) const auto maxDensity = _scatterplotPlugin->getScatterplotWidget().getDensityRenderer().getMaxDensity(); if (maxDensity > 0) - _scatterplotPlugin->getSettingsAction().getColoringAction().getColorMap1DAction().getRangeAction(ColorMapAction::Axis::X).setRange({ 0.0f, maxDensity }); + dynamic_cast(parent()->parent())->getColoringAction().getColorMap1DAction().getRangeAction(ColorMapAction::Axis::X).setRange({ 0.0f, maxDensity }); }; connect(&_sigmaAction, &DecimalAction::valueChanged, this, computeDensity); @@ -59,7 +59,7 @@ void DensityPlotAction::initialize(ScatterplotPlugin* scatterplotPlugin) computeDensity(); }); - connect(&_scatterplotPlugin->getSettingsAction().getRenderModeAction(), &OptionAction::currentIndexChanged, this, computeDensity); + connect(&dynamic_cast(parent()->parent())->getRenderModeAction(), &OptionAction::currentIndexChanged, this, computeDensity); updateSigmaAction(); computeDensity(); diff --git a/src/ScatterplotPlugin.cpp b/src/ScatterplotPlugin.cpp index eaf47e0..c071758 100644 --- a/src/ScatterplotPlugin.cpp +++ b/src/ScatterplotPlugin.cpp @@ -50,8 +50,8 @@ ScatterplotPlugin::ScatterplotPlugin(const PluginFactory* factory) : _dropWidget(nullptr), _scatterPlotWidget(new ScatterplotWidget(this)), _numPoints(0), - _settingsAction(this, "Settings"), - _primaryToolbarAction(this, "Primary Toolbar") + _settingsAction(new SettingsAction(this, "Settings")), + _primaryToolbarAction(new HorizontalToolbarAction(this, "Primary Toolbar")) { setObjectName("Scatterplot"); @@ -83,25 +83,25 @@ ScatterplotPlugin::ScatterplotPlugin(const PluginFactory* factory) : getWidget().setFocusPolicy(Qt::ClickFocus); - _primaryToolbarAction.addAction(&_settingsAction.getDatasetsAction()); - _primaryToolbarAction.addAction(&_settingsAction.getRenderModeAction(), 3, GroupAction::Horizontal); - _primaryToolbarAction.addAction(&_settingsAction.getPositionAction(), 1, GroupAction::Horizontal); - _primaryToolbarAction.addAction(&_settingsAction.getPlotAction(), 2, GroupAction::Horizontal); - _primaryToolbarAction.addAction(&_settingsAction.getColoringAction()); - _primaryToolbarAction.addAction(&_settingsAction.getSubsetAction()); - _primaryToolbarAction.addAction(&_settingsAction.getClusteringAction()); - _primaryToolbarAction.addAction(&_settingsAction.getSelectionAction()); - _primaryToolbarAction.addAction(&getSamplerAction()); + _primaryToolbarAction->addAction(&_settingsAction->getDatasetsAction()); + _primaryToolbarAction->addAction(&_settingsAction->getRenderModeAction(), 3, GroupAction::Horizontal); + _primaryToolbarAction->addAction(&_settingsAction->getPositionAction(), 1, GroupAction::Horizontal); + _primaryToolbarAction->addAction(&_settingsAction->getPlotAction(), 2, GroupAction::Horizontal); + _primaryToolbarAction->addAction(&_settingsAction->getColoringAction()); + _primaryToolbarAction->addAction(&_settingsAction->getSubsetAction()); + _primaryToolbarAction->addAction(&_settingsAction->getClusteringAction()); + _primaryToolbarAction->addAction(&_settingsAction->getSelectionAction()); + _primaryToolbarAction->addAction(&getSamplerAction()); auto focusSelectionAction = new ToggleAction(this, "Focus selection"); focusSelectionAction->setIconByName("mouse-pointer"); connect(focusSelectionAction, &ToggleAction::toggled, this, [this](bool toggled) -> void { - _settingsAction.getPlotAction().getPointPlotAction().getFocusSelection().setChecked(toggled); + _settingsAction->getPlotAction().getPointPlotAction().getFocusSelection().setChecked(toggled); }); - connect(&_settingsAction.getPlotAction().getPointPlotAction().getFocusSelection(), &ToggleAction::toggled, this, [this, focusSelectionAction](bool toggled) -> void { + connect(&_settingsAction->getPlotAction().getPointPlotAction().getFocusSelection(), &ToggleAction::toggled, this, [this, focusSelectionAction](bool toggled) -> void { focusSelectionAction->setChecked(toggled); }); @@ -114,13 +114,13 @@ ScatterplotPlugin::ScatterplotPlugin(const PluginFactory* factory) : connect(_scatterPlotWidget, &ScatterplotWidget::renderModeChanged, this, updateReadOnly); connect(&_positionDataset, &Dataset::changed, this, updateReadOnly); - //_secondaryToolbarAction.addAction(&_settingsAction.getMiscellaneousAction()); + //_secondaryToolbarAction.addAction(&_settingsAction->getMiscellaneousAction()); connect(_scatterPlotWidget, &ScatterplotWidget::customContextMenuRequested, this, [this](const QPoint& point) { if (!_positionDataset.isValid()) return; - auto contextMenu = _settingsAction.getContextMenu(); + auto contextMenu = _settingsAction->getContextMenu(); contextMenu->addSeparator(); @@ -165,7 +165,7 @@ ScatterplotPlugin::ScatterplotPlugin(const PluginFactory* factory) : // Load as point positions when no dataset is currently loaded dropRegions << new DropWidget::DropRegion(this, "Point position", description, "map-marker-alt", true, [this, candidateDataset]() { _positionDataset = candidateDataset; - _settingsAction.getColoringAction().setCurrentColorDataset(nullptr); + _settingsAction->getColoringAction().setCurrentColorDataset(nullptr); }); } else { @@ -174,7 +174,7 @@ ScatterplotPlugin::ScatterplotPlugin(const PluginFactory* factory) : // The number of points is equal, so offer the option to replace the existing points dataset dropRegions << new DropWidget::DropRegion(this, "Point position", description, "map-marker-alt", true, [this, candidateDataset]() { _positionDataset = candidateDataset; - _settingsAction.getColoringAction().setCurrentColorDataset(nullptr); + _settingsAction->getColoringAction().setCurrentColorDataset(nullptr); }); } @@ -200,7 +200,7 @@ ScatterplotPlugin::ScatterplotPlugin(const PluginFactory* factory) : 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 + _settingsAction->getColoringAction().setCurrentColorDataset(candidateDataset); // calls addColorDataset internally }); } @@ -209,12 +209,12 @@ ScatterplotPlugin::ScatterplotPlugin(const PluginFactory* factory) : 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); + _settingsAction->getPlotAction().getPointPlotAction().setCurrentPointSizeDataset(candidateDataset); }); // Offer the option to use the points dataset as source for points opacity dropRegions << new DropWidget::DropRegion(this, "Point opacity", QString("Set %1 points opacity with %2").arg(_positionDataset->text(), candidateDataset->text()), "brush", true, [this, candidateDataset]() { - _settingsAction.getPlotAction().getPointPlotAction().setCurrentPointOpacityDataset(candidateDataset); + _settingsAction->getPlotAction().getPointPlotAction().setCurrentPointOpacityDataset(candidateDataset); }); } } @@ -232,11 +232,11 @@ ScatterplotPlugin::ScatterplotPlugin(const PluginFactory* factory) : // Only allow user to color by clusters when there is a positions dataset loaded if (_positionDataset.isValid()) { - if (_settingsAction.getColoringAction().hasColorDataset(candidateDataset)) { + if (_settingsAction->getColoringAction().hasColorDataset(candidateDataset)) { // The clusters dataset is already loaded dropRegions << new DropWidget::DropRegion(this, "Color", description, "palette", true, [this, candidateDataset]() { - _settingsAction.getColoringAction().setCurrentColorDataset(candidateDataset); + _settingsAction->getColoringAction().setCurrentColorDataset(candidateDataset); }); } else { @@ -260,8 +260,8 @@ ScatterplotPlugin::ScatterplotPlugin(const PluginFactory* factory) : { // Use the clusters set for points color dropRegions << new DropWidget::DropRegion(this, "Color", description, "palette", true, [this, candidateDataset]() { - _settingsAction.getColoringAction().addColorDataset(candidateDataset); - _settingsAction.getColoringAction().setCurrentColorDataset(candidateDataset); + _settingsAction->getColoringAction().addColorDataset(candidateDataset); + _settingsAction->getColoringAction().setCurrentColorDataset(candidateDataset); }); } else @@ -282,7 +282,7 @@ ScatterplotPlugin::ScatterplotPlugin(const PluginFactory* factory) : return dropRegions; }); - auto& selectionAction = _settingsAction.getSelectionAction(); + auto& selectionAction = _settingsAction->getSelectionAction(); getSamplerAction().initialize(this, &selectionAction.getPixelSelectionAction(), &selectionAction.getSamplerPixelSelectionAction()); getSamplerAction().getEnabledAction().setChecked(false); @@ -306,7 +306,7 @@ void ScatterplotPlugin::init() layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); - layout->addWidget(_primaryToolbarAction.createWidget(&getWidget())); + layout->addWidget(_primaryToolbarAction->createWidget(&getWidget())); layout->addWidget(_scatterPlotWidget, 100); auto& navigationAction = _scatterPlotWidget->getPointRendererNavigator().getNavigationAction(); @@ -315,7 +315,7 @@ void ScatterplotPlugin::init() layout->addWidget(navigationWidget); layout->setAlignment(navigationWidget, Qt::AlignCenter); - navigationAction.setParent(&_settingsAction); + navigationAction.setParent(_settingsAction); } getWidget().setLayout(layout); @@ -345,7 +345,7 @@ void ScatterplotPlugin::init() connect(&_positionDataset, &Dataset::dataSelectionChanged, this, &ScatterplotPlugin::updateSelection); connect(&_positionDataset, &Dataset<>::guiNameChanged, this, &ScatterplotPlugin::updateHeadsUpDisplay); - connect(&_settingsAction.getMiscellaneousAction().getBackgroundColorAction(), &ColorAction::colorChanged, this, &ScatterplotPlugin::updateHeadsUpDisplayTextColor); + connect(&_settingsAction->getMiscellaneousAction().getBackgroundColorAction(), &ColorAction::colorChanged, this, &ScatterplotPlugin::updateHeadsUpDisplayTextColor); connect(&getScatterplotWidget().getPointRendererNavigator().getNavigationAction().getZoomSelectionAction(), &TriggerAction::triggered, this, [this]() -> void { if (_selectionBoundaries.isValid()) @@ -400,8 +400,8 @@ void ScatterplotPlugin::init() }); #endif - auto& datasetsAction = _settingsAction.getDatasetsAction(); - auto& pointPlotAction = _settingsAction.getPlotAction().getPointPlotAction(); + auto& datasetsAction = _settingsAction->getDatasetsAction(); + auto& pointPlotAction = _settingsAction->getPlotAction().getPointPlotAction(); connect(&datasetsAction.getPositionDatasetPickerAction(), &DatasetPickerAction::currentIndexChanged, this, &ScatterplotPlugin::updateHeadsUpDisplay); connect(&datasetsAction.getColorDatasetPickerAction(), &DatasetPickerAction::currentIndexChanged, this, &ScatterplotPlugin::updateHeadsUpDisplay); @@ -419,11 +419,11 @@ void ScatterplotPlugin::init() datasetsAction.invalidateDatasetPickerActionFilters(); } - connect(&_settingsAction.getPlotAction().getPointPlotAction().getSizeAction(), &ScalarAction::sourceDataChanged, this, [this]() -> void { + connect(&_settingsAction->getPlotAction().getPointPlotAction().getSizeAction(), &ScalarAction::sourceDataChanged, this, [this]() -> void { _scatterPlotWidget->update(); }); - connect(&_settingsAction.getPlotAction().getPointPlotAction().getOpacityAction(), &ScalarAction::sourceDataChanged, this, [this]() -> void { + connect(&_settingsAction->getPlotAction().getPointPlotAction().getOpacityAction(), &ScalarAction::sourceDataChanged, this, [this]() -> void { _scatterPlotWidget->update(); }); } @@ -438,7 +438,7 @@ void ScatterplotPlugin::loadData(const Datasets& datasets) _positionDataset = datasets.first(); // And set the coloring mode to constant - _settingsAction.getColoringAction().getColorByAction().setCurrentIndex(0); + _settingsAction->getColoringAction().getColorByAction().setCurrentIndex(0); } void ScatterplotPlugin::createSubset(const bool& fromSourceData /*= false*/, const QString& name /*= ""*/) @@ -463,7 +463,7 @@ void ScatterplotPlugin::selectPoints() auto& pixelSelectionTool = _scatterPlotWidget->getPixelSelectionTool(); - auto renderer = _settingsAction.getRenderModeAction().getCurrentIndex() > 0 ? dynamic_cast(&_scatterPlotWidget->_densityRenderer) : dynamic_cast(&_scatterPlotWidget->_pointRenderer); + auto renderer = _settingsAction->getRenderModeAction().getCurrentIndex() > 0 ? dynamic_cast(&_scatterPlotWidget->_densityRenderer) : dynamic_cast(&_scatterPlotWidget->_pointRenderer); auto& navigator = renderer->getNavigator(); // Only proceed with a valid points position dataset and when the pixel selection tool is active @@ -662,11 +662,11 @@ void ScatterplotPlugin::samplePoints() _scatterPlotWidget->update(); - auto& coloringAction = _settingsAction.getColoringAction(); + auto& coloringAction = _settingsAction->getColoringAction(); getSamplerAction().setSampleContext({ { "PositionDatasetID", _positionDataset.getDatasetId() }, - { "ColorDatasetID", _settingsAction.getColoringAction().getCurrentColorDataset().getDatasetId() }, + { "ColorDatasetID", _settingsAction->getColoringAction().getCurrentColorDataset().getDatasetId() }, { "LocalPointIndices", localPointIndices }, { "GlobalPointIndices", globalPointIndices }, { "Distances", distances }, @@ -675,7 +675,7 @@ void ScatterplotPlugin::samplePoints() { "ColorMap1D", coloringAction.getColorMap1DAction().getColorMapImage() }, { "ColorMap2D", coloringAction.getColorMap2DAction().getColorMapImage() }, { "ColorDimensionIndex", coloringAction.getDimensionAction().getCurrentDimensionAction().getCurrentIndex() }, - { "RenderMode", _settingsAction.getRenderModeAction().getCurrentText() } + { "RenderMode", _settingsAction->getRenderModeAction().getCurrentText() } }); } @@ -815,12 +815,12 @@ void ScatterplotPlugin::loadColors(const Dataset& pointsColor, const std } catch (const std::exception& e) { qDebug() << "ScatterplotPlugin::loadColors: mapping failed -> " << e.what(); - _settingsAction.getColoringAction().getColorByAction().setCurrentIndex(0); // reset to color by constant + _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 + _settingsAction->getColoringAction().getColorByAction().setCurrentIndex(0); // reset to color by constant return; } @@ -833,7 +833,7 @@ void ScatterplotPlugin::loadColors(const Dataset& pointsColor, const std _scatterPlotWidget->setScalars(colorScalars); _scatterPlotWidget->setScalarEffect(PointEffect::Color); - _settingsAction.getColoringAction().updateColorMapActionScalarRange(); + _settingsAction->getColoringAction().updateColorMapActionScalarRange(); // Render getWidget().update(); @@ -913,8 +913,8 @@ void ScatterplotPlugin::updateData() if (_positionDataset.isValid()) { // Get the selected dimensions to use as X and Y dimension in the plot - const auto xDim = _settingsAction.getPositionAction().getDimensionX(); - const auto yDim = _settingsAction.getPositionAction().getDimensionY(); + const auto xDim = _settingsAction->getPositionAction().getDimensionX(); + const auto yDim = _settingsAction->getPositionAction().getDimensionY(); // If one of the dimensions was not set, do not draw anything if (xDim < 0 || yDim < 0) @@ -923,8 +923,8 @@ void ScatterplotPlugin::updateData() // Ensure that if positionDataset has now more points, the additional points are plotted if (_numPoints != _positionDataset->getNumPoints()) { - _settingsAction.getPlotAction().getPointPlotAction().updateScatterPlotWidgetPointSizeScalars(); - _settingsAction.getPlotAction().getPointPlotAction().updateScatterPlotWidgetPointOpacityScalars(); + _settingsAction->getPlotAction().getPointPlotAction().updateScatterPlotWidgetPointSizeScalars(); + _settingsAction->getPlotAction().getPointPlotAction().updateScatterPlotWidgetPointOpacityScalars(); } // Determine number of points depending on if its a full dataset or a subset @@ -1002,11 +1002,11 @@ void ScatterplotPlugin::updateSelection() _scatterPlotWidget->update(); - auto& coloringAction = _settingsAction.getColoringAction(); + auto& coloringAction = _settingsAction->getColoringAction(); getSamplerAction().setSampleContext({ { "PositionDatasetID", _positionDataset.getDatasetId() }, - { "ColorDatasetID", _settingsAction.getColoringAction().getCurrentColorDataset().getDatasetId() }, + { "ColorDatasetID", _settingsAction->getColoringAction().getCurrentColorDataset().getDatasetId() }, { "LocalPointIndices", localPointIndices }, { "GlobalPointIndices", globalPointIndices }, { "Distances", QVariantList()}, @@ -1015,7 +1015,7 @@ void ScatterplotPlugin::updateSelection() { "ColorMap1D", coloringAction.getColorMap1DAction().getColorMapImage() }, { "ColorMap2D", coloringAction.getColorMap2DAction().getColorMapImage() }, { "ColorDimensionIndex", coloringAction.getDimensionAction().getCurrentDimensionAction().getCurrentIndex() }, - { "RenderMode", _settingsAction.getRenderModeAction().getCurrentText() } + { "RenderMode", _settingsAction->getRenderModeAction().getCurrentText() } }); } } @@ -1027,7 +1027,7 @@ void ScatterplotPlugin::updateHeadsUpDisplay() getHeadsUpDisplayAction().removeAllHeadsUpDisplayItems(); - auto& coloringAction = _settingsAction.getColoringAction(); + auto& coloringAction = _settingsAction->getColoringAction(); if (_positionDataset.isValid()) { const auto datasetsItem = getHeadsUpDisplayAction().addHeadsUpDisplayItem("Datasets", "", ""); @@ -1042,7 +1042,7 @@ void ScatterplotPlugin::updateHeadsUpDisplay() if (coloringAction.getColorByAction().getCurrentIndex() >= 2) addMetaDataToHeadsUpDisplay("Color", coloringAction.getCurrentColorDataset(), datasetsItem); - auto& pointPlotAction = _settingsAction.getPlotAction().getPointPlotAction(); + auto& pointPlotAction = _settingsAction->getPlotAction().getPointPlotAction(); //qDebug() << "ScatterplotPlugin::updateHeadsUpDisplay: point size dataset: " << pointPlotAction.getSizeAction().getCurrentDataset().isValid() << ", opacity dataset: " << pointPlotAction.getOpacityAction().getCurrentDataset().isValid(); addMetaDataToHeadsUpDisplay("Size", pointPlotAction.getSizeAction().getCurrentDataset(), datasetsItem); @@ -1058,7 +1058,7 @@ void ScatterplotPlugin::updateHeadsUpDisplayTextColor() if (auto headsUpDisplayWidgetTreeView = headsUpDisplayWidget->findChild("TreeView")) { QPalette palette = headsUpDisplayWidgetTreeView->palette(); - palette.setColor(QPalette::Text, _settingsAction.getMiscellaneousAction().getBackgroundColorAction().getColor().lightnessF() > .5f ? Qt::black : Qt::white); + palette.setColor(QPalette::Text, _settingsAction->getMiscellaneousAction().getBackgroundColorAction().getColor().lightnessF() > .5f ? Qt::black : Qt::white); headsUpDisplayWidgetTreeView->setPalette(palette); } @@ -1075,8 +1075,8 @@ void ScatterplotPlugin::fromVariantMap(const QVariantMap& variantMap) pointRenderer.getNavigator().resetView(true); - _primaryToolbarAction.fromParentVariantMap(variantMap); - _settingsAction.fromParentVariantMap(variantMap); + _primaryToolbarAction->fromParentVariantMap(variantMap); + _settingsAction->fromParentVariantMap(variantMap); if (pointRenderer.getNavigator().getNavigationAction().getSerializationCountFrom() == 0) { _scatterPlotWidget->update(); @@ -1089,8 +1089,8 @@ QVariantMap ScatterplotPlugin::toVariantMap() const { QVariantMap variantMap = ViewPlugin::toVariantMap(); - _primaryToolbarAction.insertIntoVariantMap(variantMap); - _settingsAction.insertIntoVariantMap(variantMap); + _primaryToolbarAction->insertIntoVariantMap(variantMap); + _settingsAction->insertIntoVariantMap(variantMap); return variantMap; } diff --git a/src/ScatterplotPlugin.h b/src/ScatterplotPlugin.h index a3966c5..8649f82 100644 --- a/src/ScatterplotPlugin.h +++ b/src/ScatterplotPlugin.h @@ -88,7 +88,7 @@ class ScatterplotPlugin : public ViewPlugin /** Get reference to the scatter plot widget */ ScatterplotWidget& getScatterplotWidget(); - SettingsAction& getSettingsAction() { return _settingsAction; } + SettingsAction& getSettingsAction() { return *_settingsAction; } private: void updateData(); @@ -114,15 +114,15 @@ class ScatterplotPlugin : public ViewPlugin QVariantMap toVariantMap() const override; private: - mv::gui::DropWidget* _dropWidget; /** Widget for dropping datasets */ - ScatterplotWidget* _scatterPlotWidget; /** The visualization widget */ - Dataset _positionDataset; /** Smart pointer to points dataset for point position */ - Dataset _positionSourceDataset; /** Smart pointer to source of the points dataset for point position (if any) */ - std::vector _positions; /** Point positions */ - unsigned int _numPoints; /** Number of point positions */ - SettingsAction _settingsAction; /** Group action for all settings */ - HorizontalToolbarAction _primaryToolbarAction; /** Horizontal toolbar for primary content */ - QRectF _selectionBoundaries; /** Boundaries of the selection */ + mv::gui::DropWidget* _dropWidget; /** Widget for dropping datasets */ + ScatterplotWidget* _scatterPlotWidget; /** The visualization widget */ + Dataset _positionDataset; /** Smart pointer to points dataset for point position */ + Dataset _positionSourceDataset; /** Smart pointer to source of the points dataset for point position (if any) */ + std::vector _positions; /** Point positions */ + unsigned int _numPoints; /** Number of point positions */ + QPointer _settingsAction; /** Group action for all settings */ + QPointer _primaryToolbarAction; /** Horizontal toolbar for primary content */ + QRectF _selectionBoundaries; /** Boundaries of the selection */ static const std::int32_t LAZY_UPDATE_INTERVAL = 2; From 28e77069fbcf4e7acd1d9043d0230e180b7f0de6 Mon Sep 17 00:00:00 2001 From: Alexander Vieth Date: Mon, 30 Mar 2026 17:39:40 +0200 Subject: [PATCH 24/33] Fix warnings and update points (#240) * Update number of points to uint64 * Use reference dataset * Add some const * Rename lambda capture variable to not shadow function paramters * More uint64 * Set MSVC warning level to W3 * We only want one dataset --- CMakeLists.txt | 2 +- src/MappingUtils.cpp | 18 +++++++++--------- src/MappingUtils.h | 8 ++++---- src/ScatterplotPlugin.cpp | 14 +++++++------- src/ScatterplotPlugin.h | 2 +- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b9674d0..59655d7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,7 +21,7 @@ set(CMAKE_AUTORCC ON) set(CMAKE_AUTOMOC ON) if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /DWIN32 /EHsc /MP /permissive- /Zc:__cplusplus") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /DWIN32 /EHsc /W3 /MP /permissive- /Zc:__cplusplus") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MDd") set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} /MD") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MD") diff --git a/src/MappingUtils.cpp b/src/MappingUtils.cpp index e238064..5483a24 100644 --- a/src/MappingUtils.cpp +++ b/src/MappingUtils.cpp @@ -14,7 +14,7 @@ #include #include -std::pair getSelectionMapping(const mv::Dataset& source, const mv::Dataset& target, LinkedDataCondition checkMapping) { +std::pair getSelectionMapping(const mv::Dataset& source, const mv::Dataset& target, LinkedDataCondition checkMapping) { const std::vector& linkedDatas = source->getLinkedData(); if (linkedDatas.empty()) @@ -34,18 +34,18 @@ std::pair getSelectionMapping(const mv::Dat 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 { +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 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; +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); @@ -58,7 +58,7 @@ std::pair getSelectionMappingPositionsToCol return { mapping, numTargetPoints }; } -std::pair getSelectionMappingPositionSourceToColors(const mv::Dataset& positions, const mv::Dataset& colors) { +std::pair getSelectionMappingPositionSourceToColors(const mv::Dataset& positions, const mv::Dataset& colors) { if (!positions->isDerivedData()) return { nullptr, 0 }; @@ -77,7 +77,7 @@ bool checkSurjectiveMapping(const mv::LinkedData& linkedData, const std::uint32_ std::uint32_t count = 0; for (const auto& [key, vec] : linkedMap) { - for (std::uint32_t val : vec) { + for (const std::uint32_t val : vec) { if (val >= numPointsInTarget) continue; // Skip values that are too large if (!found[val]) { diff --git a/src/MappingUtils.h b/src/MappingUtils.h index 62a4cba..6229dcb 100644 --- a/src/MappingUtils.h +++ b/src/MappingUtils.h @@ -39,17 +39,17 @@ using LinkedDataCondition = std::function getSelectionMapping(const mv::Dataset& source, const mv::Dataset& target, LinkedDataCondition checkMapping); +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); +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); +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); +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); diff --git a/src/ScatterplotPlugin.cpp b/src/ScatterplotPlugin.cpp index c071758..42d2939 100644 --- a/src/ScatterplotPlugin.cpp +++ b/src/ScatterplotPlugin.cpp @@ -138,10 +138,10 @@ ScatterplotPlugin::ScatterplotPlugin(const PluginFactory* factory) : if (datasetsMimeData == nullptr) return dropRegions; - if (datasetsMimeData->getDatasets().count() > 1) + if (datasetsMimeData->getDatasetsCount() != 1) return dropRegions; - const auto dataset = datasetsMimeData->getDatasets().first(); + const auto& dataset = datasetsMimeData->getDatasetsRef().first(); const auto datasetGuiName = dataset->text(); const auto datasetId = dataset->getId(); const auto dataType = dataset->getDataType(); @@ -244,13 +244,13 @@ ScatterplotPlugin::ScatterplotPlugin(const PluginFactory* factory) : { // Check to set whether the number of data points comprised throughout all clusters is the same number // as the number of data points in the dataset we are trying to color - int totalNumIndices = 0; + std::uint64_t totalNumIndices = 0; for (const Cluster& cluster : candidateDataset->getClusters()) { totalNumIndices += cluster.getIndices().size(); } - int totalNumPoints = 0; + std::uint64_t totalNumPoints = 0; if (_positionDataset->isDerivedData()) totalNumPoints = _positionSourceDataset->getFullDataset()->getNumPoints(); else @@ -755,7 +755,7 @@ void ScatterplotPlugin::loadColors(const Dataset& pointsColor, const std const mv::SelectionMap::Map& mapColorsToPositions = selectionMapping->getMapping().getMap(); for (const auto& [fromColorID, vecOfPositionIDs] : mapColorsToPositions) { - for (std::uint32_t toPositionID : vecOfPositionIDs) { + for (const std::uint32_t toPositionID : vecOfPositionIDs) { mappedColorScalars[toPositionID] = colorScalars[fromColorID]; } } @@ -775,7 +775,7 @@ void ScatterplotPlugin::loadColors(const Dataset& pointsColor, const std for (const auto& [fromPositionID, vecOfColorIDs] : mapPositionsToColors) { if (mappedColorScalars[fromPositionID] != std::numeric_limits::lowest()) continue; - for (std::uint32_t toColorID : vecOfColorIDs) { + for (const std::uint32_t toColorID : vecOfColorIDs) { mappedColorScalars[fromPositionID] = colorScalars[toColorID]; } } @@ -846,7 +846,7 @@ void ScatterplotPlugin::loadColors(const Dataset& clusters) return; // Get global indices from the position dataset - int totalNumPoints = 0; + std::uint64_t totalNumPoints = 0; if (_positionDataset->isDerivedData()) totalNumPoints = _positionSourceDataset->getFullDataset()->getNumPoints(); else diff --git a/src/ScatterplotPlugin.h b/src/ScatterplotPlugin.h index 8649f82..437876b 100644 --- a/src/ScatterplotPlugin.h +++ b/src/ScatterplotPlugin.h @@ -119,7 +119,7 @@ class ScatterplotPlugin : public ViewPlugin Dataset _positionDataset; /** Smart pointer to points dataset for point position */ Dataset _positionSourceDataset; /** Smart pointer to source of the points dataset for point position (if any) */ std::vector _positions; /** Point positions */ - unsigned int _numPoints; /** Number of point positions */ + std::uint64_t _numPoints; /** Number of point positions */ QPointer _settingsAction; /** Group action for all settings */ QPointer _primaryToolbarAction; /** Horizontal toolbar for primary content */ QRectF _selectionBoundaries; /** Boundaries of the selection */ From 109e3d170e1fc24459a2e56f992e631bd45d8ad6 Mon Sep 17 00:00:00 2001 From: Julian Thijssen Date: Fri, 17 Apr 2026 16:13:24 +0200 Subject: [PATCH 25/33] Update core requirement due to previous commit --- PluginInfo.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PluginInfo.json b/PluginInfo.json index a09ee50..9dc9e34 100644 --- a/PluginInfo.json +++ b/PluginInfo.json @@ -2,7 +2,7 @@ "name" : "Scatterplot View", "version" : { "plugin" : "1.0.0", - "core" : ["1.3"] + "core" : ["1.5"] }, "type" : "View", "dependencies" : ["Points"] From a555b331a1d581b2c9d6cc3ed6ad8300435290f4 Mon Sep 17 00:00:00 2001 From: Thomas Kroes Date: Tue, 30 Jun 2026 18:51:13 +0200 Subject: [PATCH 26/33] Adhere to new serialization API (#243) * Use new getter for clarity (avoid negation) (#242) * Use `mv_project_defaults()` for setting CMake defaults (#241) * Use mv project defaults * Simplify unity build setup * Prefer target based properties * Set cache variable instead of normal variable for CMake option * Adhere to revamped core --------- Co-authored-by: Alexander Vieth --- CMakeLists.txt | 18 +++++------------- conanfile.py | 2 +- src/ScalarAction.cpp | 4 ++-- src/ScatterplotPlugin.cpp | 3 ++- src/SettingsAction.cpp | 4 ++-- 5 files changed, 12 insertions(+), 19 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 59655d7..39453a5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,17 +15,7 @@ PROJECT(${PROJECT} # ----------------------------------------------------------------------------- # CMake Options # ----------------------------------------------------------------------------- -set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) set(CMAKE_INCLUDE_CURRENT_DIR ON) -set(CMAKE_AUTORCC ON) -set(CMAKE_AUTOMOC ON) - -if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /DWIN32 /EHsc /W3 /MP /permissive- /Zc:__cplusplus") - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MDd") - set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} /MD") - set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MD") -endif() # ----------------------------------------------------------------------------- # Dependencies @@ -33,6 +23,7 @@ endif() find_package(Qt6 COMPONENTS Widgets WebEngineWidgets OpenGL OpenGLWidgets REQUIRED) find_package(ManiVault COMPONENTS Core PointData ClusterData ColorData ImageData CONFIG QUIET) +mv_project_defaults() # ----------------------------------------------------------------------------- # Source files @@ -116,9 +107,10 @@ target_include_directories(${PROJECT} PRIVATE "${ManiVault_INCLUDE_DIR}") # ----------------------------------------------------------------------------- target_compile_features(${PROJECT} PRIVATE cxx_std_20) -if(MV_UNITY_BUILD) - set_target_properties(${PROJECT} PROPERTIES UNITY_BUILD ON) -endif() +set_target_properties(${PROJECT} PROPERTIES + AUTOMOC ON + UNITY_BUILD ${MV_UNITY_BUILD} +) # ----------------------------------------------------------------------------- # Target library linking diff --git a/conanfile.py b/conanfile.py index 3b84dde..73491bf 100644 --- a/conanfile.py +++ b/conanfile.py @@ -105,7 +105,7 @@ def generate(self): tc.variables["ManiVault_DIR"] = manivault_dir # Set some build options - tc.variables["MV_UNITY_BUILD"] = "ON" + tc.cache_variables["MV_UNITY_BUILD"] = True tc.generate() diff --git a/src/ScalarAction.cpp b/src/ScalarAction.cpp index f500bac..57653e4 100644 --- a/src/ScalarAction.cpp +++ b/src/ScalarAction.cpp @@ -184,7 +184,7 @@ void ScalarAction::fromVariantMap(const QVariantMap& variantMap) _magnitudeAction.fromParentVariantMap(variantMap); _sourceAction.fromParentVariantMap(variantMap); - _sourceDatasetPickerAction.fromParentVariantMap(variantMap); + //_sourceDatasetPickerAction.fromParentVariantMap(variantMap); } QVariantMap ScalarAction::toVariantMap() const @@ -193,7 +193,7 @@ QVariantMap ScalarAction::toVariantMap() const _magnitudeAction.insertIntoVariantMap(variantMap); _sourceAction.insertIntoVariantMap(variantMap); - _sourceDatasetPickerAction.insertIntoVariantMap(variantMap); + //_sourceDatasetPickerAction.insertIntoVariantMap(variantMap); return variantMap; } diff --git a/src/ScatterplotPlugin.cpp b/src/ScatterplotPlugin.cpp index 42d2939..531eb45 100644 --- a/src/ScatterplotPlugin.cpp +++ b/src/ScatterplotPlugin.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -569,7 +570,7 @@ void ScatterplotPlugin::selectPoints() auto& navigationAction = navigator.getNavigationAction(); - navigationAction.getZoomSelectionAction().setEnabled(!targetSelectionIndices.empty() && !navigationAction.getFreezeNavigation().isChecked()); + navigationAction.getZoomSelectionAction().setEnabled(!targetSelectionIndices.empty() && navigationAction.isNavigationActive()); _positionDataset->setSelectionIndices(targetSelectionIndices); diff --git a/src/SettingsAction.cpp b/src/SettingsAction.cpp index 03d0f82..f0739dc 100644 --- a/src/SettingsAction.cpp +++ b/src/SettingsAction.cpp @@ -70,8 +70,8 @@ void SettingsAction::fromVariantMap(const QVariantMap& variantMap) _plotAction.fromParentVariantMap(variantMap); _positionAction.fromParentVariantMap(variantMap); _coloringAction.fromParentVariantMap(variantMap); - _subsetAction.fromParentVariantMap(variantMap); - _clusteringAction.fromParentVariantMap(variantMap); + _subsetAction.fromParentVariantMap(variantMap, true); + _clusteringAction.fromParentVariantMap(variantMap, true); _renderModeAction.fromParentVariantMap(variantMap); _selectionAction.fromParentVariantMap(variantMap); _miscellaneousAction.fromParentVariantMap(variantMap); From 09914e23f036267228d62f415c5345a54fc1e23e Mon Sep 17 00:00:00 2001 From: Thomas Kroes Date: Tue, 21 Jul 2026 12:26:17 +0200 Subject: [PATCH 27/33] Set current point dataset when opacity dataset changed --- src/PointPlotAction.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/PointPlotAction.cpp b/src/PointPlotAction.cpp index e5c33ab..2425ab6 100644 --- a/src/PointPlotAction.cpp +++ b/src/PointPlotAction.cpp @@ -162,6 +162,10 @@ void PointPlotAction::initialize(ScatterplotPlugin* scatterplotPlugin) connect(&_opacityAction, &ScalarAction::magnitudeChanged, this, &PointPlotAction::updateScatterPlotWidgetPointOpacityScalars); connect(&_opacityAction, &ScalarAction::offsetChanged, this, &PointPlotAction::updateScatterPlotWidgetPointOpacityScalars); connect(&_opacityAction, &ScalarAction::sourceSelectionChanged, this, &PointPlotAction::updateScatterPlotWidgetPointOpacityScalars); + connect(&_opacityAction.getSourceDatasetPickerAction(), &DatasetPickerAction::datasetPicked, this, [this](Dataset<> picked) -> void { + setCurrentPointOpacityDataset(Dataset(picked)); + }); + connect(&_opacityAction, &ScalarAction::sourceDataChanged, this, &PointPlotAction::updateScatterPlotWidgetPointOpacityScalars); connect(&_opacityAction, &ScalarAction::scalarRangeChanged, this, &PointPlotAction::updateScatterPlotWidgetPointOpacityScalars); } From 004ae93e1bdff658fad19f297859546b2704fda9 Mon Sep 17 00:00:00 2001 From: Thomas Kroes Date: Tue, 21 Jul 2026 12:26:30 +0200 Subject: [PATCH 28/33] Add extra null guard --- src/ScalarAction.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/ScalarAction.cpp b/src/ScalarAction.cpp index 57653e4..24f86b6 100644 --- a/src/ScalarAction.cpp +++ b/src/ScalarAction.cpp @@ -29,13 +29,16 @@ ScalarAction::ScalarAction(QObject* parent, const QString& title, const float& m if (auto scatterplotPlugin = dynamic_cast(findPluginAncestor())) { auto positionDataset = scatterplotPlugin->getPositionDataset(); auto scalarSourcePointsDataset = Dataset(getCurrentDataset()); - const auto numScalars = scalarSourcePointsDataset->getNumPoints(); - const auto numPositions = positionDataset->getNumPoints(); - if (numScalars != numPositions) { - emitSourceSelectionChanged = false; + if (scalarSourcePointsDataset.isValid() && positionDataset.isValid()) { + const auto numScalars = scalarSourcePointsDataset->getNumPoints(); + const auto numPositions = positionDataset->getNumPoints(); - scatterplotPlugin->addNotification(QString("The number of points in the scalar source dataset does not match the number of points in the position dataset. (numPositions=%1, numScalars:%2)").arg(QString::number(numPositions), QString::number(numScalars))); + if (numScalars != numPositions) { + emitSourceSelectionChanged = false; + + scatterplotPlugin->addNotification(QString("The number of points in the scalar source dataset does not match the number of points in the position dataset. (numPositions=%1, numScalars:%2)").arg(QString::number(numPositions), QString::number(numScalars))); + } } } } else { From 9810b4cbeb009a88be7a703fb4b3bb9ac0612c62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20H=C3=B6llt?= Date: Thu, 23 Jul 2026 17:25:31 +0200 Subject: [PATCH 29/33] Extends coloring options for scatterplot (addressing issue #24) (#247) Adds 2D and 3D coloring options. 2D allows arbitrary 2 channels using the build in 2D colormaps 3D allows arbitrary 3 channels mapping directly to RGB (normalized in shader) Modes are automatically picked when datasets with exactly 2 or 3 channels are set as color or can be manually set using the extended color action Renames 2D colormaps according to their authors --- src/ColoringAction.cpp | 206 +++++++++++++++++++++++++++++++++++--- src/ColoringAction.h | 16 ++- src/ScatterplotPlugin.cpp | 71 +++++++++++-- src/ScatterplotPlugin.h | 28 ++++++ src/ScatterplotWidget.cpp | 16 ++- src/ScatterplotWidget.h | 6 ++ 6 files changed, 319 insertions(+), 24 deletions(-) diff --git a/src/ColoringAction.cpp b/src/ColoringAction.cpp index c3fca1f..71db211 100644 --- a/src/ColoringAction.cpp +++ b/src/ColoringAction.cpp @@ -16,7 +16,10 @@ ColoringAction::ColoringAction(QObject* parent, const QString& title) : _colorByModel(this), _colorByAction(this, "Color by"), _constantColorAction(this, "Constant color", DEFAULT_CONSTANT_COLOR), - _dimensionAction(this, "Dimension"), + _colorSpaceAction(this, "Color space", { "Scalar (1D)", "Duo (2D)", "RGB" }, "Scalar (1D)"), + _dimensionAction(this, "Dimension 1"), + _dimensionAction2(this, "Dimension 2"), + _dimensionAction3(this, "Dimension 3"), _colorMap1DAction(this, "1D Color map"), _colorMap2DAction(this, "2D Color map") { @@ -26,9 +29,14 @@ ColoringAction::ColoringAction(QObject* parent, const QString& title) : addAction(&_colorByAction); addAction(&_constantColorAction); - addAction(&_colorMap2DAction); + addAction(&_colorSpaceAction); addAction(&_colorMap1DAction); + addAction(&_colorMap2DAction); addAction(&_dimensionAction); + addAction(&_dimensionAction2); + addAction(&_dimensionAction3); + + _colorSpaceAction.setToolTip("Color space for data-driven coloring"); _scatterplotPlugin->getWidget().addAction(&_colorByAction); _scatterplotPlugin->getWidget().addAction(&_dimensionAction); @@ -86,28 +94,51 @@ ColoringAction::ColoringAction(QObject* parent, const QString& title) : connect(&_currentColorPointsDataset, &Dataset::dataDimensionsChanged, this, [this]() { if (_currentColorPointsDataset.isValid()) { _dimensionAction.setPointsDataset(_currentColorPointsDataset); + _dimensionAction2.setPointsDataset(_currentColorPointsDataset); + _dimensionAction3.setPointsDataset(_currentColorPointsDataset); updateScatterPlotWidgetColors(); } }); _dimensionAction.setPointsDataset(_currentColorPointsDataset); + _dimensionAction2.setPointsDataset(_currentColorPointsDataset); + _dimensionAction3.setPointsDataset(_currentColorPointsDataset); + + // Auto-select the color space for datasets with exactly two or three channels + if (!mv::projects().isOpeningProject()) { + const auto numDimensions = static_cast(_currentColorPointsDataset->getNumDimensions()); + + if (numDimensions == 2) { + _colorSpaceAction.setCurrentIndex(1); // Duo (2D) + applyDefaultChannels(); // also apply defaults when the index was already Duo + } + else if (numDimensions == 3) { + _colorSpaceAction.setCurrentIndex(2); // RGB + applyDefaultChannels(); // also apply defaults when the index was already RGB + } + } } else { _dimensionAction.setPointsDataset(Dataset()); + _dimensionAction2.setPointsDataset(Dataset()); + _dimensionAction3.setPointsDataset(Dataset()); } } else { _dimensionAction.setPointsDataset(Dataset()); + _dimensionAction2.setPointsDataset(Dataset()); + _dimensionAction3.setPointsDataset(Dataset()); } - //_dimensionAction.setVisible(currentColorDatasetTypeIsPointType); emit currentColorDatasetChanged(currentColorDataset); } else { _dimensionAction.setPointsDataset(Dataset()); - //_dimensionAction.setVisible(false); + _dimensionAction2.setPointsDataset(Dataset()); + _dimensionAction3.setPointsDataset(Dataset()); } + updateChannelActionsReadOnly(); updateScatterPlotWidgetColors(); updateScatterplotWidgetColorMap(); updateColorMapActionScalarRange(); @@ -142,7 +173,25 @@ ColoringAction::ColoringAction(QObject* parent, const QString& title) : updateColorMapActionsReadOnly(); updateColorMapActionScalarRange(); }); - + + connect(&_dimensionAction2, &DimensionPickerAction::currentDimensionIndexChanged, this, [this](const int32_t& currentDimensionIndex) { + updateScatterPlotWidgetColors(); + }); + + connect(&_dimensionAction3, &DimensionPickerAction::currentDimensionIndexChanged, this, [this](const int32_t& currentDimensionIndex) { + updateScatterPlotWidgetColors(); + }); + + connect(&_colorSpaceAction, &OptionAction::currentIndexChanged, this, [this](const std::int32_t& currentIndex) { + if (!mv::projects().isOpeningProject()) + applyDefaultChannels(); + + updateChannelActionsReadOnly(); + updateScatterPlotWidgetColors(); + updateScatterplotWidgetColorMap(); + updateColorMapActionsReadOnly(); + }); + connect(&_constantColorAction, &ColorAction::colorChanged, this, &ColoringAction::updateScatterplotWidgetColorMap); connect(&_colorMap1DAction, &ColorMapAction::imageChanged, this, &ColoringAction::updateScatterplotWidgetColorMap); connect(&_colorMap2DAction, &ColorMapAction::imageChanged, this, &ColoringAction::updateScatterplotWidgetColorMap); @@ -160,6 +209,7 @@ ColoringAction::ColoringAction(QObject* parent, const QString& title) : updateScatterplotWidgetColorMap(); updateColorMapActionScalarRange(); + updateChannelActionsReadOnly(); _scatterplotPlugin->getScatterplotWidget().setColoringMode(ScatterplotWidget::ColoringMode::Constant); } @@ -251,10 +301,38 @@ void ColoringAction::updateScatterPlotWidgetColors() if (currentColorDataset->getDataType() == ClusterType) _scatterplotPlugin->loadColors(currentColorDataset.get()); else { - const auto currentDimensionIndex = _dimensionAction.getCurrentDimensionIndex(); + const auto dimension1 = _dimensionAction.getCurrentDimensionIndex(); + + if (dimension1 < 0) + return; + + switch (_colorSpaceAction.getCurrentIndex()) + { + case 1: // Duo (2D) + { + const auto dimension2 = _dimensionAction2.getCurrentDimensionIndex(); + + if (dimension2 >= 0) + _scatterplotPlugin->loadColors2D(currentColorDataset.get(), dimension1, dimension2); + + break; + } + + case 2: // RGB + { + const auto dimension2 = _dimensionAction2.getCurrentDimensionIndex(); + const auto dimension3 = _dimensionAction3.getCurrentDimensionIndex(); - if (currentDimensionIndex >= 0) - _scatterplotPlugin->loadColors(currentColorDataset.get(), _dimensionAction.getCurrentDimensionIndex()); + if (dimension2 >= 0 && dimension3 >= 0) + _scatterplotPlugin->loadColorsRGB(currentColorDataset.get(), dimension1, dimension2, dimension3); + + break; + } + + default: // Scalar (1D) + _scatterplotPlugin->loadColors(currentColorDataset.get(), dimension1); + break; + } } updateScatterplotWidgetColorMap(); @@ -297,7 +375,16 @@ void ColoringAction::updateScatterplotWidgetColorMap() scatterplotWidget.setColoringMode(ScatterplotWidget::ColoringMode::Scatter); } else { - scatterplotWidget.setColorMap(_colorMap1DAction.getColorMapImage().mirrored(false, true)); + const auto currentColorDataset = getCurrentColorDataset(); + const bool isDuo = currentColorDataset.isValid() && currentColorDataset->getDataType() == PointType && _colorSpaceAction.getCurrentIndex() == 1; + + if (isDuo) + // mirrored is deprecated in Qt 6.9, flipped can replace it + scatterplotWidget.setColorMap(_colorMap2DAction.getColorMapImage().mirrored(false, true)); + //scatterplotWidget.setColorMap(_colorMap2DAction.getColorMapImage().flipped(Qt::Vertical)); + else + scatterplotWidget.setColorMap(_colorMap1DAction.getColorMapImage().mirrored(false, true)); + //scatterplotWidget.setColorMap(_colorMap1DAction.getColorMapImage().flipped(Qt::Vertical)); } break; @@ -324,9 +411,22 @@ void ColoringAction::updateScatterplotWidgetColorMap() void ColoringAction::updateScatterPlotWidgetColorMapRange() { + auto& scatterplotWidget = _scatterplotPlugin->getScatterplotWidget(); + + // The adjustable 1D color-map range only drives the (channel 1) scalar range for 1D scalar coloring. + // In Duo/RGB the color channels each use their own automatically-computed range, so leave channel 1 + // untouched here (otherwise identical channels would normalize differently and produce a color tint). + if (scatterplotWidget.getRenderMode() == ScatterplotWidget::SCATTERPLOT) { + const auto currentColorDataset = getCurrentColorDataset(); + const bool isPointsSource = currentColorDataset.isValid() && currentColorDataset->getDataType() == PointType; + + if (isPointsSource && _colorSpaceAction.getCurrentIndex() != 0) // Duo (1) or RGB (2) + return; + } + const auto& rangeAction = _colorMap1DAction.getRangeAction(ColorMapAction::Axis::X); - _scatterplotPlugin->getScatterplotWidget().setColorMapRange(rangeAction.getMinimum(), rangeAction.getMaximum()); + scatterplotWidget.setColorMapRange(rangeAction.getMinimum(), rangeAction.getMaximum()); } bool ColoringAction::shouldEnableColorMap() const @@ -351,9 +451,71 @@ bool ColoringAction::shouldEnableColorMap() const void ColoringAction::updateColorMapActionsReadOnly() { const auto currentIndex = _colorByAction.getCurrentIndex(); + const bool isPointsSource = currentIndex >= 2 && _currentColorPointsDataset.isValid(); + const bool isDuo = isPointsSource && _colorSpaceAction.getCurrentIndex() == 1; + const bool isRGB = isPointsSource && _colorSpaceAction.getCurrentIndex() == 2; + + _colorMap1DAction.setEnabled(shouldEnableColorMap() && (currentIndex >= 2) && !isDuo && !isRGB); + _colorMap2DAction.setEnabled(shouldEnableColorMap() && (currentIndex == 1 || isDuo)); +} + +void ColoringAction::updateChannelActionsReadOnly() +{ + const auto currentIndex = _colorByAction.getCurrentIndex(); + const auto colorSpace = _colorSpaceAction.getCurrentIndex(); + + const bool isPointsSource = currentIndex >= 2 && _currentColorPointsDataset.isValid(); + + const bool isDuo = isPointsSource && colorSpace == 1; // Duo (2D) + const bool isRGB = isPointsSource && colorSpace == 2; // RGB + + // All actions remain visible; only their enabled state reflects the current coloring mode. + + // Constant color: only usable in constant mode + _constantColorAction.setEnabled(currentIndex == 0); + + // Color space selector: only usable for a points color source + _colorSpaceAction.setEnabled(isPointsSource); + + // Dimension pickers: channel 1 for any points source, channel 2 for Duo/RGB, channel 3 for RGB only + _dimensionAction.setEnabled(isPointsSource); + _dimensionAction2.setEnabled(isDuo || isRGB); + _dimensionAction3.setEnabled(isRGB); +} + +void ColoringAction::applyDefaultChannels() +{ + if (!_currentColorPointsDataset.isValid()) + return; + + const auto numDimensions = static_cast(_currentColorPointsDataset->getNumDimensions()); - _colorMap1DAction.setEnabled(shouldEnableColorMap() && (currentIndex >= 2)); - _colorMap2DAction.setEnabled(shouldEnableColorMap() && (currentIndex == 1)); + switch (_colorSpaceAction.getCurrentIndex()) + { + case 1: // Duo (2D): default to the first two channels + { + if (numDimensions >= 2) { + _dimensionAction.setCurrentDimensionIndex(0); + _dimensionAction2.setCurrentDimensionIndex(1); + } + + break; + } + + case 2: // RGB: default to the first three channels + { + if (numDimensions >= 3) { + _dimensionAction.setCurrentDimensionIndex(0); + _dimensionAction2.setCurrentDimensionIndex(1); + _dimensionAction3.setCurrentDimensionIndex(2); + } + + break; + } + + default: + break; + } } void ColoringAction::connectToPublicAction(WidgetAction* publicAction, bool recursive) @@ -368,7 +530,10 @@ void ColoringAction::connectToPublicAction(WidgetAction* publicAction, bool recu if (recursive) { actions().connectPrivateActionToPublicAction(&_colorByAction, &publicColoringAction->getColorByAction(), recursive); actions().connectPrivateActionToPublicAction(&_constantColorAction, &publicColoringAction->getConstantColorAction(), recursive); + actions().connectPrivateActionToPublicAction(&_colorSpaceAction, &publicColoringAction->getColorSpaceAction(), recursive); actions().connectPrivateActionToPublicAction(&_dimensionAction, &publicColoringAction->getDimensionAction(), recursive); + actions().connectPrivateActionToPublicAction(&_dimensionAction2, &publicColoringAction->getDimensionAction2(), recursive); + actions().connectPrivateActionToPublicAction(&_dimensionAction3, &publicColoringAction->getDimensionAction3(), recursive); actions().connectPrivateActionToPublicAction(&_colorMap1DAction, &publicColoringAction->getColorMap1DAction(), recursive); actions().connectPrivateActionToPublicAction(&_colorMap2DAction, &publicColoringAction->getColorMap2DAction(), recursive); } @@ -384,7 +549,10 @@ void ColoringAction::disconnectFromPublicAction(bool recursive) if (recursive) { actions().disconnectPrivateActionFromPublicAction(&_colorByAction, recursive); actions().disconnectPrivateActionFromPublicAction(&_constantColorAction, recursive); + actions().disconnectPrivateActionFromPublicAction(&_colorSpaceAction, recursive); actions().disconnectPrivateActionFromPublicAction(&_dimensionAction, recursive); + actions().disconnectPrivateActionFromPublicAction(&_dimensionAction2, recursive); + actions().disconnectPrivateActionFromPublicAction(&_dimensionAction3, recursive); actions().disconnectPrivateActionFromPublicAction(&_colorMap2DAction, recursive); } @@ -395,11 +563,22 @@ void ColoringAction::fromVariantMap(const QVariantMap& variantMap) { GroupAction::fromVariantMap(variantMap); + // Restore the color source first so the dimension pickers are targeted at the right dataset, + // then restore the color space and channels, and finally the color maps. _colorByAction.fromParentVariantMap(variantMap); _constantColorAction.fromParentVariantMap(variantMap); _dimensionAction.fromParentVariantMap(variantMap); + _dimensionAction2.fromParentVariantMap(variantMap); + _dimensionAction3.fromParentVariantMap(variantMap); + _colorSpaceAction.fromParentVariantMap(variantMap); _colorMap1DAction.fromParentVariantMap(variantMap); _colorMap2DAction.fromParentVariantMap(variantMap); + + // Apply the fully-restored coloring state + updateChannelActionsReadOnly(); + updateScatterPlotWidgetColors(); + updateScatterplotWidgetColorMap(); + updateColorMapActionsReadOnly(); } QVariantMap ColoringAction::toVariantMap() const @@ -408,7 +587,10 @@ QVariantMap ColoringAction::toVariantMap() const _colorByAction.insertIntoVariantMap(variantMap); _constantColorAction.insertIntoVariantMap(variantMap); + _colorSpaceAction.insertIntoVariantMap(variantMap); _dimensionAction.insertIntoVariantMap(variantMap); + _dimensionAction2.insertIntoVariantMap(variantMap); + _dimensionAction3.insertIntoVariantMap(variantMap); _colorMap1DAction.insertIntoVariantMap(variantMap); _colorMap2DAction.insertIntoVariantMap(variantMap); diff --git a/src/ColoringAction.h b/src/ColoringAction.h index 4a1d480..a1de452 100644 --- a/src/ColoringAction.h +++ b/src/ColoringAction.h @@ -66,6 +66,12 @@ class ColoringAction : public VerticalGroupAction /** Update the color by action options */ void updateColorByActionOptions(); + /** Enable/disable the color space and channel picker actions for the current coloring mode */ + void updateChannelActionsReadOnly(); + + /** Set the dimension pickers to sensible defaults (the first channels) for the current color space */ + void applyDefaultChannels(); + /** Update the colors of the points in the scatter plot widget */ void updateScatterPlotWidgetColors(); @@ -119,7 +125,10 @@ class ColoringAction : public VerticalGroupAction OptionAction& getColorByAction() { return _colorByAction; } ColorAction& getConstantColorAction() { return _constantColorAction; } + OptionAction& getColorSpaceAction() { return _colorSpaceAction; } DimensionPickerAction& getDimensionAction() { return _dimensionAction; } + DimensionPickerAction& getDimensionAction2() { return _dimensionAction2; } + DimensionPickerAction& getDimensionAction3() { return _dimensionAction3; } ColorMapAction& getColorMap1DAction() { return _colorMap1DAction; } ColorMapAction& getColorMap2DAction() { return _colorMap2DAction; } @@ -131,7 +140,10 @@ class ColoringAction : public VerticalGroupAction ColorSourceModel _colorByModel; /** Color by model (model input for the color by action) */ OptionAction _colorByAction; /** Action for picking the coloring type */ ColorAction _constantColorAction; /** Action for picking the constant color */ - DimensionPickerAction _dimensionAction; /** Dimension picker action */ + OptionAction _colorSpaceAction; /** Color space for data coloring (Scalar 1D / Duo 2D / RGB) */ + DimensionPickerAction _dimensionAction; /** Dimension picker action (color channel 1) */ + DimensionPickerAction _dimensionAction2; /** Dimension picker action (color channel 2, for Duo/RGB) */ + DimensionPickerAction _dimensionAction3; /** Dimension picker action (color channel 3, for RGB) */ ColorMap1DAction _colorMap1DAction; /** One-dimensional color map action */ ColorMap2DAction _colorMap2DAction; /** Two-dimensional color map action */ Dataset _currentColorPointsDataset; /** Current color dataset */ @@ -145,4 +157,4 @@ class ColoringAction : public VerticalGroupAction Q_DECLARE_METATYPE(ColoringAction) -inline const auto coloringActionMetaTypeId = qRegisterMetaType("ColoringAction"); \ No newline at end of file +inline const auto coloringActionMetaTypeId = qRegisterMetaType("ColoringAction"); diff --git a/src/ScatterplotPlugin.cpp b/src/ScatterplotPlugin.cpp index 531eb45..2f18488 100644 --- a/src/ScatterplotPlugin.cpp +++ b/src/ScatterplotPlugin.cpp @@ -713,16 +713,16 @@ void ScatterplotPlugin::positionDatasetChanged() updateData(); } -void ScatterplotPlugin::loadColors(const Dataset& pointsColor, const std::uint32_t& dimensionIndex) +bool ScatterplotPlugin::mapColorScalars(const Dataset& pointsColor, const std::uint32_t& dimensionIndex, std::vector& colorScalars) { // Only proceed with valid points dataset if (!pointsColor.isValid()) - return; + return false; const auto numColorPoints = pointsColor->getNumPoints(); // Generate point colorScalars for color mapping - std::vector colorScalars = {}; + colorScalars.clear(); pointsColor->extractDataForDimension(colorScalars, dimensionIndex); // If number of points do not match, use a mapping @@ -815,14 +815,12 @@ void ScatterplotPlugin::loadColors(const Dataset& pointsColor, const std } catch (const std::exception& e) { - qDebug() << "ScatterplotPlugin::loadColors: mapping failed -> " << e.what(); - _settingsAction->getColoringAction().getColorByAction().setCurrentIndex(0); // reset to color by constant - return; + qDebug() << "ScatterplotPlugin::mapColorScalars: mapping failed -> " << e.what(); + return false; } catch (...) { - qDebug() << "ScatterplotPlugin::loadColors: mapping failed for an unknown reason."; - _settingsAction->getColoringAction().getColorByAction().setCurrentIndex(0); // reset to color by constant - return; + qDebug() << "ScatterplotPlugin::mapColorScalars: mapping failed for an unknown reason."; + return false; } std::swap(mappedColorScalars, colorScalars); @@ -830,6 +828,18 @@ void ScatterplotPlugin::loadColors(const Dataset& pointsColor, const std assert(colorScalars.size() == _numPoints); + return true; +} + +void ScatterplotPlugin::loadColors(const Dataset& pointsColor, const std::uint32_t& dimensionIndex) +{ + std::vector colorScalars = {}; + + if (!mapColorScalars(pointsColor, dimensionIndex, colorScalars)) { + _settingsAction->getColoringAction().getColorByAction().setCurrentIndex(0); // reset to color by constant + return; + } + // Assign colorScalars and scalar effect _scatterPlotWidget->setScalars(colorScalars); _scatterPlotWidget->setScalarEffect(PointEffect::Color); @@ -840,6 +850,49 @@ void ScatterplotPlugin::loadColors(const Dataset& pointsColor, const std getWidget().update(); } +void ScatterplotPlugin::loadColors2D(const Dataset& pointsColor, const std::uint32_t& dimensionIndexX, const std::uint32_t& dimensionIndexY) +{ + std::vector colorScalarsX = {}; + std::vector colorScalarsY = {}; + + if (!mapColorScalars(pointsColor, dimensionIndexX, colorScalarsX) || + !mapColorScalars(pointsColor, dimensionIndexY, colorScalarsY)) { + _settingsAction->getColoringAction().getColorByAction().setCurrentIndex(0); // reset to color by constant + return; + } + + // Assign both channels and the two-channel 2D coloring effect + _scatterPlotWidget->setScalars(colorScalarsX); + _scatterPlotWidget->setScalars2(colorScalarsY); + _scatterPlotWidget->setScalarEffect(PointEffect::Color2DChannels); + + // Render + getWidget().update(); +} + +void ScatterplotPlugin::loadColorsRGB(const Dataset& pointsColor, const std::uint32_t& dimensionIndexR, const std::uint32_t& dimensionIndexG, const std::uint32_t& dimensionIndexB) +{ + std::vector colorScalarsR = {}; + std::vector colorScalarsG = {}; + std::vector colorScalarsB = {}; + + if (!mapColorScalars(pointsColor, dimensionIndexR, colorScalarsR) || + !mapColorScalars(pointsColor, dimensionIndexG, colorScalarsG) || + !mapColorScalars(pointsColor, dimensionIndexB, colorScalarsB)) { + _settingsAction->getColoringAction().getColorByAction().setCurrentIndex(0); // reset to color by constant + return; + } + + // Assign the three channels and the RGB coloring effect + _scatterPlotWidget->setScalars(colorScalarsR); + _scatterPlotWidget->setScalars2(colorScalarsG); + _scatterPlotWidget->setScalars3(colorScalarsB); + _scatterPlotWidget->setScalarEffect(PointEffect::ColorRGB); + + // Render + getWidget().update(); +} + void ScatterplotPlugin::loadColors(const Dataset& clusters) { // Only proceed with valid clusters and position dataset diff --git a/src/ScatterplotPlugin.h b/src/ScatterplotPlugin.h index 437876b..ef32af8 100644 --- a/src/ScatterplotPlugin.h +++ b/src/ScatterplotPlugin.h @@ -63,6 +63,23 @@ class ScatterplotPlugin : public ViewPlugin */ void loadColors(const Dataset& points, const std::uint32_t& dimensionIndex); + /** + * Load 2D color from two dimensions of a points dataset (mapped through the 2D color map) + * @param points Smart pointer to points dataset + * @param dimensionIndexX Index of the dimension mapped to the color map x-axis + * @param dimensionIndexY Index of the dimension mapped to the color map y-axis + */ + void loadColors2D(const Dataset& points, const std::uint32_t& dimensionIndexX, const std::uint32_t& dimensionIndexY); + + /** + * Load RGB color from three dimensions of a points dataset + * @param points Smart pointer to points dataset + * @param dimensionIndexR Index of the dimension mapped to red + * @param dimensionIndexG Index of the dimension mapped to green + * @param dimensionIndexB Index of the dimension mapped to blue + */ + void loadColorsRGB(const Dataset& points, const std::uint32_t& dimensionIndexR, const std::uint32_t& dimensionIndexG, const std::uint32_t& dimensionIndexB); + /** * Load color from clusters dataset * @param clusters Smart pointer to clusters dataset @@ -113,6 +130,17 @@ class ScatterplotPlugin : public ViewPlugin */ QVariantMap toVariantMap() const override; +private: + + /** + * Extract dimension \p dimensionIndex from \p pointsColor and map it into the position dataset's point space + * @param pointsColor Smart pointer to the color points dataset + * @param dimensionIndex Index of the dimension to extract + * @param colorScalars Output vector of scalars, sized to the number of position points on success + * @return Boolean determining whether the mapping succeeded + */ + bool mapColorScalars(const Dataset& pointsColor, const std::uint32_t& dimensionIndex, std::vector& colorScalars); + private: mv::gui::DropWidget* _dropWidget; /** Widget for dropping datasets */ ScatterplotWidget* _scatterPlotWidget; /** The visualization widget */ diff --git a/src/ScatterplotWidget.cpp b/src/ScatterplotWidget.cpp index ff6f6f8..837ed7c 100644 --- a/src/ScatterplotWidget.cpp +++ b/src/ScatterplotWidget.cpp @@ -328,7 +328,21 @@ void ScatterplotWidget::setHighlights(const std::vector& highlights, const void ScatterplotWidget::setScalars(const std::vector& scalars) { _pointRenderer.setColorChannelScalars(scalars); - + + update(); +} + +void ScatterplotWidget::setScalars2(const std::vector& scalars) +{ + _pointRenderer.setColorChannel2Scalars(scalars); + + update(); +} + +void ScatterplotWidget::setScalars3(const std::vector& scalars) +{ + _pointRenderer.setColorChannel3Scalars(scalars); + update(); } diff --git a/src/ScatterplotWidget.h b/src/ScatterplotWidget.h index 9e73b49..4441bd6 100644 --- a/src/ScatterplotWidget.h +++ b/src/ScatterplotWidget.h @@ -80,6 +80,12 @@ class ScatterplotWidget : public QOpenGLWidget, protected QOpenGLFunctions_3_3_C void setHighlights(const std::vector& highlights, const std::int32_t& numSelectedPoints); void setScalars(const std::vector& scalars); + /** Set the second color scalar channel (used for 2D and RGB coloring) */ + void setScalars2(const std::vector& scalars); + + /** Set the third color scalar channel (used for RGB coloring) */ + void setScalars3(const std::vector& scalars); + /** * Set colors for each individual data point * @param colors Vector of colors (size must match that of the loaded points dataset) From 5104caf54312f44a44de7e902c4e379351aa3ba8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20H=C3=B6llt?= Date: Fri, 24 Jul 2026 10:00:32 +0200 Subject: [PATCH 30/33] Fixes Qt 6.10 build Replaced deprecated 'mirrored' method with 'flipped' for color maps. --- src/ColoringAction.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/ColoringAction.cpp b/src/ColoringAction.cpp index 71db211..ded2c5c 100644 --- a/src/ColoringAction.cpp +++ b/src/ColoringAction.cpp @@ -379,12 +379,9 @@ void ColoringAction::updateScatterplotWidgetColorMap() const bool isDuo = currentColorDataset.isValid() && currentColorDataset->getDataType() == PointType && _colorSpaceAction.getCurrentIndex() == 1; if (isDuo) - // mirrored is deprecated in Qt 6.9, flipped can replace it - scatterplotWidget.setColorMap(_colorMap2DAction.getColorMapImage().mirrored(false, true)); - //scatterplotWidget.setColorMap(_colorMap2DAction.getColorMapImage().flipped(Qt::Vertical)); + scatterplotWidget.setColorMap(_colorMap2DAction.getColorMapImage().flipped(Qt::Vertical)); else - scatterplotWidget.setColorMap(_colorMap1DAction.getColorMapImage().mirrored(false, true)); - //scatterplotWidget.setColorMap(_colorMap1DAction.getColorMapImage().flipped(Qt::Vertical)); + scatterplotWidget.setColorMap(_colorMap1DAction.getColorMapImage().flipped(Qt::Vertical)); } break; From 7485c00b922b5b68920541f19fc3c60b47c482c2 Mon Sep 17 00:00:00 2001 From: Alexander Vieth Date: Fri, 24 Jul 2026 10:02:34 +0200 Subject: [PATCH 31/33] CI: Remove Release build and install steps (#248) --- conanfile.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/conanfile.py b/conanfile.py index 73491bf..a8ba72b 100644 --- a/conanfile.py +++ b/conanfile.py @@ -120,7 +120,6 @@ def build(self): cmake = self._configure_cmake() cmake.build(build_type="RelWithDebInfo") - cmake.build(build_type="Release") def package(self): package_dir = pathlib.Path(self.build_folder, "package") @@ -138,23 +137,9 @@ def package(self): relWithDebInfo_dir, ] ) - subprocess.run( - [ - "cmake", - "--install", - self.build_folder, - "--config", - "Release", - "--prefix", - release_dir, - ] - ) self.copy(pattern="*", src=package_dir) def package_info(self): self.cpp_info.relwithdebinfo.libdirs = ["RelWithDebInfo/lib"] self.cpp_info.relwithdebinfo.bindirs = ["RelWithDebInfo/Plugins", "RelWithDebInfo"] self.cpp_info.relwithdebinfo.includedirs = ["RelWithDebInfo/include", "RelWithDebInfo"] - self.cpp_info.release.libdirs = ["Release/lib"] - self.cpp_info.release.bindirs = ["Release/Plugins", "Release"] - self.cpp_info.release.includedirs = ["Release/include", "Release"] From a86c3e03c3cebe9b0b45ab82d654864fe9a3e85a Mon Sep 17 00:00:00 2001 From: Alexander Vieth Date: Fri, 24 Jul 2026 10:06:15 +0200 Subject: [PATCH 32/33] Upgrade build workflow actions and Python version Updated build workflow to use newer versions of actions and Python. --- .github/workflows/build.yml | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 61ed56b..1023720 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,14 +5,10 @@ on: pull_request: workflow_dispatch: -env: - # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) - BUILD_TYPE: Release - # for matrix check https://docs.github.com/en/actions/reference/specifications-for-github-hosted-runners jobs: prepare_matrix: - runs-on: ubuntu-latest + runs-on: ubuntu-slim outputs: matrix: ${{ steps.matrix_setup.outputs.matrix }} steps: @@ -34,7 +30,7 @@ jobs: steps: - name: Checkout the source - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: submodules: recursive ref: ${{ github.event.pull_request.head.ref }} @@ -45,9 +41,9 @@ jobs: sudo xcode-select -switch /Applications/Xcode_${{matrix.build-xcode-version}}.app - name: Setup python version - uses: actions/setup-python@v5 + uses: actions/setup-python@v7 with: - python-version: "3.11" + python-version: "3.12" - name: Start ssh key agent uses: webfactory/ssh-agent@v0.9.0 From 73bf4dbcfa855201fd82e18fe2f99309dd050a3e Mon Sep 17 00:00:00 2001 From: Thomas Kroes Date: Tue, 28 Jul 2026 08:59:21 +0200 Subject: [PATCH 33/33] Revert principal dimension action name change (#250) * Revert principle dimension action name change * Ignore loading errors for newly introduced actions Do this for backwards compatibility --- src/ColoringAction.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ColoringAction.cpp b/src/ColoringAction.cpp index ded2c5c..a0859e1 100644 --- a/src/ColoringAction.cpp +++ b/src/ColoringAction.cpp @@ -17,7 +17,7 @@ ColoringAction::ColoringAction(QObject* parent, const QString& title) : _colorByAction(this, "Color by"), _constantColorAction(this, "Constant color", DEFAULT_CONSTANT_COLOR), _colorSpaceAction(this, "Color space", { "Scalar (1D)", "Duo (2D)", "RGB" }, "Scalar (1D)"), - _dimensionAction(this, "Dimension 1"), + _dimensionAction(this, "Dimension"), _dimensionAction2(this, "Dimension 2"), _dimensionAction3(this, "Dimension 3"), _colorMap1DAction(this, "1D Color map"), @@ -565,9 +565,9 @@ void ColoringAction::fromVariantMap(const QVariantMap& variantMap) _colorByAction.fromParentVariantMap(variantMap); _constantColorAction.fromParentVariantMap(variantMap); _dimensionAction.fromParentVariantMap(variantMap); - _dimensionAction2.fromParentVariantMap(variantMap); - _dimensionAction3.fromParentVariantMap(variantMap); - _colorSpaceAction.fromParentVariantMap(variantMap); + _dimensionAction2.fromParentVariantMap(variantMap, true); + _dimensionAction3.fromParentVariantMap(variantMap, true); + _colorSpaceAction.fromParentVariantMap(variantMap, true); _colorMap1DAction.fromParentVariantMap(variantMap); _colorMap2DAction.fromParentVariantMap(variantMap);