What is the Best Way to Deal with an Empty Line Object?

Suppose I have some data:
x = 1:10; y = 1:10;
Now I want to plot three lines based on three logical index vectors.
i1 = x<=3;
i2 = x > 3 & x < 7;
i3 = x >= 7;
hlines = plot(x(i1),y(i1),'r',x(i2),y(i2),'b',x(i3),y(i3),'g');
legend(hlines,'first','second','third');
So far so good. Now suppose i2 is empty
i1 = x<=3;
i2 = x > 100;
i3 = x >= 7;
hlines = plot(x(i1),y(i1),'r',x(i2),y(i2),'b',x(i3),y(i3),'g');
legend(hlines,'first','second','third');
Warning: Ignoring extra legend entries.
Oops, the legend is not what I want; green should be 'third'.
One way to deal with this is to append NaN to all inputs
hlines = plot([x(i1) nan],[y(i1) nan],'r',[x(i2) nan],[y(i2) nan],'b',[x(i3) nan],[y(i3) nan],'g');
legend(hlines,'first','second','third');
I don't mind that 'second' shows up in legend, even though there is no line. In fact, that's preferred. Keep in mind that all this is in a function and there is no way to know a priori of any of i1, i2, i3 will be empty.
Is there a better way to make sure the the legend is consistent regardless of whether or not i1, i2, or i3 is empty?

 Accepted Answer

i1 = x<=3;
i2 = x > 100;
i3 = x >= 7;
plot(x(i1),y(i1),'r',x(i2),y(i2),'b',x(i3),y(i3),'g');
hold on
hlines = plot(nan,nan,'r', nan, nan, 'b', nan, nan, 'g');
legend(hlines, {'first', 'second', 'third'});
That is, when your legend does not necessarily match the graphic objects exactly, then create fake invisible graphic objects and legend() those.
This technique is very useful for situations such as scatter() where the number of graphics objects being created does not match the number of classes needed to be described.

5 Comments

This approach has the advantage over mine of not increasing the memory footprint by creating new vectors by appending the NaN. But this solution, and mine, both feel like they should be .... unwarranted. I wonder if it wouldn't be better if something like
i2 = [];
hline = plot(x(i2),y(i2))
returned a non-empty line object with some property indicating it's an empty line (maybe just XData and YData both []), instead of returning an empty line object as it does now. That way, plotting empty lines wouldn't have any effect on legends, color ordering, etc. There are probably trade-offs either way.
Such objects can exist. I am not sure why MATLAB handles it the way it does.
h = plot([],[], 'r')
h =
0×1 empty Line array.
h = plot(nan, nan, 'r'); h.XData = []; h.YData = []
h =
Line with properties: Color: [1 0 0] LineStyle: '-' LineWidth: 0.5000 Marker: 'none' MarkerSize: 6 MarkerFaceColor: 'none' XData: [1×0 double] YData: [1×0 double] ZData: [1×0 double] Show all properties
OTOH, this approach has a disadvantage, as I see it, that three line objects are needed for the legend and three adidtional line objects are needed for the actual lines if desired to potentially manipulate the plotted line objects downstream (perhaps for printing). So even if plot returns a 3-element vector, the properties of the hlines used for the legends and the hlines returned from plot() have to stay in sync with each other. I'm sure this can be dealt with, but still.
Regardless of issues with legend(), any downstream manipulations will be painful anyway if the return value from plot can have anywhere from 0-3 elements depending on how i1, i2, and i3 are determined from the processing. I mean, suppose
hlines = plot(x(i1),y(i1),'r',x(i2),y(i2),'b',x(i3),y(i3),'g');
returns hlines with two elements because one of i1, i2, or i3 was empty. Now I want to change the color of (what should be) the red line to black. But if i1 was empty, then
hlines(1).Color = 'k'
will change the color of the wrong line. Or suppose i3 was empty and I want to change the color of the green line
hlines(3).Color = 'k'
results in an error becaue hlines only has two elements.
It almost seems that the only robust way to handle the downstream manipulation is to have three separate plots (as in Steven's answer) and store each returned hline in a cell array, and then manipulate each line object separately, having to check for isempty() on each before doing anything. Ouch. Even doing it this way doesn't help with the legend. Though I suppose one could identify which elements of the cell array contain empty line objects and then replace them with NaN-lines. So maybe something like this:
x = 1:10; y = 1:10;
i1 = x<=3;
i2 = x > 100;
i3 = x >= 7;
colors = {'r','b','g'};
hold on;
h{1} = plot(x(i1),y(i1),'r');
h{2} = plot(x(i2),y(i2),'b');
h{3} = plot(x(i3),y(i3),'g');
for ii = 1:3
if isempty(h{ii})
h{ii} = plot(nan,nan,'Color',colors{ii});
end
end
h = [h{:}];
legend(h,'First','Second','Third')
I guess this is a solution for the problems at hand (legend, and downstream manipulation) but it seems kind of messy.
Some of the issues can be reduced by setting Tag for the objects and the same Tag for the proxy line being used for the legend.
obj1 = findobj(ax, 'Tag', 'first');
set(obj1, 'color', 'k', 'Marker', '*')
Do you think it's worth an enhancement request for plot to not return an empty line obect for empty input argurments? Or would that be a change in behavior too much to ask for? I still wonder if there would be any drawbacks to returning a non-empty Line object (other than that users now might be relying on the current behavior in some way.

Sign in to comment.

More Answers (1)

x = 1:10;
y = 1:10;
i1 = x<=3;
i2 = x > 100;
i3 = x >= 7;
hold on
plot(x(i1),y(i1),'r','DisplayName', 'first');
plot(x(i2),y(i2),'b','DisplayName', 'second');
plot(x(i3),y(i3),'g','DisplayName', 'third');
legend show

1 Comment

This does help, but the user prefers that second also show up, even if there are no visible lines for it.

Sign in to comment.

Categories

Find more on Creating, Deleting, and Querying Graphics Objects in Help Center and File Exchange

Products

Release

R2021a

Asked:

on 13 Jun 2021

Commented:

on 13 Jun 2021

Community Treasure Hunt

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

Start Hunting!