Multiple custom graphics object inheriting from ChartContainer - matlab

I am trying to write a custom chart (or graphical object, rather), and I would like to plot several of them in a single figure. I am following this guide, but I keep getting:
Error using matlab.graphics.chartcontainer.ChartContainer
Adding TestChart to axes is not supported. Turn hold off.
In my custom class I am combining a line and a surface, but this a minimal class works as an example:
classdef TestChart < matlab.graphics.chartcontainer.ChartContainer
properties (Access = private, Transient, NonCopyable)
% Chart axes.
HandleLine(1,1) % Handle of the line object
end
properties
Axes
XData double {mustBeReal}
YData double {mustBeReal}
ZData double {mustBeReal}
end
methods (Access = protected)
function setup(this)
this.Axes = getAxes(this);
% Create line handle
this.HandleLine = plot3(this.Axes, NaN, NaN, NaN, '-o');
end
function update(this)
this.HandleLine.XData = this.XData;
this.HandleLine.YData = this.YData;
this.HandleLine.ZData = this.ZData;
end
end
end
And then, if I try creating two of them:
a = TestChart('XData', [0 0], 'YData', [0 0], 'ZData', [1 2]);
b = TestChart('XData', [0 0], 'YData', [0 0], 'ZData', [5 8]);
I get the error saying "Adding TestChart to axes is not supported. Turn hold off.".
Is it possible to create custom graphics object that can be plotted several times in the same axex ?
Thanks in advance!

Related

MATLAB show all 'active' plots, plt.show()?

hold on
ax = gca;
plot(ax, [1 2 3])
hold off
hold on
ax = gca;
plot(ax, [3 2 1])
hold off
Python's Matplotlib has plt.show() which can be used like
plt.plot([1, 2, 3])
plt.show()
plt.plot([3, 2, 1])
plt.show()
to display two separate figures. I've tried to recreate this behavior with the top block, with no success. Can it be done?
Context
I know this can be accomplished with explicit calls to figure and axes, but the idea is for this to happen after we finish plotting, not before we begin. That is, I define convenience plot functions
function plot1(varargin)
% do stuff
end
function plot2(varargin)
% do stuff
end
and each has a keyword argument show, which is to be used like
plot1(x, show=true)
plot2(x, show=true)
so plot2 should figure out that something's plotted before it without user input.
I tested below to work as intended with calls to variants of plot, scatter, and image. Also, as in Python, show=false for the last executed plot still acts same as show=true.
plot2([1 2 3], show=1)
plot2([3 2 1])
plot2([1 2 3])
plot2([3 2 1])
function plot2(x, C)
arguments
x;
C.show = false;
end
hold on
fig = gcf();
ax = gca();
plot(ax, x);
maybe_show(fig, ax, C.show)
end
function maybe_show(fig, ax, show_)
fig.Visible = true;
if show_
figure('Visible', 'off')
hold off
end
end

Error using patch in MATLAB

I am creating a drag box to zoom in that uses the patch function. I get the following error when I drag:
Error using patch
Not enough input arguments.
Error in boxReady (line 31)
guiele.dragBox = patch(guiele.ResponsePlotAxis, ...
repmat(vabls.CurrentPoint(1,1),[1 4]), ...
repmat(vabls.CurrentPoint(1,2),[1 4]));
Here's the code I'm using:
% This is the point the cursor is at when the user presses down. drawBox is called again
% when the button is released and the current point then is the other corner of the patch
vabls.CurrentPoint = get(guiele.ResponsePlotAxis,'CurrentPoint');
set(guiele.ResponsePlotLine,'erasemode','none');
XYLims=[get(guiele.ResponsePlotAxis,'xlim') get(guiele.ResponsePlotAxis,'ylim')];
axes(guiele.ResponsePlotAxis);
hold on;
if ishandle(guiele.dragBox)
delete(guiele.dragBox);
end
guiele.dragBox = patch(guiele.ResponsePlotAxis, ...
repmat(vabls.CurrentPoint(1,1),[1 4]), ...
repmat(vabls.CurrentPoint(1,2),[1 4]));
set(guiele.dragBox,'FaceColor','none','EdgeColor','r','LineStyle',':');
% initialize some varaiables
guiele.ResponsePlotAxis=-1;
guiele.dragBox = -1;
The three-argument form for patch (or 4-argument form including an axes handle) requires that you also enter color data for each patch:
patch(X, Y, C);
% Or ...
patch(ax, X, Y, C);
If you don't want to enter color data, you can use the following form:
patch(ax, 'XData', X, 'YData', Y);
So your call to patch would look something like this:
guiele.dragBox = patch(guiele.ResponsePlotAxis, ...
'XData', repmat(vabls.CurrentPoint(1, 1), [1 4]), ...
'YData', repmat(vabls.CurrentPoint(1, 2), [1 4]));

Add non-existent entry to legend

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:

non-homogenous grouped data in MATLAB plotyy()

I have to plot 1 line plot and 3 grouped scatter plots in a single plot window.
The following is the code I tried,
figure;
t1=0:0.1:10;
X = 2*sin(t1);
ts = 0:1:10;
Y1 = randi([0 1],length(ts),1);
Y2 = randi([0 1],length(ts),1);
Y3 = randi([0 1],length(ts),1);
plotyy(t1,X,[ts',ts',ts'],[Y1,Y2,Y3],'plot','scatter');
%plotyy(t1,X,[ts',ts',ts'],[Y1,Y2,Y3],'plot','plot');
The following are my questions,
The above code works if I replace 'scatter' by 'plot' (see commented out line), but 'scatter' works only for 1 data set and not for 3. Why?
How to individually assign colors to the 3 grouped scatter plots or plots?
Read the error message you're given:
Error using scatter (line 44) X and Y must be vectors of the same
length.
If you look at the documentation for scatter you'll see that the inputs must be vectors and you're attempting to pass arrays.
One option is to stack the vectors:
plotyy(t1,X,[ts';ts';ts'],[Y1;Y2;Y3],'plot','scatter');
But I don't know if this is what you're looking for, it certainly doesn't look like the commented line. You'll have to clarify what you want the final plot to look like.
As for the second question, I would honestly recommend not using plotyy. I may be biased but I've found it far to finicky for my tastes. The method I like to use is to stack multiple axes and plot to each one. This gives me full control over all of my graphics objects and plots.
For example:
t1=0:0.1:10;
X = 2*sin(t1);
ts = 0:1:10;
Y1 = randi([0 1],length(ts),1);
Y2 = randi([0 1],length(ts),1);
Y3 = randi([0 1],length(ts),1);
% Create axes & store handles
h.myfig = figure;
h.ax1 = axes('Parent', h.myfig, 'Box', 'off');
h.ax2 = axes('Parent', h.myfig, 'Position', h.ax1.Position, 'Color', 'none', 'YAxisLocation', 'Right');
% Preserve axes formatting
hold(h.ax1, 'on');
hold(h.ax2, 'on');
% Plot data
h.plot(1) = plot(h.ax1, t1, X);
h.scatter(1) = scatter(h.ax2, ts', Y1);
h.scatter(2) = scatter(h.ax2, ts', Y2);
h.scatter(3) = scatter(h.ax2, ts', Y3);
Gives you:
And now you have full control over all of the axes and line properties. Note that this assumes you have R2014b or newer in order to use the dot notation for accessing the Position property of h.ax1. If you are running an older version you can use get(h.ax1, 'Position') instead.

How can I specify to which figure a plot should go?

I have multiple figures open, and I want to update them independently during runtime. The following toy example should clarify my intention:
clf;
figure('name', 'a and b'); % a and b should be plotted to this window
hold on;
ylim([-100, 100]);
figure('name', 'c'); % only c should be plotted to this window
a = 0;
b = [];
for i = 1:100
a = a + 1;
b = [b, -i];
c = b;
xlim([0, i]);
plot(i, a, 'o');
plot(i, b(i), '.r');
drawnow;
end
The problem here is that when I open the second figure, I cannot tell the plot functions to plot to the first one instead of the second (and only c should be plotted to the second).
You can use something like
figure(1)
plot(x,y) % this will go on figure 1
figure(2)
plot(z,w) % this will go on another figure
The command will also set the figure visible and on top of everything.
You can switch back and forth between the figures as necessary by issuing the same figure command. Alternatively, you can use the handle to the figure as well:
h=figure(...)
and then issue figure(h) instead of using numeric indices. With this syntax, you can also prevent the figure from popping up on top by using
set(0,'CurrentFigure',h)
You can specify the axes-object in the plot-command. See here:
http://www.mathworks.de/help/techdoc/ref/plot.html
So, open a figure, insert the axes, save the id of the axes object, and then plot into it:
figure
hAx1 = axes;
plot(hAx1, 1, 1, '*r')
hold on
figure
hAx2 = axes;
plot(hAx2, 2, 1, '*r')
hold on
plot(hAx2, 3, 4, '*b')
plot(hAx1, 3, 3, '*b')
Alternatively, you can use gca instead of creating the axes object yourself (because it's automatically created within the actual figure, when it doesn't exist!)
figure
plot(1,1)
hAx1 = gca;
hold on
figure
plot(2,2)
plot(hAx1, 3, 3)
See the following hierarchy representing the relationship between figures and axes
From http://www.mathworks.de/help/techdoc/learn_matlab/f3-15974.html.