Related
By plotting the curves in a loop that each curve is related to its own stored data (i.e. method) in the previous question, how is it possible to change and define a new specific color and line type (e.g. "--") for each method's curve after the loop?
figure
hold on
for m = 1:length(methods)
prFileName = strcat(readpath,dataset, '_', methods{m}, '_PRCurve.txt');
R = load(prFileName);
precision = R(:, 1);
recall = R(:, 2);
plot(recall, precision,'color',methods_colors(m,:),'linewidth',2);
end
axis([0 1 0 1]);
hold off
grid on;
box on;
legend('methode one','method two')
xlabel('Recall','fontsize',12);
ylabel('Precision','fontsize',12);
set(gcf,'color','w'); %Background color
ax = gca; % current axes
ax.GridLineStyle='-';
ax.GridAlpha=0.7;
ax.XAxis.LineWidth=4;
ax.YAxis.LineWidth=4;
Grid.LineWidth = 3;
set(gca,'FontName','Arial','FontWeight','bold');
For convenience in implementation, each method's data can be assumes as rand(256x2).
You can use lines_obj = findobj(h, 'Type', 'Line'), and set(lines_obj, ....
Example:
h = figure;
%Plot something
x = -6:0.1:6;
plot(x, sin(x), x, cos(x), x, sin(x.^2), x, cos(x.^2));
%Find all lines in figure
lines_obj = findobj(h, 'Type', 'Line');
%Set style of all lines to '--'
set(lines_obj, 'LineStyle', '--');
result:
In case you need to set different style for each line, and you need to match as specific type to specific plot...
Keep an array of line objects while plotting, and use a for loop:
close all
g = gobjects(4); %Initialize and array with 4 graphics objects.
x = -6:0.1:6;
g(1) = plot(x, sin(x), 'r');hold on %Keep returned object in g(1)
g(2) = plot(x, cos(x), 'g'); %Keep returned object in g(2)
g(3) = plot(x, sin(x.^2), 'b');
g(4) = plot(x, cos(x.^2), 'y');
%Cell array of styles.
styles = {'-', '--', ':', '-.'};
%Array of width values
widths = [0.5, 1, 1.5, 2];
%Modify the style of each line:
for i = 1:length(g)
g(i).LineStyle = styles{i};
g(i).LineWidth = widths(i);
end
Result:
I am trying to run the following function so that I can plot some points connected by a line, but the lines are not displaying in the color I want (white). I tried other colors as well, but the lines still won't show. I don't understand why. Can someone help?
function draw_constellations
figure
hold on
axis([0,100,0,100])
grid off
set(gcf,'color','k')
set(gca,'color','k')
axis square
while 1
[x,y,button] = ginput(1);
switch button
case 1 % left mouse
p = plot(x,y,'w-*');
case {'q','Q'} % on keyboard
break;
end
end
It is because x and y you used to plot are scalers. To plot lines, you will need to store all the x and y you get from ginput in vectors and plot the vectors:
[x,y,button] = ginput(1);
switch button
case 1 % left mouse
xs = [xs x];
ys = [ys y];
p = plot(xs,ys,'w-*');
% more code to go
end
However, if xs and ys are plotted everytime a new point is entered, you will have lines overlapping. To avoid this, we only plot the first point and update the p.XData and p.YData for new points:
if isempty(p)
p = plot(x,y,'w-*');
else
p.XData = xs;
p.YData = ys;
Full code:
figure
hold on
axis([0,100,0,100])
grid off
set(gcf,'color','k')
set(gca,'color','k')
axis square
xs = [];
ys = [];
p = [];
while 1
[x,y,button] = ginput(1);
switch button
case 1 % left mouse
xs = [xs x];
ys = [ys y];
if isempty(p)
p = plot(x,y,'w-*');
else
p.XData = xs;
p.YData = ys;
end
case {'q','Q'} % on keyboard
break;
end
end
Result:
You can use
plot(x,y,'*','color','blue') % plots in blue.
plot(x,y,'*','color',[.5 .4 .7]) % plots the RGB value [.5 .4 .7].
If you want lots of color names, you could use the rgb function to return the RGB values of just about any color. For example,
plot(x,y,'*','color',rgb('blood red'))
Further demonstration on how to change the default color order that you get when you plot lines without specifying the color:
Ever wonder how it plots blue first, then dark green, then red, then cyan, etc.? Ever want to change the default order so that it plots curves with the color order you want instead of the default color order, and without having to specify the color in every single call to plot()? If so, run the attached demo.
% Unless you specify the 'Color' property when you plot,
% plots are plotted according to the 'ColorOrder' property of the axes.
% This demo shows how you can change the default color order of plots.
clc; % Clear the command window.
close all; % Close all figures (except those of imtool.)
clear; % Erase all existing variables.
workspace; % Make sure the workspace panel is showing.
fontSize = 18;
% Make 20 plots, with 25 data points in each plot.
numberOfDataSets = 20;
x = 1:25;
y = rand(numberOfDataSets, length(x));
% These y would all be on top of each other.
% Separate the plots vertically.
offsets = repmat((1:numberOfDataSets)', [1, length(x)]);
y = y + offsets;
% Get the initial set of default plot colors.
initialColorOrder = get(gca,'ColorOrder') % Initial
% See what the colors look like when plotted:
subplot(2, 1, 1);
plot(x,y, 'LineWidth', 3);
grid on;
caption = sprintf('%d plots with the Initial Default Color Order (Note the repeating colors)', numberOfDataSets);
title(caption, 'FontSize', fontSize);
xlabel('X', 'FontSize', fontSize);
ylabel('Y', 'FontSize', fontSize);
% Enlarge figure to full screen.
set(gcf, 'units','normalized','outerposition',[0 0 1 1]); % Maximize figure.
% Give a name to the title bar.
set(gcf,'name','Image Analysis Demo','numbertitle','off')
choice = menu('Which ColorOrder do you want?', 'jet', 'random', 'hsv', 'hot', 'cool', 'spring', 'summer',...
'autumn', 'winter', 'lines', 'gray', 'bone', 'copper', 'pink');
% Make a new axes:
subplot(2, 1, 2);
% Create a new colormap that will define the new default color order property.
switch choice
case 1
newDefaultColors = jet(numberOfDataSets);
case 2
newDefaultColors = rand(numberOfDataSets, 3);
case 3
newDefaultColors = hsv(numberOfDataSets);
case 4
newDefaultColors = hot(numberOfDataSets);
case 5
newDefaultColors = cool(numberOfDataSets);
case 6
newDefaultColors = spring(numberOfDataSets);
case 7
newDefaultColors = summer(numberOfDataSets);
case 8
newDefaultColors = autumn(numberOfDataSets);
case 9
newDefaultColors = winter(numberOfDataSets);
case 10
newDefaultColors = lines(numberOfDataSets);
case 11
newDefaultColors = gray(numberOfDataSets);
case 12
newDefaultColors = bone(numberOfDataSets);
case 13
newDefaultColors = copper(numberOfDataSets);
otherwise
newDefaultColors = pink(numberOfDataSets);
end
% Note: You can build your own custom order if you want,
% just make up a array with numberOfDataSets rows and 3 columns
% where each element is in the range 0-1.
% Apply the new default colors to the current axes.
set(gca, 'ColorOrder', newDefaultColors, 'NextPlot', 'replacechildren');
% Now get the new set of default plot colors.
% Verify it changed by printing out the new default color set to the commandwindow.
newColorOrder = get(gca,'ColorOrder')
% Now plot the datasets with the changed default colors.
plot(x,y, 'LineWidth', 3);
grid on;
caption = sprintf('%d plots with the New Default Color Order', numberOfDataSets);
title(caption, 'FontSize', fontSize);
xlabel('X', 'FontSize', fontSize);
ylabel('Y', 'FontSize', fontSize);
msgbox('Done with ColorOrder demo!');
Special thanks/credit goes to this.
Is there a way to have a mark on a plot axis for any data points off the current plot in Matlab? It is great if the method works with zoom and pan, but I can live without it.
I have several data sets that I am plotting using subplots. Each plot contains 20 data points, similar to marking where darts landed on a dart board. For most data sets, these plots are within a standard range, ie the size of the dart board, so I am using this standard for the axis range of each plot to make the subplots easily visually comparable. However, a few data sets have an outlier that is outside this standard range (typically way outside). I do not want to change the axis to show the outlier, but I want the user to be aware that there is 1 or more data points off the plot, and preferably in which direction they are off.
I thought a bit about trying to use the axis markers (set(gca, 'Ytick', xLowerLimit: markSpacing: xUpperLimit)) and adding an extra marker in a different color to indicate the location of an outlier, but I couldn't see how to do this without disrupting the regular markers and in a automated way that could allow for multiple outlier marks.
One approach is to see if there is any data that exceed your Y Axis limit and then place some text somewhere to notify the user. Options include text (puts text on axis) or uicontrol (puts text somewhere in figure window).
Here is an example using uicontrol:
% Set up figure window and axes
h.f = figure;
h.ax = axes('Units', 'Normalized', 'Position', [0.13 0.2 0.75 0.75]);
% Plot some sample data and window our axes to replicate the question
x = 1:10;
y = [1:9 20];
xmin = 0; xmax = 10;
ymin = 0; ymax = 10;
plot(h.ax, x, y);
axis(h.ax, [xmin xmax ymin ymax]);
% Generate logical matrix to find if any y data is beyond our axis limit
ymask = y > ymax;
if any(ymask) % Loop triggers if any y value is greater than ymax
str = sprintf('There are %u data points greater than current y axis limit', sum(ymask));
uicontrol('Parent', h.f, ...
'Style', 'text', ...
'Units', 'Normalized', ...
'Position', [0.01 0.01 0.9 0.05], ...
'String', str ...
);
end
Which generates the following (annotations added manually):
This can be extended to other directions with some simple copy + paste and tweaks.
Edit: see bottom for improved method using callbacks.
You can find data points exceeding the axes limits and draw them on the limits, using different symbols:
A = randn(20, 2);
x_range = [-1.5, 1.5];
y_range = [-1.5, 1.5];
figure(1);
clf;
ah = axes;
plot(ah, A(:,1), A(:,2), 'o')
hold on
set(ah, 'XLim', x_range, 'YLim', y_range)
x_lt = A(:,1) < x_range(1);
x_gt = A(:,1) > x_range(2);
y_lt = A(:,2) < y_range(1);
y_gt = A(:,2) > y_range(2);
A_out = A;
A_out(x_lt, 1) = x_range(1);
A_out(x_gt, 1) = x_range(2);
A_out(y_lt, 2) = y_range(1);
A_out(y_gt, 2) = y_range(2);
A_out = A_out(x_lt | x_gt | y_lt | y_gt, :);
plot(ah, A_out(:,1), A_out(:,2), 'rx')
This produces a plot like this:
Edit: add callbacks
If you really want to go fancy, you can add callbacks so that outliers are automatically drawn (and removed) when the figure is zoomed. Adding the same functionality for panning is left as an exercise to the reader ;)
mark_outliers.m:
function mark_outliers(fig, ax)
ah = ax.Axes;
lobj = findobj(ah, 'Type', 'Line');
x_range = ah.XLim;
y_range = ah.YLim;
ah.NextPlot = 'add';
for i_l = 1:numel(lobj)
xd = lobj(i_l).XData;
yd = lobj(i_l).YData;
x_lt = xd < x_range(1);
x_gt = xd > x_range(2);
y_lt = yd < y_range(1);
y_gt = yd > y_range(2);
outliers = x_lt | x_gt | y_lt | y_gt;
if any(outliers)
xd_out = xd;
xd_out(x_lt) = x_range(1);
xd_out(x_gt) = x_range(2);
yd_out = yd;
yd_out(y_lt) = y_range(1);
yd_out(y_gt) = y_range(2);
xd_out = xd_out(outliers);
yd_out = yd_out(outliers);
plot(ah, xd_out, yd_out, 'xr', 'Tag', 'Outliers')
end
end
delete_outliers.m:
function delete_outliers(fig, ah)
ah = ah.Axes;
delete(findobj(ah, 'Tag', 'Outliers'));
example.m:
A = randn(20, 2);
fh = figure(1);
clf;
ah = axes;
plot(ah, A(:,1), A(:,2), 'o')
h = zoom(fh);
h.ActionPreCallback=#delete_outliers;
h.ActionPostCallback=#mark_outliers;
I want to add an entry manually to a MATLAB legend. This legend can be pre-existent and contain other graphed elements' entries, but not necessarily.
I make a scatter plot, but instead of using e.g. scatter(x,y), I plot it using
for n = 1:numel(x)
text(x(n),y(n),num2str(n), ...
'HorizontalAlignment','center','color',[1 0 0])
end
This results in a scatter plot of numbers one through the number of elements in x (and y, because they are of the same size). I want to add a legend entry for these numbers.
I tried to add or edit the legend with
[h,icons,plots,s] = legend(___)
as described on the legend documentation page. I can't figure out how I can add a legend entry, without having to plot something (such as an actual scatter plot or regular plot). I want the usual line or marker symbol in the legend to be a number or character such as 'n', indicating the numbers in the graph. Is this possible and how would one achieve this?
EDIT by Erik
My answer goes below zelanix's answer, because mine is based on it.
Original answer
A fairly workable solution may be as follows:
x = rand(10, 1);
y = rand(10, 1);
figure;
text(x,y,num2str(transpose(1:numel(x))),'HorizontalAlignment','center')
% Create dummy legend entries, with white symbols.
hold on;
plot(0, 0, 'o', 'color', [1 1 1], 'visible', 'off');
plot(0, 0, 'o', 'color', [1 1 1], 'visible', 'off');
hold off;
% Create legend with placeholder entries.
[h_leg, icons] = legend('foo', 'bar');
% Create new (invisible) axes on top of the legend so that we can draw
% text on top.
ax2 = axes('position', get(h_leg, 'position'));
set(ax2, 'Color', 'none', 'Box', 'off')
set(ax2, 'xtick', [], 'ytick', []);
% Draw the numbers on the legend, positioned as per the original markers.
text(get(icons(4), 'XData'), get(icons(4), 'YData'), '1', 'HorizontalAlignment', 'center')
text(get(icons(6), 'XData'), get(icons(6), 'YData'), '2', 'HorizontalAlignment', 'center')
axes(ax1);
Output:
The trick to this is that the new axes are created in exactly the same place as the legend, and the coordinates of the elements of the icons are in normalised coordinates which can now be used inside the new axes directly. Of course you are now free to use whatever font size / colour / whatever you need.
The disadvantage is that this should only be called after your legend has been populated and positioned. Moving the legend, or adding entries will not update the custom markers.
Erik's answer
Based on zelanix's answer above. It is a work-in-progress answer, I am trying to make a quite flexible function of this. Currently, it's just a script that you'd need to adapt to your situation.
% plot some lines and some text numbers
f = figure;
plot([0 1],[0 1],[0 1],[1 0])
x = rand(25,1);
y = rand(25,1);
for n = 1:numel(x)
text(x(n),y(n),num2str(n), ...
'HorizontalAlignment','center','color',[1 0 0])
end
hold on
% scatter(x,y) % used to test the number positions
scatter(x,y,'Visible','off') % moves the legend location to best position
% create the dummy legend using some dummy plots
plot(0,0,'o','Visible','off')
[l,i] = legend('some line','some other line','some numbers','location','best');
l.Visible = 'off';
% create empty axes to mimick legend
oa = gca; % the original current axes handle
a = axes;
axis manual
a.Box = 'on';
a.XTick = [];
a.YTick = [];
% copy the legend's properties and contents to the new axes
a.Units = l.Units; % just in case
a.Position = l.Position;
i = copyobj(i,a);
% replace the marker with a red 'n'
s = findobj(i,'string','some numbers');
% m = findobj(i(i~=s),'-property','YData','marker','o');
m = findobj(i(i~=s),'-property','YData');
sy = s.Position(2);
if numel(m)>1
dy = abs(m(1).YData - sy);
for k = 2:numel(m)
h = m(k);
dy2 = abs(h.YData - sy);
if dy2<dy
kbest = k;
dy = dy2;
end
end
m = m(kbest);
end
m.Visible = 'off';
mx = m.XData;
text(mx,sy,'n','HorizontalAlignment','center','color',[1 0 0])
% reset current axes to main axes
f.CurrentAxes = oa;
The result:
I would like to give the subplots I make a simple label. Unfortunately I'm getting an ugly behavior. Consider the following function:
function h = set_label1(label)
tlh = get(gca, 'Title');
if strcmp(get(tlh, 'String'), '')
title(' ');
end
ylh = get(gca, 'YLabel');
if strcmp(get(ylh, 'String'), '')
ylabel(' ');
end
ylp = get(ylh, 'Position');
x = ylp(1);
tlp = get(tlh, 'Position');
y = tlp(2);
h = text('String', label, ...
'HorizontalAlignment', 'right',...
'VerticalAlignment', 'Baseline', ...
'FontUnits', 'pixels', ...
'FontSize', 16, ...
'FontWeight', 'bold', ...
'FontName', 'Arial', ...
'Position', [x y 0]);
end
Here is a simple test run:
figure;
h1 = axes('OuterPosition', [0,0,.5 1]);
set(h1,'LooseInset',get(h1,'TightInset'));
h2 = axes('OuterPosition', [.5,0,.5 1]);
set(h2,'LooseInset',get(h2,'TightInset'));
axes(h1);
plot([0 1], [4 5]);
set_label1('A');
axes(h2);
plot([0 1], [4 5]);
set_label1('B');
The picture I obtain is:
If you resize the figure the labels will not be in the right position anymore. That is fine, I expected it (If you know how to put them back where they belong and you tell us that would make me very happy).
THe problem I'm facing is that I do not want to specify the position of the label in 'data' units.
Instead, I want to use normalized units. So I used modified form of function. Now let us use this:
function h = set_label2(label)
tlh = get(gca, 'Title');
if strcmp(get(tlh, 'String'), '')
title(' ');
end
ylh = get(gca, 'YLabel');
if strcmp(get(ylh, 'String'), '')
ylabel(' ');
end
oldUnits = replace_prop(ylh, 'Units', 'normalized');
ylp = get(ylh, 'Position');
x = ylp(1);
set(ylh, 'Units', oldUnits);
oldUnits = replace_prop(tlh, 'Units', 'normalized');
tlp = get(tlh, 'Position');
y = tlp(2);
set(ylh, 'Units', oldUnits);
h = text('String', label, ...
'HorizontalAlignment', 'right',...
'VerticalAlignment', 'Baseline', ...
'FontUnits', 'pixels', ...
'FontSize', 16, ...
'FontWeight', 'bold', ...
'FontName', 'Arial', ...
'Units', 'normalized',...
'Position', [x y 0]);
end
function oldvalue = replace_prop(handle, propName, newvalue)
oldvalue = get(handle, propName);
set(handle, propName, newvalue);
end
Running the same test:
figure;
h1 = axes('OuterPosition', [0,0,.5 1]);
set(h1,'LooseInset',get(h1,'TightInset'));
h2 = axes('OuterPosition', [.5,0,.5 1]);
set(h2,'LooseInset',get(h2,'TightInset'));
axes(h1);
plot([0 1], [4 5]);
set_label2('A');
axes(h2);
plot([0 1], [4 5]);
set_label2('B');
We obtain the exact same picture as before. The only problem is that when we resize it now something bad happens:
The labels are actually in the correct position. But it seems that the 'LooseInset' and 'TightInset' property I used make the axes act as if there is no labels.
Is there any fix for this? Really all I am doing is getting the position of the title and ylabel in normalized units as opposed in data units and this seems to mess it up.
The reason I need to get it in normalized units is so that when we get a 3D plot I can position the label with respect to the title and the zlabel.
For posterity's sake here is the version I decided to go with. It does what I expect it to do, but now I have a problem which I have no idea how to solve. OK, first the good news, here is the function called axes_label.
function c = axes_label(varargin)
if isa(varargin{1}, 'char')
axesHandle = gca;
else
axesHandle = get(varargin{1}{1}, 'Parent');
end
if strcmp(get(get(axesHandle, 'Title'), 'String'), '')
title(axesHandle, ' ');
end
if strcmp(get(get(axesHandle, 'YLabel'), 'String'), '')
ylabel(axesHandle, ' ');
end
if strcmp(get(get(axesHandle, 'ZLabel'), 'String'), '')
zlabel(axesHandle, ' ');
end
if isa(varargin{1}, 'char')
label = varargin{1};
if nargin >=2
dx = varargin{2};
if nargin >= 3
dy = varargin{3};
else
dy = 0;
end
else
dx = 3;
dy = 3;
end
h = text('String', label, ...
'HorizontalAlignment', 'left',...
'VerticalAlignment', 'top', ...
'FontUnits', 'pixels', ...
'FontSize', 16, ...
'FontWeight', 'bold', ...
'FontName', 'Arial', ...
'Units', 'normalized');
el = addlistener(axesHandle, 'Position', 'PostSet', #(o, e) posChanged(o, e, h, dx, dy));
c = {h, el};
else
h = varargin{1}{1};
delete(varargin{1}{2});
if nargin >= 2
if isa(varargin{2}, 'char')
set(h, 'String', varargin{2});
if nargin >=3
dx = varargin{3};
dy = varargin{4};
else
dx = 3;
dy = 3;
end
else
dx = varargin{2};
dy = varargin{3};
end
else
error('Needs more arguments. Type help axes_label');
end
el = addlistener(axesHandle, 'Position', 'PostSet', #(o, e) posChanged(o, e, h, dx, dy));
c = {h, el};
end
posChanged(0, 0, h, dx, dy);
end
function posChanged(~, ~, h, dx, dy)
axh = get(h, 'Parent');
p = get(axh, 'Position');
o = get(axh, 'OuterPosition');
xp = (o(1)-p(1))/p(3);
yp = (o(2)-p(2)+o(4))/p(4);
set(h, 'Units', get(axh, 'Units'),'Position', [xp yp]);
set(h, 'Units', 'pixels');
p = get(h, 'Position');
set(h, 'Position', [p(1)+dx, p(2)+5-dy]);
set(h, 'Units', 'normalized');
end
Ok, so how do we use this crappy function? I made it so that we can have these uses:
% c = axes_label('label')
% Places the text object with the string 'label' on the upper-left
% corner of the current axes and returns a cell containing the handle
% of the text and an event listener.
%
% c = axes_label('label', dx, dy)
% Places the text object dx pixels from the left side of the axes
% and dy pixels from the top. These values are set to 3 by default.
%
% c = axes_label(c, ...)
% Peforms the operations mentioned above on cell c containing the
% handle of the text and the event listener.
%
% c = axes_label(c, dx, dy)
% Adjusts the current label to the specifed distance from the
% upper-left corner of the current axes.
If we perform the same test as before:
figure;
h1 = axes('OuterPosition', [0,0,.5 1]);
set(h1,'LooseInset',get(h1,'TightInset'));
h2 = axes('OuterPosition', [.5,0,.5 1]);
set(h2,'LooseInset',get(h2,'TightInset'));
axes(h1);
plot([0 1], [4 5]);
axes_label('A');
axes(h2);
plot([0 1], [4 5]);
axes_label('B', 250, 250);
Now we obtain what I wanted. Label 'A' is set at the upper-left corner of the axes's Outerbox. And label B I explicitly set it to be 250 pixels from its upper-left corner. Here is a plot:
What I like about this function is that if I were to store the cell returned from it and then I put back I can change the position. For instance if label = axes_label('A'); Then on the command prompt I can do label = axes_label(label, 10, 20); and I will see my label move.
The problem I'm facing now is ralated to the function export_fig
If I try to use this:
export_fig('testing.png', '-nocrop', '-painters');
Then this is the figure I obtain.
This is the reason why I exaggerated with label B. Before I added the event listeners export_fig would do an OK job at printing the labels where I had positioned them. But somehow now export_fig doesn't do what it claims it does. Mainly exporting an image with
Figure/axes reproduced as it appears on screen
If instead we remove the option -painters then we get this:
There is probably a bug in code since I'm not experienced with listeners, so if anyone can fix this behavior and/or can improve on this code please feel free to do so and share it as an answer.
First of all, I like your idea of using the title/y-label to position the text on the upper left corner, clever :)
Now, instead of using normalized units, keep using data units and create an event listener for whenever the title or the y-label change their positions, and use their new values to re-adjust the created text.
So add the following to the end of your set_label1 function:
addlistener(ylh, 'Position', 'PostSet', #(o,e) posChanged(o,e,h,1))
addlistener(tlh, 'Position', 'PostSet', #(o,e) posChanged(o,e,h,2))
and here is the callback function used for both cases (we use the last argument idx to control whether to set x or y coordinate):
function posChanged(src,evt,hTxt,idx)
posLabel = evt.NewValue; %# new position of either title/y-label
posText = get(hTxt, 'Position'); %# current text position
posText(idx) = posLabel(idx); %# update x or y position (based on idx)
set(hTxt, 'Position',posText) %# adjust the text position
end