Skip to content

Commit 1fc2164

Browse files
authored
Feature/generic tooltip action (#138)
* Initialize the view plugin tooltip action with placeholder content * Basic draft version working * Support focus region in view plugin * Adhere to changed core API * Adhere to changed core pixel sampling API * Clear focus when sample selection ends * Add position dataset ID, color dataset ID and distances to tooltip context * Allow restriction of the number of samples points * Allow restriction of the number of samples points (2) * Sort points based on distance from cursor * Small refactor * Correct small bug in distance calculation * Fix flickering bug * Fix default tooltip * Fix tooltip string formatting issue * Small fix * Add extra attributes in tooltip context * Final fix of flickering * Adhere to new core lazy update scheme for view plugin sampler * Add render mode to sample context * Do not show sample selection visuals when the mouse is not hovering
1 parent 2254b63 commit 1fc2164

File tree

6 files changed

+258
-100
lines changed

6 files changed

+258
-100
lines changed

src/ScatterplotPlugin.cpp

Lines changed: 151 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,7 @@ ScatterplotPlugin::ScatterplotPlugin(const PluginFactory* factory) :
4545
_numPoints(0),
4646
_settingsAction(this, "Settings"),
4747
_primaryToolbarAction(this, "Primary Toolbar"),
48-
_secondaryToolbarAction(this, "Secondary Toolbar"),
49-
_selectPointsTimer()
48+
_secondaryToolbarAction(this, "Secondary Toolbar")
5049
{
5150
setObjectName("Scatterplot");
5251

@@ -214,18 +213,28 @@ ScatterplotPlugin::ScatterplotPlugin(const PluginFactory* factory) :
214213
return dropRegions;
215214
});
216215

217-
/*
218-
_selectPointsTimer.setSingleShot(true);
216+
auto& selectionAction = _settingsAction.getSelectionAction();
219217

220-
connect(&_selectPointsTimer, &QTimer::timeout, this, [this]() -> void {
221-
if (_selectPointsTimer.isActive())
222-
_selectPointsTimer.start(LAZY_UPDATE_INTERVAL);
223-
else {
224-
_selectPointsTimer.stop();
225-
selectPoints();
226-
}
218+
getSamplerAction().initialize(this, &selectionAction.getPixelSelectionAction(), &selectionAction.getSamplerPixelSelectionAction());
219+
getSamplerAction().setTooltipGeneratorFunction([this](const ViewPluginSamplerAction::SampleContext& toolTipContext) -> QString {
220+
QStringList localPointIndices, globalPointIndices;
221+
222+
for (const auto& localPointIndex : toolTipContext["LocalPointIndices"].toList())
223+
localPointIndices << QString::number(localPointIndex.toInt());
224+
225+
for (const auto& globalPointIndex : toolTipContext["GlobalPointIndices"].toList())
226+
globalPointIndices << QString::number(globalPointIndex.toInt());
227+
228+
if (localPointIndices.isEmpty())
229+
return {};
230+
231+
return QString("<table> \
232+
<tr> \
233+
<td><b>Point ID's: </b></td> \
234+
<td>%1</td> \
235+
</tr> \
236+
</table>").arg(globalPointIndices.join(", "));
227237
});
228-
*/
229238
}
230239

231240
ScatterplotPlugin::~ScatterplotPlugin()
@@ -247,26 +256,26 @@ void ScatterplotPlugin::init()
247256
// Update the data when the scatter plot widget is initialized
248257
connect(_scatterPlotWidget, &ScatterplotWidget::initialized, this, &ScatterplotPlugin::updateData);
249258

250-
// Update the selection when the pixel selection tool selected area changed
251259
connect(&_scatterPlotWidget->getPixelSelectionTool(), &PixelSelectionTool::areaChanged, [this]() {
252260
if (_scatterPlotWidget->getPixelSelectionTool().isNotifyDuringSelection()) {
253-
//_selectPointsTimer.start(LAZY_UPDATE_INTERVAL);
254261
selectPoints();
255262
}
256263
});
257264

258-
// Update the selection when the pixel selection process ended
259265
connect(&_scatterPlotWidget->getPixelSelectionTool(), &PixelSelectionTool::ended, [this]() {
260266
if (_scatterPlotWidget->getPixelSelectionTool().isNotifyDuringSelection())
261267
return;
262268

263-
//_selectPointsTimer.start(LAZY_UPDATE_INTERVAL);
264269
selectPoints();
265270
});
266271

272+
connect(&getSamplerAction(), &ViewPluginSamplerAction::sampleContextRequested, this, &ScatterplotPlugin::samplePoints);
273+
267274
connect(&_positionDataset, &Dataset<Points>::changed, this, &ScatterplotPlugin::positionDatasetChanged);
268275
connect(&_positionDataset, &Dataset<Points>::dataChanged, this, &ScatterplotPlugin::updateData);
269276
connect(&_positionDataset, &Dataset<Points>::dataSelectionChanged, this, &ScatterplotPlugin::updateSelection);
277+
278+
_scatterPlotWidget->installEventFilter(this);
270279
}
271280

272281
void ScatterplotPlugin::loadData(const Datasets& datasets)
@@ -299,28 +308,21 @@ void ScatterplotPlugin::createSubset(const bool& fromSourceData /*= false*/, con
299308

300309
void ScatterplotPlugin::selectPoints()
301310
{
311+
auto& pixelSelectionTool = _scatterPlotWidget->getPixelSelectionTool();
312+
302313
// Only proceed with a valid points position dataset and when the pixel selection tool is active
303-
if (!_positionDataset.isValid() || !_scatterPlotWidget->getPixelSelectionTool().isActive() || _scatterPlotWidget->isNavigating())
314+
if (!_positionDataset.isValid() || !pixelSelectionTool.isActive() || _scatterPlotWidget->isNavigating())
304315
return;
305316

306-
//qDebug() << _positionDataset->getGuiName() << "selectPoints";
307-
308-
// Get binary selection area image from the pixel selection tool
309-
auto selectionAreaImage = _scatterPlotWidget->getPixelSelectionTool().getAreaPixmap().toImage();
310-
311-
// Get smart pointer to the position selection dataset
312-
auto selectionSet = _positionDataset->getSelection<Points>();
317+
auto selectionAreaImage = pixelSelectionTool.getAreaPixmap().toImage();
318+
auto selectionSet = _positionDataset->getSelection<Points>();
313319

314-
// Create vector for target selection indices
315320
std::vector<std::uint32_t> targetSelectionIndices;
316321

317-
// Reserve space for the indices
318322
targetSelectionIndices.reserve(_positionDataset->getNumPoints());
319323

320-
// Mapping from local to global indices
321324
std::vector<std::uint32_t> localGlobalIndices;
322325

323-
// Get global indices from the position dataset
324326
_positionDataset->getGlobalIndices(localGlobalIndices);
325327

326328
auto& zoomRectangleAction = _scatterPlotWidget->getNavigationAction().getZoomRectangleAction();
@@ -333,24 +335,18 @@ void ScatterplotPlugin::selectPoints()
333335
QPointF uvNormalized = {};
334336
QPoint uv = {};
335337

336-
// Loop over all points and establish whether they are selected or not
337-
for (std::uint32_t i = 0; i < _positions.size(); i++) {
338-
uvNormalized = QPointF((_positions[i].x - zoomRectangleAction.getLeft()) / zoomRectangleAction.getWidth(), (zoomRectangleAction.getTop() - _positions[i].y) / zoomRectangleAction.getHeight());
338+
for (std::uint32_t localPointIndex = 0; localPointIndex < _positions.size(); localPointIndex++) {
339+
uvNormalized = QPointF((_positions[localPointIndex].x - zoomRectangleAction.getLeft()) / zoomRectangleAction.getWidth(), (zoomRectangleAction.getTop() - _positions[localPointIndex].y) / zoomRectangleAction.getHeight());
339340
uv = uvOffset + QPoint(uvNormalized.x() * size, uvNormalized.y() * size);
340341

341-
if (uv.x() >= selectionAreaImage.width() || uv.x() < 0 ||
342-
uv.y() >= selectionAreaImage.height() || uv.y() < 0)
342+
if (uv.x() >= selectionAreaImage.width() || uv.x() < 0 || uv.y() >= selectionAreaImage.height() || uv.y() < 0)
343343
continue;
344344

345-
// Add point if the corresponding pixel selection is on
346345
if (selectionAreaImage.pixelColor(uv).alpha() > 0)
347-
targetSelectionIndices.push_back(localGlobalIndices[i]);
346+
targetSelectionIndices.push_back(localGlobalIndices[localPointIndex]);
348347
}
349348

350-
// Selection should be subtracted when the selection process was aborted by the user (e.g. by pressing the escape key)
351-
const auto selectionModifier = _scatterPlotWidget->getPixelSelectionTool().isAborted() ? PixelSelectionModifierType::Subtract : _scatterPlotWidget->getPixelSelectionTool().getModifier();
352-
353-
switch (selectionModifier)
349+
switch (const auto selectionModifier = pixelSelectionTool.isAborted() ? PixelSelectionModifierType::Subtract : pixelSelectionTool.getModifier())
354350
{
355351
case PixelSelectionModifierType::Replace:
356352
break;
@@ -367,30 +363,29 @@ void ScatterplotPlugin::selectPoints()
367363
switch (selectionModifier)
368364
{
369365
// Add points to the current selection
370-
case PixelSelectionModifierType::Add:
371-
{
372-
// Add indices to the set
373-
for (const auto& targetIndex : targetSelectionIndices)
374-
set.insert(targetIndex);
366+
case PixelSelectionModifierType::Add:
367+
{
368+
// Add indices to the set
369+
for (const auto& targetIndex : targetSelectionIndices)
370+
set.insert(targetIndex);
375371

376-
break;
377-
}
372+
break;
373+
}
378374

379-
// Remove points from the current selection
380-
case PixelSelectionModifierType::Subtract:
381-
{
382-
// Remove indices from the set
383-
for (const auto& targetIndex : targetSelectionIndices)
384-
set.remove(targetIndex);
375+
// Remove points from the current selection
376+
case PixelSelectionModifierType::Subtract:
377+
{
378+
// Remove indices from the set
379+
for (const auto& targetIndex : targetSelectionIndices)
380+
set.remove(targetIndex);
385381

386-
break;
387-
}
382+
break;
383+
}
388384

389-
default:
390-
break;
385+
case PixelSelectionModifierType::Replace:
386+
break;
391387
}
392388

393-
// Convert set back to vector
394389
targetSelectionIndices = std::vector<std::uint32_t>(set.begin(), set.end());
395390

396391
break;
@@ -405,6 +400,105 @@ void ScatterplotPlugin::selectPoints()
405400
events().notifyDatasetDataSelectionChanged(_positionDataset->getSourceDataset<Points>());
406401
}
407402

403+
void ScatterplotPlugin::samplePoints()
404+
{
405+
auto& samplerPixelSelectionTool = _scatterPlotWidget->getSamplerPixelSelectionTool();
406+
407+
if (!_positionDataset.isValid() || _scatterPlotWidget->isNavigating())
408+
return;
409+
410+
auto selectionAreaImage = samplerPixelSelectionTool.getAreaPixmap().toImage();
411+
412+
auto selectionSet = _positionDataset->getSelection<Points>();
413+
414+
std::vector<std::uint32_t> targetSelectionIndices;
415+
416+
targetSelectionIndices.reserve(_positionDataset->getNumPoints());
417+
418+
std::vector<std::uint32_t> localGlobalIndices;
419+
420+
_positionDataset->getGlobalIndices(localGlobalIndices);
421+
422+
auto& zoomRectangleAction = _scatterPlotWidget->getNavigationAction().getZoomRectangleAction();
423+
424+
const auto width = selectionAreaImage.width();
425+
const auto height = selectionAreaImage.height();
426+
const auto size = width < height ? width : height;
427+
const auto uvOffset = QPoint((selectionAreaImage.width() - size) / 2.0f, (selectionAreaImage.height() - size) / 2.0f);
428+
429+
QPointF pointUvNormalized;
430+
431+
QPoint pointUv, mouseUv = _scatterPlotWidget->mapFromGlobal(QCursor::pos());
432+
433+
std::vector<char> focusHighlights(_positions.size());
434+
435+
std::vector<std::pair<float, std::uint32_t>> sampledPoints;
436+
437+
for (std::uint32_t localPointIndex = 0; localPointIndex < _positions.size(); localPointIndex++) {
438+
pointUvNormalized = QPointF((_positions[localPointIndex].x - zoomRectangleAction.getLeft()) / zoomRectangleAction.getWidth(), (zoomRectangleAction.getTop() - _positions[localPointIndex].y) / zoomRectangleAction.getHeight());
439+
pointUv = uvOffset + QPoint(pointUvNormalized.x() * size, pointUvNormalized.y() * size);
440+
441+
if (pointUv.x() >= selectionAreaImage.width() || pointUv.x() < 0 || pointUv.y() >= selectionAreaImage.height() || pointUv.y() < 0)
442+
continue;
443+
444+
if (selectionAreaImage.pixelColor(pointUv).alpha() > 0) {
445+
const auto sample = std::pair<float, std::uint32_t>((QVector2D(mouseUv) - QVector2D(pointUv)).length(), localPointIndex);
446+
447+
sampledPoints.emplace_back(sample);
448+
}
449+
}
450+
451+
QVariantList localPointIndices, globalPointIndices, distances;
452+
453+
localPointIndices.reserve(static_cast<std::int32_t>(sampledPoints.size()));
454+
globalPointIndices.reserve(static_cast<std::int32_t>(sampledPoints.size()));
455+
distances.reserve(static_cast<std::int32_t>(sampledPoints.size()));
456+
457+
std::int32_t numberOfPoints = 0;
458+
459+
std::sort(sampledPoints.begin(), sampledPoints.end(), [](const auto& sampleA, const auto& sampleB) -> bool {
460+
return sampleB.first > sampleA.first;
461+
});
462+
463+
for (const auto& sampledPoint : sampledPoints) {
464+
if (getSamplerAction().getRestrictNumberOfElementsAction().isChecked() && numberOfPoints >= getSamplerAction().getMaximumNumberOfElementsAction().getValue())
465+
break;
466+
467+
const auto& distance = sampledPoint.first;
468+
const auto& localPointIndex = sampledPoint.second;
469+
const auto& globalPointIndex = localGlobalIndices[localPointIndex];
470+
471+
distances << distance;
472+
localPointIndices << localPointIndex;
473+
globalPointIndices << globalPointIndex;
474+
475+
focusHighlights[localPointIndex] = true;
476+
477+
numberOfPoints++;
478+
}
479+
480+
if (getSamplerAction().getHighlightFocusedElementsAction().isChecked())
481+
const_cast<PointRenderer&>(_scatterPlotWidget->getPointRenderer()).setFocusHighlights(focusHighlights, static_cast<std::int32_t>(focusHighlights.size()));
482+
483+
_scatterPlotWidget->update();
484+
485+
auto& coloringAction = _settingsAction.getColoringAction();
486+
487+
getSamplerAction().setSampleContext({
488+
{ "PositionDatasetID", _positionDataset.getDatasetId() },
489+
{ "ColorDatasetID", _settingsAction.getColoringAction().getCurrentColorDataset().getDatasetId() },
490+
{ "LocalPointIndices", localPointIndices },
491+
{ "GlobalPointIndices", globalPointIndices },
492+
{ "Distances", distances },
493+
{ "ColorBy", coloringAction.getColorByAction().getCurrentText() },
494+
{ "ConstantColor", coloringAction.getConstantColorAction().getColor() },
495+
{ "ColorMap1D", coloringAction.getColorMap1DAction().getColorMapImage() },
496+
{ "ColorMap2D", coloringAction.getColorMap2DAction().getColorMapImage() },
497+
{ "ColorDimensionIndex", coloringAction.getDimensionAction().getCurrentDimensionAction().getCurrentIndex() },
498+
{ "RenderMode", _settingsAction.getRenderModeAction().getCurrentText() }
499+
});
500+
}
501+
408502
Dataset<Points>& ScatterplotPlugin::getPositionDataset()
409503
{
410504
return _positionDataset;

src/ScatterplotPlugin.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ class ScatterplotPlugin : public ViewPlugin
8383
/** Use the pixel selection tool to select data points */
8484
void selectPoints();
8585

86+
/** Use the sampler pixel selection tool to sample data points */
87+
void samplePoints();
88+
8689
public:
8790

8891
/** Get reference to the scatter plot widget */
@@ -121,8 +124,6 @@ class ScatterplotPlugin : public ViewPlugin
121124
HorizontalToolbarAction _primaryToolbarAction; /** Horizontal toolbar for primary content */
122125
HorizontalToolbarAction _secondaryToolbarAction; /** Secondary toolbar for secondary content */
123126

124-
QTimer _selectPointsTimer; /** Timer to limit the refresh rate of selection updates */
125-
126127
static const std::int32_t LAZY_UPDATE_INTERVAL = 2;
127128

128129
};

0 commit comments

Comments
 (0)