Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
635 views
in Technique[技术] by (71.8m points)

plot - MATLAB scatter with tooltip showing other kinds of data and connecting points with arrowhead

I want to ask two questions here. In short they are,

  1. In a scatter plot in MATLAB how can I click a point using the tooltip and get not the x,y data but some other sort of data associated with the x,y point? Right now I have a workaround using gscatter and a file from file-exchange (see below). But it will get messy for large data sets.

  2. How do I connect two points with an arrowhead on the line between them? For example in the rlocus plots MATLAB makes, there is a nifty little arrowhead. Is there a native way to do this in MATLAB for arbitrary plots?

Consider the data set in MATLAB

clearvars
LionNames={'Tyrion','Jamie','Cersei'};
Data = rand(3,2,2);
LionsDay1=struct('Names',{},'Data',[]);
LionsDay2=struct('Names',{},'Data',[]);

for i =1:numel(LionNames)
    LionsDay1(i).Names=LionNames{i};
    LionsDay1(i).Data=Data(i,:,1);
    LionsDay2(i).Names=LionNames{i};
    LionsDay2(i).Data=Data(i,:,2);
end

WolfNames = {'Robert','Arya','Sansa','Jon'};
Data = rand(4,2,2);
WolvesDay1=struct('Names',{},'Data',[]);
WolvesDay2=struct('Names',{},'Data',[]);

for i =1:numel(WolfNames)
    WolvesDay1(i).Names=WolfNames{i};
    WolvesDay1(i).Data=Data(i,:,1);
    WolvesDay2(i).Names=WolfNames{i};
    WolvesDay2(i).Data=Data(i,:,2);
end

Here the data for each group is x and y data. For the purposes of this question or example, the data structure above is not all that important, but I have made it so the reader gets a feel for the big picture.


So using a file from MATLAB file exchange I am able to scatter plot and name each point as well as it's class. For example,

lionsData=reshape([LionsDay1(:).Data],2,3);
wolvesData=reshape([WolvesDay1(:).Data],2,4);
xData=[lionsData(1,:) wolvesData(1,:)];
yData=[lionsData(2,:) wolvesData(2,:)];
group=repmat({'Lions'},[1,3]);
group= [group repmat({'Wolves'},[1,4])];
gscatter(xData',yData',group');

Names=[LionNames WolfNames];
labelpoints(xData,yData,Names)

Creates,

Example

But as you can imagine, this would get messy for large data sets (>50 data points); or if the points were very close together, hence the first question. Clicking on a point to reveal the name would be much better.


For the second question, doing,

day1Lions=reshape([LionsDay1(:).Data],2,3);
day2Lions=reshape([LionsDay2(:).Data],2,3);

for k = 1 : size(day1Lions, 2)
    plot([day1Lions(1,k), day2Lions(1,k)], [day1Lions(2,k), day2Lions(2,k)],'s-');
    hold on
end
legend('Tyrion','Jamie','Cersei')

gives,

enter image description here

So in a sense we can see how much the two points have changed between day 1 and day 2 but now I don't know which is day 1 and day 2. It would be nice to put an arrow going from the day 1 data point to day 2 data point. Of course if the hover/tooltip question above has a flexible enough answer, that might fix this problem too.

Of course in the end, we would also have lions and wolves mixed in with day 1 and day 2, but having these two simple questions answered would likely answer issues when doing the combined plot as well.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Answer 1

One solution is you define your own callback function for the data-tooltip. To do that, you first need to save Names within the figure. We can use UserData property for that:

% modify the end of your code to:
gsh = gscatter(xData',yData',group');
Names = [LionNames WolfNames];
set(gsh,{'UserData'},{Names});

Next, we create the following callback function (I took Matlab's defualt and edit it), and save it in new m-file:

function output_txt = tooltip_callback(obj,event_obj)
% Display the position of the data cursor
% obj          Currently not used (empty)
% event_obj    Handle to event object
% output_txt   Data cursor text string (string or cell array of strings).

pos = get(event_obj,'Position');
output_txt = {['X: ',num2str(pos(1),4)],...
    ['Y: ',num2str(pos(2),4)],...
    event_obj.Target.UserData{event_obj.Target.XData==pos(1)}}; % <- this line is the only change
% If there is a Z-coordinate in the position, display it as well
if length(pos) > 2
    output_txt{end+1} = ['Z: ',num2str(pos(3),4)];
end

Now we click on one of the tooltips in the figure and choose Select Text Update Function:

tooltip menu

and from the browser we pick the callback function we saved.

The result:

Names

In the same manner, you can add the days to the tooltip if you want, or use my answer to Q2...

Answer 2

Here is how you can use annotations to do that:

ax = axes;  % create the axis
% plot all lines (no need for the loop) so we can put the legend after:
p = plot(day1Lions,day2Lions,'-');
legend('Tyrion','Jamie','Cersei')
% get the lines colors:
col = cell2mat(get(p,'Color'));
% loop through the arrows:
for k = 1:size(day1Lions, 2)
    % get the data coordinates:
    x = day1Lions(:,k);
    y = day2Lions(:,k);
    pos = ax.Position;
    % convert them to normalized coordinates:    
    %  white area * ((value - axis min)  / axis length)  + gray area
    normx = pos(3)*((x-ax.XLim(1))./range(ax.XLim))+ pos(1);
    normy = pos(4)*((y-ax.YLim(1))./range(ax.YLim))+ pos(2);
    % plot the arrow
    annotation('arrow',normx,normy,'Color',col(k,:))
end

the result:

arrows

you could also set the original lines invisable, with:

set(p,{'Visible'},{'off'})

but it will turn the legend text gray, and they are totally covered by the arrows anyway.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...