codeaviator
codeaviator

Reputation: 2565

Legend only affects to one of the linked axes

The following MVCE reproduces a problem that I am experiencing when displaying a legend in a figure that has several axes objects.

In order to plot an elevation profile of the terrain I use two axes:

Vertical profile, legend

During the plotting tasks I work with each set of axes independently and at the end of the process I link ax(1) and ax(2) using linkaxes(ax, 'xy') so that they remain synchronized dimensionally.

Finally, I add a legend in the 'southoutside' location but only the foreground axes are automatically shrinked to allow space for the legend, the background axes stay unchanged, thus interfering with the intended layout, as shown in the following image:

Vertical profile, legend, bad axes

The very purpose of using linkaxes was to prevent this undesired behavior.

I would be grateful if someone could shed some light on how to solve this problem.


Update

In response to gnovice comment:

The reason why I am using two axes is because I thought it was the only possible way to combine different colormaps in the same figure. Note that for the sky I use sky_map and for the terrain I use the demcmap function which sets a different colormap.


Code

% Create figure.
figure;

% Create background axes for the sky.
ax(1) = axes;

% Sky data for background.
x_bg = [0, 0, 10, 10];
y_bg = [0, 10, 10, 0];

% Fill sky with color gradient on y-axis.
fill(x_bg, y_bg, y_bg);

% Generate custom sky colormap.
sky_map = interp1([0, 1], [135, 206, 235; 255, 255, 255]./255, linspace(0, 1, 255));

% Apply sky colormap.
colormap(ax(1), sky_map);

% Create foreground axes for the terrain.
ax(2) = axes;

% Terrain data.
x = [0, 0:10, 10];
y = [0, 5*rand(size(0:10)), 0];

% Fill terrain.
fill(x, y, y);

% Sets the colormap and color axis limits based on the elevation data
% limits.
demcmap(y);

% Link x-axis and y-axis of ax(1) and ax(2).
linkaxes(ax, 'xy');

% Hide ax(1).
set(ax(1), 'Visible', 'off');

% Transparent background for ax(2).
set(ax(2), 'Color', 'none');

% Display title.
title('Vertical Profile');

% Display legend underneath.
legend('Terrain', 'Location', 'southoutside');

Upvotes: 4

Views: 255

Answers (2)

EBH
EBH

Reputation: 10450

You can use 2 colormaps on the same figure with the following workaround. The main idea is to concatenate all the colormaps you want to use in one colormap, and then choose which part of the colormap you use:

maxHight = 5;
xmax = 10;
% Sky data for background:
x_bg = [0, 0, 1, 1];
y_bg = [0, 1, 1, 0];

% Terrain data:
x = [0 0:xmax xmax];
y = [0 maxHight*rand(size(x(2:end-1))) 0];

% Custom sky colormap:
sky_map = interp1([0, 1], [135, 206, 235; 255, 255, 255]./255, linspace(0, 1, 255));

% Terrain colormap:
tmap = demcmap(y);

% concat both colormaps:
cmap = [tmap;sky_map];

% use the new colormap on the figure:
colormap(cmap);

% Create background axes for the sky:
ax(1) = axes;

% plot the sky as a patch:
patch(x_bg,y_bg,y_bg,'LineStyle','none')

% define the part of the colormap to use:
ax(1).CLim = [-(size(sky_map)/size(cmap)) 1];

% Create foreground axes for the terrain:
ax(2) = axes;

% plot the terrain as a patch:
patch(ax(2),x,y,y)

% define the part of the colormap to use:
ax(2).CLim = [0 max(y)*(size(cmap)/size(tmap))];

% set the limits and axes visibility:
ylim([1 2*maxHight])
ax(1).Visible = 'off';
ax(2).Color = 'none';
linkprop(ax,'position');

% Display title.
title('Vertical Profile');

% Display legend underneath.
legend('Terrain', 'Location', 'southoutside');

Which will give you this figure:

Terrain

Upvotes: 3

codeaviator
codeaviator

Reputation: 2565

Method 1: Using two axes and linkprop.

Quoting excaza's comment:

linkaxes only syncs axes limits. You'll need to sync properties (like Position) with linkprop

Add linkprop(ax, 'Position'); before calling legend.

Result:

Method 1

As a side note, if you wish to learn more, I suggest reading "Using linkaxes vs. linkprop" from Undocumented Matlab, specially to solve the problem of linkaxes being overriden by each other.


Code:

% Create figure.
figure;

% Create background axes for the sky.
ax(1) = axes;

% Sky data for background.
x_bg = [0, 0, 10, 10];
y_bg = [0, 10, 10, 0];

% Fill sky with color gradient on y-axis.
fill(x_bg, y_bg, y_bg);

% Generate custom sky colormap.
sky_map = interp1([0, 1], [135, 206, 235; 255, 255, 255]./255, linspace(0, 1, 255));

% Apply sky colormap.
colormap(ax(1), sky_map);

% Create foreground axes for the terrain.
ax(2) = axes;

% Terrain data.
x = [0, 0:10, 10];
y = [0, 5*rand(size(0:10)), 0];

% Fill terrain.
fill(x, y, y);

% Sets the colormap and color axis limits based on the elevation data
% limits.
demcmap(y);

% Link x-axis and y-axis limits of ax(1) and ax(2).
linkaxes(ax, 'xy');

% Link Position property of ax(1) and ax(2).
linkprop(ax, 'Position');

% Hide ax(1).
set(ax(1), 'Visible', 'off');

% Transparent background for ax(2).
set(ax(2), 'Color', 'none');

% Display title.
title('Vertical Profile');

% Display legend underneath.
legend('Terrain', 'Location', 'southoutside');

Method 2: Using one axes and modifying 'FaceVertexCData'

Quoting gnovice's comments 1 and 2:

I have to ask... why are you using two axes? Couldn't you plot the two filled polygons on the same axes, then modify the ZData property of each patch to ensure they are layered properly?

Having multiple objects that need their own colormaps is tricky, but you can usually get around it by defining object colors so that they are RGB colors instead of colormap indices. For example, the fill command you use is really just creating patch objects, so for the sky background you could just make a patch object directly, paying special attention to how you define the C input argument. The sky could be made without a colormap, so the terrain could use it.

For the sky part, replace fill with patch and set hold on. And then modify the 'FaceVertexCData' property with an array of true color RGB values (see Patch Properties).

In my code below, the true color array is called CData and is defined by:

% True color array.
CData = [sky_map(1, :); sky_map(end, :); sky_map(end, :); sky_map(1, :)];

which looks like this:

CData =

    0.5294    0.8078    0.9216
    1.0000    1.0000    1.0000
    1.0000    1.0000    1.0000
    0.5294    0.8078    0.9216

then use set with the patch handle h1:

% Apply sky colormap to 'FaceVertexCData' property.
set(h1, 'FaceVertexCData', CData);

Result:

Method 2


Code:

% Create figure.
figure;

% Sky data for background.
x_bg = [0, 0, 10, 10];
y_bg = [0, 10, 10, 0];

% Sky indexed colors.
c_bg = [0, 1, 1, 0];

% Patch sky using default colormap (parula).
h1 = patch(x_bg, y_bg, c_bg);

% Generate custom sky colormap.
sky_map = interp1([0, 1], [135, 206, 235; 255, 255, 255]./255, linspace(0, 1, 255));

% True color array.
CData = [sky_map(1, :); sky_map(end, :); sky_map(end, :); sky_map(1, :)];

% Apply sky colormap to 'FaceVertexCData' property.
set(h1, 'FaceVertexCData', CData);

% Retain sky patch in the current axes.
hold on;

% Terrain data.
x = [0, 0:10, 10];
y = [0, 5*rand(size(0:10)), 0];

% Fill terrain.
h2 = fill(x, y, y);

% Sets the colormap and color axis limits based on the elevation data
% limits.
demcmap(y);

% Display title.
title('Vertical Profile');

% Display legend underneath.
legend(h2, 'Terrain', 'Location', 'southoutside');

Differences

Note that the results generated with Method 1 and Method 2 are very similar but not the same:

Method 1:

  • Slightly smaller figure.
  • Tick marks are visible.
  • Gray color for legend backgound.

Method 2:

  • Slightly bigger figure.
  • Tick marks are not visible.
  • White color for legend backgound.

Upvotes: 3

Related Questions