|
1 | 1 | #include "ScatterplotPlugin.h" |
2 | 2 |
|
| 3 | +#include "MappingUtils.h" |
3 | 4 | #include "ScatterplotWidget.h" |
4 | 5 |
|
5 | 6 | #include <Application.h> |
|
31 | 32 |
|
32 | 33 | #include <algorithm> |
33 | 34 | #include <cassert> |
| 35 | +#include <exception> |
| 36 | +#include <map> |
| 37 | +#include <stdexcept> |
34 | 38 | #include <vector> |
35 | 39 |
|
36 | 40 | #define VIEW_SAMPLING_HTML |
@@ -172,25 +176,35 @@ ScatterplotPlugin::ScatterplotPlugin(const PluginFactory* factory) : |
172 | 176 | }); |
173 | 177 | } |
174 | 178 |
|
175 | | - // Accept both data with the same number if points and data which is derived from |
176 | | - // a parent that has the same number of points (e.g. for HSNE embeddings) |
| 179 | + // Accept for recoloring: |
| 180 | + // 1. data with the same number of points |
| 181 | + // 2. data which is derived from a parent that has the same number of points (e.g. for HSNE embeddings), where we can use global indices for mapping |
| 182 | + // 3. data which has a fully-covering selection mapping, that we can use for setting colors. Mapping in order of preference: |
| 183 | + // a) from color (or it's parent) to position |
| 184 | + // b) from color to position (or it's parent) |
| 185 | + // c) from source of position to color |
| 186 | + |
| 187 | + // [1. Same number of points] |
177 | 188 | const auto numPointsCandidate = candidateDataset->getNumPoints(); |
178 | 189 | const auto numPointsPosition = _positionDataset->getNumPoints(); |
179 | | - const bool sameNumPoints = numPointsPosition == numPointsCandidate; |
180 | | - const bool sameNumPointsAsFull = |
181 | | - /*if*/ _positionDataset->isDerivedData() ? |
182 | | - /*then*/ _positionDataset->getSourceDataset<Points>()->getFullDataset<Points>()->getNumPoints() == numPointsCandidate : |
183 | | - /*else*/ false; |
| 190 | + const bool hasSameNumPoints = numPointsPosition == numPointsCandidate; |
| 191 | + |
| 192 | + // [2. Derived from a parent] |
| 193 | + const bool hasSameNumPointsAsFull = fullSourceHasSameNumPoints(_positionDataset, candidateDataset); |
184 | 194 |
|
185 | | - if (sameNumPoints || sameNumPointsAsFull) { |
| 195 | + // [3. Full selection mapping] |
| 196 | + const bool hasSelectionMapping = checkSelectionMapping(candidateDataset, _positionDataset); |
| 197 | + |
| 198 | + if (hasSameNumPoints || hasSameNumPointsAsFull || hasSelectionMapping) { |
186 | 199 | // Offer the option to use the points dataset as source for points colors |
187 | 200 | dropRegions << new DropWidget::DropRegion(this, "Point color", QString("Colorize %1 points with %2").arg(_positionDataset->text(), candidateDataset->text()), "palette", true, [this, candidateDataset]() { |
188 | 201 | _settingsAction.getColoringAction().setCurrentColorDataset(candidateDataset); // calls addColorDataset internally |
189 | 202 | }); |
190 | 203 |
|
191 | 204 | } |
192 | 205 |
|
193 | | - if (sameNumPoints) { |
| 206 | + // Accept for resizing and opacity: Only data with the same number of points |
| 207 | + if (hasSameNumPoints) { |
194 | 208 | // Offer the option to use the points dataset as source for points size |
195 | 209 | dropRegions << new DropWidget::DropRegion(this, "Point size", QString("Size %1 points with %2").arg(_positionDataset->text(), candidateDataset->text()), "ruler-horizontal", true, [this, candidateDataset]() { |
196 | 210 | _settingsAction.getPlotAction().getPointPlotAction().setCurrentPointSizeDataset(candidateDataset); |
@@ -647,49 +661,125 @@ void ScatterplotPlugin::positionDatasetChanged() |
647 | 661 | updateData(); |
648 | 662 | } |
649 | 663 |
|
650 | | -void ScatterplotPlugin::loadColors(const Dataset<Points>& points, const std::uint32_t& dimensionIndex) |
| 664 | +void ScatterplotPlugin::loadColors(const Dataset<Points>& pointsColor, const std::uint32_t& dimensionIndex) |
651 | 665 | { |
652 | 666 | // Only proceed with valid points dataset |
653 | | - if (!points.isValid()) |
| 667 | + if (!pointsColor.isValid()) |
654 | 668 | return; |
655 | 669 |
|
656 | | - // Generate point scalars for color mapping |
657 | | - std::vector<float> scalars; |
| 670 | + const auto numColorPoints = pointsColor->getNumPoints(); |
658 | 671 |
|
659 | | - points->extractDataForDimension(scalars, dimensionIndex); |
| 672 | + // Generate point colorScalars for color mapping |
| 673 | + std::vector<float> colorScalars = {}; |
| 674 | + pointsColor->extractDataForDimension(colorScalars, dimensionIndex); |
660 | 675 |
|
661 | | - const auto numColorPoints = points->getNumPoints(); |
| 676 | + // If number of points do not match, use a mapping |
| 677 | + // prefer global IDs (for derived data) over selection mapping |
| 678 | + // prefer color to position over position to color over source of position to color |
| 679 | + if (numColorPoints != _numPoints) { |
662 | 680 |
|
| 681 | + std::vector<float> mappedColorScalars(_numPoints, std::numeric_limits<float>::lowest()); |
663 | 682 |
|
664 | | - if (numColorPoints != _numPoints) { |
| 683 | + try { |
| 684 | + const bool hasSameNumPointsAsFull = fullSourceHasSameNumPoints(_positionDataset, pointsColor); |
665 | 685 |
|
666 | | - const bool sameNumPointsAsFull = |
667 | | - /*if*/ _positionDataset->isDerivedData() ? |
668 | | - /*then*/ _positionSourceDataset->getFullDataset<Points>()->getNumPoints() == numColorPoints : |
669 | | - /*else*/ false; |
| 686 | + if (hasSameNumPointsAsFull) { |
| 687 | + std::vector<std::uint32_t> globalIndices = {}; |
| 688 | + _positionDataset->getGlobalIndices(globalIndices); |
670 | 689 |
|
671 | | - if (sameNumPointsAsFull) { |
672 | | - std::vector<std::uint32_t> globalIndices; |
673 | | - _positionDataset->getGlobalIndices(globalIndices); |
| 690 | + for (std::int32_t localIndex = 0; localIndex < globalIndices.size(); localIndex++) { |
| 691 | + mappedColorScalars[localIndex] = colorScalars[globalIndices[localIndex]]; |
| 692 | + } |
674 | 693 |
|
675 | | - std::vector<float> localScalars(_numPoints, 0); |
676 | | - std::int32_t localColorIndex = 0; |
| 694 | + } |
| 695 | + else if ( // mapping from color data set to position data set |
| 696 | + const auto [selectionMapping, numPointsTarget] = getSelectionMappingColorsToPositions(pointsColor, _positionDataset); |
| 697 | + /* check if valid */ |
| 698 | + selectionMapping != nullptr && |
| 699 | + numPointsTarget == _numPoints && |
| 700 | + checkSurjectiveMapping(*selectionMapping, numPointsTarget) |
| 701 | + ) |
| 702 | + { |
| 703 | + // Map values like selection |
| 704 | + const mv::SelectionMap::Map& mapColorsToPositions = selectionMapping->getMapping().getMap(); |
677 | 705 |
|
678 | | - for (const auto& globalIndex : globalIndices) |
679 | | - localScalars[localColorIndex++] = scalars[globalIndex]; |
| 706 | + for (const auto& [fromColorID, vecOfPositionIDs] : mapColorsToPositions) { |
| 707 | + for (std::uint32_t toPositionID : vecOfPositionIDs) { |
| 708 | + mappedColorScalars[toPositionID] = colorScalars[fromColorID]; |
| 709 | + } |
| 710 | + } |
680 | 711 |
|
681 | | - std::swap(localScalars, scalars); |
682 | | - } |
683 | | - else { |
684 | | - qWarning("Number of points used for coloring does not match number of points in data, aborting attempt to color plot"); |
| 712 | + } |
| 713 | + else if ( // mapping from position data set to color data set |
| 714 | + const auto [selectionMapping, numPointsTarget] = getSelectionMappingPositionsToColors(_positionDataset, pointsColor); |
| 715 | + /* check if valid */ |
| 716 | + selectionMapping != nullptr && |
| 717 | + numPointsTarget == numColorPoints && |
| 718 | + checkSurjectiveMapping(*selectionMapping, numPointsTarget) |
| 719 | + ) |
| 720 | + { |
| 721 | + // Map values like selection (in reverse, use first value that occurs) |
| 722 | + const mv::SelectionMap::Map& mapPositionsToColors = selectionMapping->getMapping().getMap(); |
| 723 | + |
| 724 | + for (const auto& [fromPositionID, vecOfColorIDs] : mapPositionsToColors) { |
| 725 | + if (mappedColorScalars[fromPositionID] != std::numeric_limits<float>::lowest()) |
| 726 | + continue; |
| 727 | + for (std::uint32_t toColorID : vecOfColorIDs) { |
| 728 | + mappedColorScalars[fromPositionID] = colorScalars[toColorID]; |
| 729 | + } |
| 730 | + } |
| 731 | + |
| 732 | + } |
| 733 | + else if ( // mapping from source of position data set to color data set |
| 734 | + const auto [selectionMapping, numPointsTarget] = getSelectionMappingPositionSourceToColors(_positionDataset, pointsColor); |
| 735 | + /* check if valid */ |
| 736 | + selectionMapping != nullptr && |
| 737 | + numPointsTarget == numColorPoints && |
| 738 | + checkSurjectiveMapping(*selectionMapping, numPointsTarget) |
| 739 | + ) |
| 740 | + { |
| 741 | + // the selection map is from full source data of positions data to pointsColor |
| 742 | + // we need to use both the global indices of the positions (i.e. in the source) and the linked data mapping |
| 743 | + const mv::SelectionMap::Map& mapGlobalToColors = selectionMapping->getMapping().getMap(); |
| 744 | + std::vector<std::uint32_t> globalIndices = {}; |
| 745 | + _positionDataset->getGlobalIndices(globalIndices); |
| 746 | + |
| 747 | + for (std::int32_t localIndex = 0; localIndex < globalIndices.size(); localIndex++) { |
| 748 | + |
| 749 | + if (mappedColorScalars[localIndex] != std::numeric_limits<float>::lowest()) |
| 750 | + continue; |
| 751 | + |
| 752 | + const auto& indxColors = mapGlobalToColors.at(globalIndices[localIndex]); // from full source (parent) to colorDataset |
| 753 | + |
| 754 | + for (const auto& indColors : indxColors) { |
| 755 | + mappedColorScalars[localIndex] = colorScalars[indColors]; |
| 756 | + } |
| 757 | + } |
| 758 | + |
| 759 | + } |
| 760 | + else { |
| 761 | + throw std::runtime_error("Coloring data set does not match position data set in a known way, aborting attempt to color plot"); |
| 762 | + } |
| 763 | + |
| 764 | + } |
| 765 | + catch (const std::exception& e) { |
| 766 | + qDebug() << "ScatterplotPlugin::loadColors: mapping failed -> " << e.what(); |
| 767 | + _settingsAction.getColoringAction().getColorByAction().setCurrentIndex(0); // reset to color by constant |
685 | 768 | return; |
686 | 769 | } |
| 770 | + catch (...) { |
| 771 | + qDebug() << "ScatterplotPlugin::loadColors: mapping failed for an unknown reason."; |
| 772 | + _settingsAction.getColoringAction().getColorByAction().setCurrentIndex(0); // reset to color by constant |
| 773 | + return; |
| 774 | + } |
| 775 | + |
| 776 | + std::swap(mappedColorScalars, colorScalars); |
687 | 777 | } |
688 | 778 |
|
689 | | - assert(scalars.size() == _numPoints); |
| 779 | + assert(colorScalars.size() == _numPoints); |
690 | 780 |
|
691 | | - // Assign scalars and scalar effect |
692 | | - _scatterPlotWidget->setScalars(scalars); |
| 781 | + // Assign colorScalars and scalar effect |
| 782 | + _scatterPlotWidget->setScalars(colorScalars); |
693 | 783 | _scatterPlotWidget->setScalarEffect(PointEffect::Color); |
694 | 784 |
|
695 | 785 | _settingsAction.getColoringAction().updateColorMapActionScalarRange(); |
|
0 commit comments