Edit objects within a figure - matlab

I have figures (in Matlab's fig file format) each of which contains a line plot with two lines (representing EEG curves), axes, bunch of labels etc.
I want to:
change the color of the lines
remove some of the labels
I would loop over the fig files and do the same thing for each of them.
Is there a list of all the objects within the figure that I could index and edit? How can I get to those objects using commands (i.e. without the gui)?

Line, lable, etc. are children of axis, which is itself a child of a figure. What you need to do is acquire handles to the objects you want to change through this hierarchy.
% Get a handle to the figure
hfig = openfig('testfig');
% Get all children of the CurrentAxes. Most of what you want is here.
axes_obj = allchild(hfig.CurrentAxes);
% Edit Axes object according to its type
For ii = 1:length(axes_obj)
switch axes_obj(ii).Type
case 'Text'
% Do something, for example:
axes_obj(ii).String = 'changed';
case 'Line'
% Do something, for example:
axes_obj(ii).MarkerEdgeColor = 'b';
end
end
% Save figure
savefig(hfig, 'testfig')
You can see all the properties of the object you wish to edit by simply typing axes_obj(ii) in the command window.

Related

Changing the legend of a figure so that various lines share the same legend entry

A colleague has passed me a .fig file that has many lines on the same plots and they are coloured based on which group they belong to. The figure is shown below for reference.
I need to change the legend so that lines with the same colour have the same legend entry. The problem is that I don't have access to the raw data so I can't use the method mentioned here so is there a way to change the legend entries just using the .fig file? I tried changing some of the legend names to NaN in the property inspector but that just changes the entries to NaN.
If you have the *.fig file you can extract any included data with the 'get' method if you have understood the MATLAB Graphics Objects Hierarchy.
For example, see the left plot below as example of your *.fig file. you can extract the data in there by digging through the Children of your current figure object.
% Open your figure
fig = openfig('your_figure.fig');
% fig = gcf % If you have the figure already opened
title('loaded figure')
% Get all objects from figure (i.e. legend and axis handle)
Objs = get(fig, 'Children');
% axis handle is second entry of figure children
HA = Objs(2);
% get line objects from axis (is fetched in reverse order)
HL = flipud(get(HA, 'Children'));
% retrieve data from line objects
for i = 1:length(HL)
xData(i,:) = get(HL(i), 'XData');
yData(i,:) = get(HL(i), 'YData');
cData{i} = get(HL(i), 'Color');
end
xy data of all lines in the figure is now extracted to xData and yData. The color information is saved to cell cData. You can now replot the figure with a legend the way you want (e.g. using the SO solution you found already):
% Draw new figure with data extracted from old figure
figure()
title('figure with reworked legend')
hold on
for i = 1:length(HL)
h(i) = plot(xData(i,:), yData(i,:), 'Color', cData{i});
end
% Use method of the SO answer you found already to combine equally colored
% line objects to the same color
legend([h(1), h(3)], 'y1', 'y2+3')
Result is the plot below on the right, where each color is only listed once.

How to delete a single point on a Matlab figure? preferably by GUI

Consider the following plot:
My data points are too dense, and I would like to hide or delete some of them points to make the chart clearer. How can I do this without without recomputing the data and re-plotting the figure (which is time-consuming)?
The simplest way:
Click button "Brush/Select Data"
Select Points
Right Click mouse
Choose "Remove"
In this way, the line continuity is retained and no coding is needed.
When plotting unconnected points, you can replace either the XData or YData (or both) of the points you don't want by NaN. For charts with lines, this doesn't work (it creates discontinuities), and the appropriate points must be removed from the data vectors. I'll demonstrate how this is done in the example below:
For the purposes of demonstration, I shall assume that you're working with a .fig file, and have no handles to the plots/lines.
Let this be the code used to plot a figure:
x = [0:0.05:0.9, 1:0.01:1.09, 1.1:0.1:pi/2];
figure(); plot(x,sin(x),'-+',x,sin(x-0.02),'-^',x,sin(x-0.04),'-s'); grid minor;
Then, assuming you loaded the figure and it is the current figure (gcf), you can do:
function q45177572
%% Find handles:
hL = findobj(gcf,'Type','Line');
%% Choose which indices to remove, and remove...
for ind1 = 1:numel(hL)
% (Option 1) Keeping every other datapoint:
ind2rem = 1:2:numel(hL(ind1).XData);
% (Option 2) Removing points in a certain interval:
ind2rem = hL(ind1).XData > 1 & hL(ind1).XData < 1.05;
% (Option 3) Manual (watch out for indices out of bounds!)
ind2rem = [1 3 20:23 30];
% (Option 4) ...
% Finally, delete the points:
hL(ind1).XData(ind2rem) = [];
hL(ind1).YData(ind2rem) = [];
end
You can use whichever logic you want to compute ind2rem, or even specify it manually.
The above was tested on R2017a.
The most GUI way I can think of is selecting the point using the 'Data Cursor' tool to see what are its' values and then replace the XData and YData property of this point to NaN, at the 'More Properties' button.
Alternatively, you can alter the callback function of the 'Data Cursor' to do this for you (you can see here another example).
Check if the data points in your plot are a 'Scatter' graphic object, to do that type get(gca,'children') in the command window while the figure is chosen, and see if the first line of output is:
Scatter with properties:
if so, use option 1 below. If not, plot the data points and the lines as different objects, or use option 2 below.
After you create the figure, right click on one of the tooltips in the figure and choose Edit Text Update Function.
Option 1
In the editor that opens add the following lines at the end:
event_obj.Target.XData(event_obj.Target.XData==pos(1)) = nan;
event_obj.Target.YData(event_obj.Target.YData==pos(2)) = nan;
Give the function some meaningful name, and save it as a new .m file.
Here is a simple example of what you should get:
function output_txt = DeletePoint(obj,event_obj) % Give it some meaningful name
% 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)]};
% 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
% The lines you add:
event_obj.Target.XData(event_obj.Target.XData==pos(1)) = nan;
event_obj.Target.YData(event_obj.Target.YData==pos(2)) = nan;
Now, right click again on one of the tooltips in the figure and choose Select Text Update Function. In the browser that opens choose the function you just saved (here, it's DeletePoint.m).
Now, every click on a point using the 'Data Cursor' tool will delete it. However, keep in mind that the only way to restore the points is to newly create the figure, as the data is deleted from the figure (but not from the variables in the workspace that hold it).
Option 2
In the editor that opens add the following lines at the end:
% The lines you add:
props = {'Color',...
'LineStyle',...
'LineWidth',...
'Marker',...
'MarkerSize',...
'MarkerEdgeColor',...
'MarkerFaceColor'};
line_props = get(event_obj.Target,props);
newX = event_obj.Target.XData;
newY = event_obj.Target.YData;
newX(newX==pos(1)) = [];
newY(newY==pos(2)) = [];
new_line = line(newX,newY);
set(new_line,props,line_props);
delete(event_obj.Target)
warning('off') % after you finish removing points, type warning('on') in the command window
All the rest is as in option 1. The difference here is that the latter function draws all the line again with the same properties, but without the point you selected. Normally, each time you delete a point the Data cursor automatically moves to the next point (and this is why we can't simply delete the points, it will initiate a loop that deletes the whole line). Here we delete the whole line, so Matlab issues a warning on every deletion:
Warning: Error while executing listener callback for MarkedClean event. Source has been deleted
To avoid that I added warning('off') and the end of the function, but you should set them on again as soon as you finish with the Data cursor tool.

Plotting, marking and legend in a loop

I want to plot a graph, mark a point and write legend in a loop.
labels = {}
for i = 1: size(error,1)
r = plot(handles.axes1,temp(i).time,temp(i).signal);
hold (handles.axes1, 'on')
a = %find minimum index
xmin = temp(i).time(a);
ymin = temp(i).signal(a);
plot(handles.axes1,xmin,ymin,'ro')
labels{end+1} = sprintf('Type : %s, %f,%f',error{i,1}.main(1,1).type,xmin,ymin)
end
grid on
legend(r, labels);
Labeling does not work, as it takes only 1st elements ignoring extra element. and the whole method is a mess with color code all messed up, is there elegant way of doing this where my legend colour matches the plot ones
Another way to do this is to use the DisplayName keyword.
for i = 1:N_lines
%...
r(i) = plot(handles.axes1, temp(i).time, temp(i).signal, 'DisplayName', labels{i});
%...
end
legend('show')
The advantage of doing it like this is that it attaches the name directly to the plotted point. If you are debugging, and open the plot as its being written in the plot browser, as each point get plotted the name will appear next to the point in the right-hand pane. You also don't need to keep track of a separate labels variable in case you end up later reordering your points for some reason. This way the labels always travel with their associated points.
I should add that when you call the legend command with a cell of labels, among other things it backfills 'DisplayName' so that you can still change and query it after the plot has been built even if you don't explicitly set it like I've done above. However, I find this method to be self-documenting and more convenient because it's one less thing to keep track of.
You need to make r an array. As it is, you're only storing the last plot handle so when you call legend it's only going to use the last plot and the first label.
% Create a unique color for each plot
colors = hsv(size(error, 1));
for i = 1:size(error, 1)
r(i) = plot(handles.axes1,temp(i).time,temp(i).signal, 'Color', colors(i,:));
%...
labels{i} = sprintf('Type : %s, %f,%f',error{i,1}.main(1,1).type,xmin,ymin);
end
legend(r, labels)
As a side-note it's best to not use i or j as your looping variable and also, don't name your variable error as that's the name of a MATLAB built-in.

How to get handle of the legend of a plot generated through plotyy when importing a fig file?

So my figures are here: http://www.atmos.uw.edu/~akchen0/CERES_Project/
I'd like to get the handle of the legend of the figure here so that I can copy the figure's legend entries when replotting the legend on a subplot.
If I try to find the axes by running findobj(ax1,'Type','axes','Tag','legend') though, it just returns an Empty matrix: 0-by-1. Is there another way to get the legend?
Your mistake was, that you used the axes handle instead of the figure handle:
leg = findobj(figureHandle,'Type','axes','Tag','legend')
or simply:
leg = findobj(gcf,'Tag','legend')
will work.
The setup is like this. First the figure is created. This one servs as a canvas. Then the other objects are drawn upon the figure. The objects drawn is called children. The function call,
plot(0:10);
Creates a figure and one child. The child is called an axes. By adding a legend with,
legend('This is a legend');
matlab creates an axes object named legend an places it in the figure. This becomes another child. The object handles to the children can be found in
h = get(gcf,'Children')
Then you may save the figure and close it down. When you are loading the figure again, the whole figure, with legends and so on is restored. This includes giving the children new object handles. This means that the children can again be retrieved with,
h = get(gcf,'Children')
you can find the child properties with
get(h(anyElementInChButJustOnePerCall))
By looking at the properties, you will notice that there is a porperty called Tag. This one is named legend for the legend. You can find it with get and look through the properties for all handles (should not be too many) or automatically with a for loop
h = get(hFig,'children');
hLeg = [];
for k = 1:length(h)
if strcmpi(get(h(k),'Tag'),'legend')
hLeg = h(k);
break;
end
end
Where hFig is the handle of the figure (written in the upper right "Figure..."). Or if the figure is just loaded it can be accessed with "get current figure" gcf.

How to refreshing annotations and subplots accross function calls in MATLAB

I am using MATLAB for my data analysis. In my scripts I create figures with fit results so that I can quickly play with fit parameters and see how they change my end results.
My question is whether there is a simple way to be able to just refresh my figures along with subplots and annotations without losing the positions and sizes of the subplots and annotations.
I.e.: I would like to be able manually position my figures on my workspace (I use Linux), manually adjust figure size/position, subplot sizes/positions and annotation sizes/positions and then have their content update, when I rerun the script that does my fitting.
I do realize that the command figure(...) does this nicely and it works, but I am having the problem, that when I resize/move subplots and move annotations, that they're sizes/positions get lost, when I rerun the script.
I am aware that I probably need to use the subplot/annotation handles for this but the question is, what the most elegant and simple way is to do this? Since I need the code to also work when run the first time (i.e. no figures/subplot/annoations yet existent), will I need a lot of if-clause, to check if the handles already exist?
I've been using MATLAB for quite some time and for almost equally long it's been bothering me that I don't know an elegant way to do this!
I had two ideas:
use the "File > Generate Code..." functionality. MATLAB will create
a function that recreates the figure with any modification you made
interactively.
manually retrieve the properties of interest for the objects manipulated
and apply them again when you rerun your scripts. You could either
maintain a list of handles for those graphics objects, or even use the
'Tag' in combination with the FINDOBJ function to locate such objects.
I will illustrate the latter idea with an example:
When the script is run for the first time, we give the user the chance to make changes to the figures interactively. Once done, we retrieve the 'Position' property of figures and all children components contained inside them. These values are then saved to a MAT-file.
Now the user adjusts some parameters and reruns the script. We check for the presence of the MAT-file. If it exists, we load the saved position values and apply them to the figures and their descendant objects, thus restoring the components to their last saved state.
This solution is rather simplistic, thus if changes are made to the script breaking the hierarchy of the graphics handles, you will have to delete the MAT-file, and run the script again.
%# close all figures
close all
%# your script which creates figures
figure, plot(rand(100,1))
figure
subplot(121), plot( cos(linspace(0,6*pi,100)) )
subplot(122), image, axis image, axis ij
%# check for MAT-file
if exist('script_prefs.mat','file')
%# load saved values
load script_prefs.mat
%# get opened figures, and find objects with a position property
fig = get(0, 'Children'); %# allchild(0)
obj = findobj(fig, '-property','position');
try
%# apply values to position property
set(fig, {'Position'},figPos);
set(obj, {'Position'},objPos);
catch
%# delete MAT-file
delete script_prefs.mat
end
else
%# get opened figures, and find objects with a position property
fig = get(0, 'Children');
obj = findobj(fig, '-property','position');
%# wait for the user to finish customizing
waitFig = figure('Menubar','none', 'Position',[200 200 200 40]);
h = uicontrol('Units','normalized', 'Position',[0 0 1 1], ...
'String','Done?', 'Callback','uiresume(gcbf)');
uiwait(waitFig);
close(waitFig);
%# get position property of figures and tagged objects
figPos = get(fig, 'Position');
objPos = get(obj, 'Position');
%# save values to file
save script_prefs.mat figPos objPos
end
I take it you mean you want to refresh the plots themselves, but not anything else.
When you perform a plot(), specify an output argument to retrieve a line handle. Then, when you want to plot different, data, manually adjust that line handles' XData and YData:
lh = plot(xdata,ydata);
%# do some calculations here
...
%# calculated new values: newX and newY
set(lh, 'XData', newx, 'YData', newy);
This is likewise for anything else you want to refresh but not recreate - get the handle corresponding to the graphics object and manually update its properties at a low level.