Add data tips to non-supported chart

I'm creating a 2D graph, element by element, using rectangles. This is my current code:
clc
fig=figure('Position', [600 300 1000 1000])
fig =
Figure (1) with properties: Number: 1 Name: '' Color: [1 1 1] Position: [600 300 1000 1000] Units: 'pixels' Use GET to show all properties
ax = axes
ax =
Axes with properties: XLim: [0 1] YLim: [0 1] XScale: 'linear' YScale: 'linear' GridLineStyle: '-' Position: [0.1300 0.1100 0.7750 0.8150] Units: 'normalized' Use GET to show all properties
axis([-128 128 -128 128])
xticks([-128 -112 -96 -80 -64 -48 -32 -16 0 16 32 48 64 80 96 112 128]);
yticks([-128 -112 -96 -80 -64 -48 -32 -16 0 16 32 48 64 80 96 112 128]);
grid on
grid minor
OffsetX=0;
OffsetY=0;
Axe_A = transpose(linspace(0, 96, 24))
Axe_A = 24×1
0 4.1739 8.3478 12.5217 16.6957 20.8696 25.0435 29.2174 33.3913 37.5652
<mw-icon class=""></mw-icon>
<mw-icon class=""></mw-icon>
Axe_L = transpose(linspace(50, 50, 24))
Axe_L = 24×1
50 50 50 50 50 50 50 50 50 50
<mw-icon class=""></mw-icon>
<mw-icon class=""></mw-icon>
Axe_B = transpose(linspace(0, 0, 24))
Axe_B = 24×1
0 0 0 0 0 0 0 0 0 0
<mw-icon class=""></mw-icon>
<mw-icon class=""></mw-icon>
InGamut = transpose(linspace(0, 0, 24))
InGamut = 24×1
0 0 0 0 0 0 0 0 0 0
<mw-icon class=""></mw-icon>
<mw-icon class=""></mw-icon>
Lab = [Axe_L, Axe_A, Axe_B];
RGB = lab2rgb(Lab)
RGB = 24×3
0.4663 0.4663 0.4663 0.4951 0.4573 0.4668 0.5226 0.4477 0.4674 0.5492 0.4376 0.4679 0.5749 0.4269 0.4685 0.5999 0.4156 0.4690 0.6242 0.4035 0.4696 0.6481 0.3906 0.4702 0.6714 0.3767 0.4708 0.6943 0.3619 0.4715
<mw-icon class=""></mw-icon>
<mw-icon class=""></mw-icon>
%Add 4th column to flag out of gamut colors
RGBx = [RGB InGamut];
for ROW = 1:row
if RGB(ROW,1) < 0
RGBx(ROW,1) = 0;
RGBx(ROW,4) = 1;
end
if RGB(ROW,1) > 1
RGBx(ROW,1) = 1;
RGBx(ROW,4) = 1;
end
if RGB(ROW,2) < 0
RGBx(ROW,2) = 0;
RGBx(ROW,4) = 1;
end
if RGB(ROW,2) > 1
RGBx(ROW,2) = 1;
RGBx(ROW,4) = 1;
end
if RGB(ROW,3) < 0
RGBx(ROW,3) = 0;
RGBx(ROW,4) = 1;
end
if RGB(ROW,3) > 1
RGBx(ROW,3) = 1;
RGBx(ROW,4) = 1;
end
end
Unrecognized function or variable 'row'.
T = RGBx
[row,col] = size(RGB); % 16 x 3 double
for ROW = 1:row
if RGBx(ROW,4) == 0
rectangle(ax,'Position',[OffsetX OffsetY 4 4],'FaceColor',[RGBx(ROW,1) RGBx(ROW,2) RGBx(ROW,3)], 'LineStyle', 'none');
else
rectangle(ax,'Position',[OffsetX OffsetY 4 4],'FaceColor',[RGBx(ROW,1) RGBx(ROW,2) RGBx(ROW,3)]');
end
OffsetX=OffsetX+4
end
This is what the result looks like, so far :
Trouble is, there does not seem to be a way to add data tips to this graphics?
I wish I could display the Lab values when the mouse hover the graphic.

Réponses (1)

Adam Danz
Adam Danz le 18 Jan 2022
Modifié(e) : Adam Danz le 21 Jan 2022
Demo 1: Click rectangle to show datatip using ButtonDownFcn
This demo is based on this answer using patches. A ButtonDownFcn is assigned to each rectangle. When clicked, a datatip appears. A 1x3 vector of RGB color values is added to the datatip.
% Define rectangle data
[xo,yo] = meshgrid(1:4,1:5); % rectangle (x,y) positions
rng('default') % for reproducibility of this demo
colors = rand(numel(xo),3) % mx3 color matrix for m rectangles (aka "Lab" in your code)
labels = compose('[%.2f %.2f %.2f]',colors) % Rectangle labels
% Draw rectangles
hFig = figure();
ax = axes(hFig);
hold(ax, 'on')
for i = 1:numel(xo)
rectangle(ax, 'Position', [xo(i), yo(i), .6, .6], ...
'FaceColor', colors(i,:), ...
'LineStyle','none', ...
'UserData', labels(i), ... % Assign label to each rectangle
'ButtonDownFcn', @rectButtonDownFcn); % Assign button-down-function
end
function rectButtonDownFcn(rectObj, hit)
% Responds to mouse clicks on rectangle objs.
% Add point at click location and adds a datatip representing
% the underlying patch.
% datatip() requires Matlab r2019b or later
% store mouse click coordinate
hitPoint = hit.IntersectionPoint;
% store original hold state and return at the end
ax = ancestor(rectObj,'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 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=plot(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('Color:', repelem(rectObj.UserData,1,4));
hh.DataTipTemplate.DataTipRows(end+1) = dtr;
end
Demo 2: Move cursor over rectangles to show datatip using WindowButtonMotionFcn
A WindowButtonMotionFcn is assigned to the figure. When the mouse is within a rectangle, a datatip appears and follows the mouse until it leaves the rectangle. A 1x3 vector of RGB color values is added to the datatip. For simplicity, this demo assumes rectangles do not overlap and that their edges are parallel to the x and y axes. If any rectangle is deleted, the WindowButtonMotionFcn will be removed.
% Define rectangle data
[xo,yo] = meshgrid(1:4,1:5); % rectangle (x,y) positions
rng('default') % for reproducibility of this demo
colors = rand(numel(xo),3) % mx3 color matrix for m rectangles (aka "Lab" in your code)
labels = compose('[%.2f %.2f %.2f]',colors) % Rectangle labels
% Draw rectangles
hFig = figure();
ax = axes(hFig);
hold(ax, 'on') % required by WindowButtonMotionFcn
rectHandles = gobjects(size(xo));
for i = 1:numel(xo)
rectHandles(i) = rectangle(ax, ...
'Position', [xo(i), yo(i), .6, .6], ...
'FaceColor', colors(i,:), ...
'LineStyle','none', ...
'UserData', labels(i), ... % Assign label to each rectangle
'DeleteFc', @(h,~)set(ancestor(h,'figure'), 'WindowButtonMotionFcn',''));
% Remove WindowButtonMotionFcn if any rectangle is deleted
end
% Assign WindowButtonMotionFcn to figure
hFig.WindowButtonMotionFcn = {@rectButtonMoFcn, rectHandles(:), ax};
function rectButtonMoFcn(~, ~, rectObj, axHandle)
% Responds to mouse movement in figure (fig).
% Displays datatip if within rectangle (rectObj).
% This fcn should be as efficient and simple as possible.
% datatip() requires Matlab r2019b or later
persistent floatingMarkerHandle
% Create floating marker if it doesn't exist
if isempty(floatingMarkerHandle) || ~isvalid(floatingMarkerHandle)
floatingMarkerHandle = plot(axHandle,rectObj(1).Position(1),rectObj(1).Position(2),'k.','Visible','off');
datatip(floatingMarkerHandle, floatingMarkerHandle.XData, floatingMarkerHandle.YData);
dtr = dataTipTextRow('Color:', repelem({'[0 0 0]'},1,4));
floatingMarkerHandle.DataTipTemplate.DataTipRows(end+1) = dtr;
end
% get rectangle values
rectPos = cell2mat(get(rectObj,'position'));
rectX = [rectPos(:,1), rectPos(:,1)+rectPos(:,3)];
rectY = [rectPos(:,2), rectPos(:,2)+rectPos(:,4)];
% Determine which rectangle the mouse is in, if any.
% Assumes rectangle edges are vertical or horizontal (parallel to x,y axes)
cp = axHandle.CurrentPoint(1,1:2); % Current mouse point, data units, 2D axes.
rectIdx = cp(1) >= rectX(:,1) & ...
cp(1) <=rectX(:,2) & ...
cp(2) >= rectY(:,1) & ...
cp(2) <=rectY(:,2);
% Update floating marker and data tip if inside a rectangle; otherwise turnoff
if any(rectIdx)
set(floatingMarkerHandle,'XData',cp(1),'YData',cp(2),'Visible','on');
rectUserData = rectObj(rectIdx).UserData; % Assumes only 1 rectangle is selected (ie, no overlap)
floatingMarkerHandle.DataTipTemplate.DataTipRows(end).Value = rectUserData;
else
set(floatingMarkerHandle,'Visible','off');
end
end

10 commentaires

Roger Breton
Roger Breton le 19 Jan 2022
Wow! Thanks Adam! That's a lot of interesting code for me to study!
Let me see how I can adapt it to my project....
Roger Breton
Roger Breton le 19 Jan 2022
Your code is interesting. For one thing, I wouldn't have thought that a ButtonDownFn could be attached to rectangle objects! This, indeed, opens up invaluable opportunities!!!!!!!!!!
Roger Breton
Roger Breton le 19 Jan 2022
Your idea made me think about adding a WindowButtonMotionFcn to the Figure. Lo and behold, this is the code I have so far which retrieves the precise a* and b* coordinates from the mouse :
set (fig, 'WindowButtonMotionFcn', @mouseMove);
function mouseMove (object, eventdata)
global ax;
% CurrentPoint is relative to ax.InnerPosition
% CurrentPoint Origin (0,0) is top, left of image area
CP = round(get (ax, 'CurrentPoint'));
x = CP(1,1);
y = CP(1,2);
fprintf('X,Y Coordinates of the mouse = %i , %i \n', x, y);
This is great! Now, if only I could rig these x,y coordinates in some kind of data tip...
I'll see if I can dig up the DataTip Template code used on my trisurf project...
Otherwise, I'll settle for outputting the value in a text control.
Roger Breton
Roger Breton le 19 Jan 2022
I'm toying with the idea of overlaying a 2D plot, make it invisible, just to have the convenience of one of code to activate the datatip? I don't need to see the points, something like this, perhaps :
My code would have to be modified this way :
CIE_a = [-128 -112 -96 -80 -64 -48 -32 -16 0 16 32 48 64 80 96 112 128];
CIE_b = [-128 -112 -96 -80 -64 -48 -32 -16 0 16 32 48 64 80 96 112 128];
sc = scatter(CIE_a,CIE_b);
% grid on;
dt = datatip(sc,4,0);
I probably have to come up with finer scales but I could probably come up a "grid" of CIE_a* and CIE_b* that would cover the whole CIE ab diagram? Right now, I'm using 4x4 'tiles' (rectangles). If I could come up with a grid of coordinates that are centered on each 4 x 4 tile, and hide the points, while still enjoying the benefit of flip a data tip with one line of code, it's worth attempting... It won't be elegant or efficient but if it can do the trick... Let me try and report...
Roger Breton
Roger Breton le 19 Jan 2022
Now experimenting with altering the scatter 'transparency' :
sc = scatter(CIE_a,CIE_b);
alpha(.1)
I can get the value all the way down to 0. Will see. For now, I have to figure the CIE_a, CIE_b arrays...
Adam Danz
Adam Danz le 20 Jan 2022
I'm glad this inspired you, @Roger Breton, thanks for the feedback!
Some feedback on your code:
  1. Don't use a global variable to share the axis handle. Suppose you create the figure and then open another script that contains the same global variable name which, by the way, is a commonly used variable name ("ax"). Now your axis handle is overwritten. Or, suppose your function is run twice with different axes. Instead, get the axis handle within the callback function using ancestor(object,'axes') or, more efficiently, pass the axis handle in to the callback function using set(...,{@mouseMove,ax}); function mouseMove(object,eventdata,ax)
  2. Instead of re-creating a datatip, you just just add a fixed text box to your figure margin that displays the values.
Roger Breton
Roger Breton le 20 Jan 2022
I take your suggestions very well, Adam! I experimented at some point with passing the axis handle in the callback function parameters but it did not solve my problem then? But I'll 'revisit' it -- promised.
I was thinking about using a fixed text box in the figure margin that displays the value but it's not as "live" or "interactive" as having a datatip exactly where the mouse points, which is the 'point' I want to nail with students...
Adam Danz
Adam Danz le 21 Jan 2022
Modifié(e) : Adam Danz le 31 Jan 2022
I've added a 2nd demo to my answer showing how to apply the same method as my first demo to a WindowButtonMotionFcn so that datatips show automatically when hovering over rectangels.
I believe this is what you were describing in this comment.
Muhammad Anam
Muhammad Anam le 1 Déc 2024
@Adam Danz: Why there is a need to use command 'plot'. Why data cannot directly be pinned to patch?
Adam Danz
Adam Danz le 2 Déc 2024
@Muhammad Anam, good question. Rather than limiting data tips to the 4 vertices of a rectanlge, my solution allows you to click anywhere in the rectangle. But the datatip still has to be associated with an existing plotted cooredinate. So I'm using plot() to add a temporary point at the click location where the datatip should appear.

Connectez-vous pour commenter.

Catégories

En savoir plus sur Graphics Performance dans Centre d'aide et File Exchange

Produits

Version

R2021b

Community Treasure Hunt

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

Start Hunting!

Translated by