Data tip on the centroid of a patch face?

30 views (last 30 days)
Hallo.
Is it possible programmatically create a data tip on a patch face center? It looks like it always sticks to the patch vertices.
The color of this 2D patch represents some data which would be nice to explore using data tip with a custom update function. I know how to write such update function but the problem is that the data tip automatically sticks only to the patch vertices...
Here is the patch example:
Thanks!
----- Update ----
Here is an example:
clc
close all
% Define some data
[xo,yo] = ndgrid(1:4,1:5); % data point positions
data = 100*rand(numel(xo),1); % data point values
% Form patch faces, each for each point of the data
width = 0.8;
x = nan(4,numel(xo));
y = nan(4,numel(xo));
x(1,:) = xo(:) - width/2; y(1,:) = yo(:) - width/2;
x(2,:) = xo(:) - width/2; y(2,:) = yo(:) + width/2;
x(3,:) = xo(:) + width/2; y(3,:) = yo(:) + width/2;
x(4,:) = xo(:) + width/2; y(4,:) = yo(:) - width/2;
% Plot
hFig = figure('color','w');
hPatch = patch('XData',x, 'YData',y, 'ZData',0*x, 'CData',data, 'FaceColor','flat');
colormap jet; colorbar;
% Set custom update function
dcm = datacursormode(hFig);
set(dcm,'UpdateFcn',{@myupdatefcn,data});
% Add tatatip. Here I would like to display the data tip at exact location (2,3) which is middle of the patch face, but
% it is snapped to the face vertex despite the option 'SnapToDataVertex' is 'off'...
% Moreover, it seems the datatip function do not respect the new update function (but this is not important for me now).
% See the figure below.
dt = datatip(hPatch, 2,3, 'SnapToDataVertex','off');
function txt = myupdatefcn(~,evt,data)
pos = get(evt,'Position');
ind = ceil( get(evt, 'DataIndex') / 4 );
txt = { sprintf('(x,y): (%g, %g)', pos(1:2)),...
sprintf('data index: %i', ind),...
sprintf('data value: %g', data(ind))
};
end
Here is result:
First datatip was created by the function datatip which is not at the position (2, 3) despite the option 'SnapToDataVertex' is 'off' (and which do not use the new update function). Second data tip was created by clicking the patch.
How to switch off the snapping?
P.S. MATLAB version R2020b.
  7 Comments
Adam Danz
Adam Danz on 30 Mar 2021
If the grid edges are contiguous then imagesc or heatmap is the way to go, and imagesc would likely be the better choice since it's more customizable. If the 2D bin edges are not contiguous then histogram2 might be a better approach, although I haven't entirely through it through.
Oleg Iupikov
Oleg Iupikov on 30 Mar 2021
Edited: Oleg Iupikov on 30 Mar 2021
I'm writing a general function, which can be used in many different occasions, including 2D (like in this example) and 3D (like surf) plots, including some interactivity elements. I like patch for its high customizability. Data can represent some physical objects (e.g. in my use case they can be antenna elements in an array), which are not necessary rectangular and equispaced. They may be located in 3D space, and not in plane. And so far I have plotted data with tens of thouthads faces without facing performance issues.
Yes, imagesc is nice and useful funtion, but only for a particular use case...
patch is very useful in many visualization cases, and it would be really nice if it can support data tips with SnapToDataVertex='off', like lines do.
But thanks for your tips anyway! Very much appreciated! Perhaps I will go for small dots for now.

Sign in to comment.

Accepted Answer

Adam Danz
Adam Danz on 30 Mar 2021
Edited: Adam Danz on 11 Jan 2024
Datatips are anchored to the vertices of patches and other graphics objects so the only way I know of to make datatip positions more flexible is to create a temporarily point that hosts the datatip.
That's what this demo does.
Basic methods (see inline comments for details)
  1. Assigns a ButtonDownFcn to the patch objects that responds to mouse clicks on the patches.
  2. Stores the data variable in the patch object UserData so we don't have to pass it to the function.
  3. Turns off datacursormode because that will block the hit detection on patches (at least it did for me).
The ButtonDownFcn does the following
  1. Finds the nearst patch vertices to the mouse click
  2. Finds the patch associated with those vertices
  3. Stores original axis hold-state, places axis on hold (returns hold state at the end).
  4. Searches for pre-existing data tips and data tip markers (see next step) created by this function and deletes them.
  5. Adds a small, barely visible temporary marker at the location of the mouse click. This marker will host the datatip.
  6. Adds datatip to the temporary marker.
  7. Updates the datatip to display the index and data info.
% Define some data
[xo,yo] = ndgrid(1:4,1:5); % data point positions
data = 100*rand(numel(xo),1); % data point values
% Form patch faces, each for each point of the data
width = 0.8;
x = nan(4,numel(xo));
y = nan(4,numel(xo));
x(1,:) = xo(:) - width/2; y(1,:) = yo(:) - width/2;
x(2,:) = xo(:) - width/2; y(2,:) = yo(:) + width/2;
x(3,:) = xo(:) + width/2; y(3,:) = yo(:) + width/2;
x(4,:) = xo(:) + width/2; y(4,:) = yo(:) - width/2;
% Plot patch objects
hFig = figure('color','w');
hPatch = patch('XData',x, 'YData',y, 'ZData',0*x, 'CData',data, 'FaceColor','flat');
colormap jet; colorbar;
% Store data in patch-userdata
hPatch.UserData.data = data;
% Set buttondownfcn on patch objects.
datacursormode(hFig,'off') %data cursor mode will prevent mouse clicks!
hPatch.ButtonDownFcn = @patchButtonDownFcn;
function patchButtonDownFcn(patchObj, hit)
% Responds to mouse clicks on patch objs.
% Add point at click location and adds a datacursor representing
% the underlying patch.
% datatip() requires Matlab r2019b or later
% Find closet vertices to mouse click
hitPoint = hit.IntersectionPoint;
[~, minDistIdx] = min(pdist2(patchObj.Vertices,hitPoint));
% Find patch associated with nearest vertices
[patchIndex, ~] = find(patchObj.Faces == minDistIdx);
% store original hold state and return at the end
ax = ancestor(patchObj,'axes');
holdStates = ["off","on"];
holdstate = ishold(ax);
cleanup = onCleanup(@()hold(holdStates(holdstate+1)));
hold(ax,'on')
% Search for and destroy previously existing datatips
% produced by this callback fuction.
preexisting = findobj(ax,'Tag','TempDataTipMarker');
delete(preexisting)
% detect 2D|3D axes
nAxes = numel(axis(ax))/2;
% Plot temp point at click location and add basic datatip
if nAxes==2 % 2D axes
hh=plot(ax,hitPoint(1),hitPoint(2),'k.','Tag','TempDataTipMarker');
dt = datatip(hh, hitPoint(1), hitPoint(2),'Tag','TempDataTipMarker');
else %3D axes
hh=plot3(ax,hitPoint(1),hitPoint(2),hitPoint(3),'k.','Tag','TempDataTipMarker');
dt = datatip(hh, hitPoint(1), hitPoint(2), hitPoint(3),'Tag','TempDataTipMarker');
end
dt.DeleteFcn = @(~,~)delete(hh);
clear cleanup % return hold state
% Update datatip
pos = hit.IntersectionPoint(1:2);
dtr = dataTipTextRow('Patch index:', patchIndex, '%i');
dtr(2) = dataTipTextRow('Data value:', patchObj.UserData.data(patchIndex), '%g');
hh.DataTipTemplate.DataTipRows(end+1:end+2) = dtr;
end
  5 Comments
Adam Danz
Adam Danz on 11 Jan 2024
Thanks @Hugh Stone, fixed it. I appreciate you letting me know.
Hugh Stone
Hugh Stone on 11 Jan 2024
No problems - thanks for the initial code snippet! I also noticed that when using it with a complicated 3D patch model (results from a large [>200000 patches] FEM) the selection of patches via vertices was not reliable. Instead I calculted the centroid coordinates of the patches, stored them in the patch UserData and used the centroids to select the patches. Code is:
patchCent = patchObj.UserData.data(:,1:3);
[~, patchIndex] = min(my_pdist2(patchCent,hitPoint));
I had to create my own pdist2 function (easy) because I don't have the statistics toolbox. :). Plot below shows part of a solid model with surface "indicator elements". I used the datatips and shading to work out "hot-spots" and critical cases etc.,.

Sign in to comment.

More Answers (0)

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!