In the code below I try to highlight a specific column by red. However, the resulting label color bar is not aligned with the labels, as shown in the image. How can I correct this?
datdat = randn(5,10);
regregnames = {'A', 'B', 'C', 'D', 'E'};
colors = cell(1,size(datdat,2));
for i=1:size(datdat,2)
colors{i} = [1,1,1];
end
colors{3} = [1,0,0];
s.Labels = arrayfun(#num2str, 1:size(datdat,2), 'UniformOutput', false);
s.Colors = colors;
clscls = clustergram(datdat, 'RowLabels', regregnames, 'ColumnLabels', s.Labels, 'ColumnLabelsColor', s, 'LabelsWithMarkers', true);
This is definitely a bug in MATLAB. How do I know? By inspecting the clustergram.plot function.
If we set a breakpoint on line 142, positionAxes(obj, imAxes), and run your code until that point, we get the following figure:
where the alignment is correct, but the dendrogram is not visible. Then, the code proceeds to reposition the axes (mainly making them smaller), while unfortunately neglecting the bottom part with the red label.
To understand how to fix this, we should go a bit back, into HeatMap.plot > initHMAxes where this bottom bar is created and find where its handle is stored. Then, all we need to do is adjust the position of this element in accordance with the rest of the clustergram (HeatMap).
I'll leave digging through the handles/appdata "as an exercise to the reader", but long story short, just add this to the end of your code:
hAx = struct(clscls).HMAxesHandle;
data = getappdata(hAx, 'HeatMapAxesData');
data.XMarkerAxes.Position = data.XMarkerAxes.Position.*[0 1 0 1] + hAx.Position.*[1 0 1 0];
The result:
BTW, on R2017b I'm getting the following warning:
Warning: COLUMNLABELSCOLOR is not supported and will be removed in a future release.
Use LabelsWithMarkers for similar functionality.
So technically this is not a bug, but rather an unsupported feature.
Related
Is there a (manual or automatic) way of cutting out several parts of the y-axis for certain x-values in a Matlab plot? I found solutions in other programming languages (see 2 links below), but not for Matlab, except for BreakAxis and BreakYaxis on File Exchange, but this only works for one break.
I am also posting my code below, for which I would like to implement it. I would like to have a y-axis break for each yNegData and yPosData, i.e. two breaks, each at [0.3*min(yNegData) 0.7*min(yNegData] and [0.3*max(yPosData) 0.7*max(yPosData].
If you could write it in a way that I could use it for different kinds of plots (not only bar, but also line, for example), that would be very useful.
http://lagrange.univ-lyon1.fr/docs/matplotlib/examples/pylab_examples/broken_axis.html
Using gnuplot, how to 'cut out' usused y-axis regions of the graph
The code:
revenue = ones(100,1);
opex = -1*ones(100,1);
opex(10:15,1) = 3;
data{1} = revenue;
data{2} = opex;
colors = parula(numel(data));
labels = {'Revenue','Opex'};
for i = 1:numel(data)
dataNeg{i} = data{i};
dataNeg{i}(data{i}>0) = 0;
dataPos{i} = data{i};
dataPos{i}(data{i}<0) = 0;
mdata(i) = nnz(dataPos{i}); % was: mean(data{i});
end
[~,posOrder] = sort(mdata,'ascend');
[~,negOrder] = sort(mdata,'descend');
yDataPos = [dataPos{posOrder}];
yDataNeg = [dataNeg{negOrder}];
hold on;
bNeg = bar(yDataNeg,'stack');
bPos = bar(yDataPos,'stack');
for i= 1:numel(data)
set(bNeg(i),'FaceColor',colors(negOrder(i),:))
set(bPos(i),'FaceColor',colors(posOrder(i),:))
end
legend(labels{:});
hold off;
There is this package
https://nl.mathworks.com/matlabcentral/fileexchange/45760-break-y-axis
which works for lines; it has a little example
a=20*rand(21,1)+10;
figure;hold on;
plot(a);
breakyaxis([14 21]);
% hold off %% I guess, inserted by me
However, it does not seem to work as well for bar plots -- if you replace plot(a) by bar(a) in the example above the split stripe does not cover the width of the axes. It could be tweaked I'm sure but that might not be worth the effort.
If you're OK with switching to R you can use gap.barplot() from the plotrix package (see this example) and you can also still switch to gnuplot (see this example).
I'm writing longer script for plotting some bar plots in Matlab. I want to set my own colors of the bars when a proper condition is met. I have 30 bars in one plot. That's the general background, I won't paste here this long code, instead of I created shorter code only to illustrate one particular problem I'm struggling with. For some reason bar() function in MATLAB doesn't allow to use custom variables to indicate which plot needs to be, for example in this case, re-colored.
Here's the sample script that I prepared:
cond_1 = 10;
cond_2 = 8;
i = 1;
cond_SD_1 = 2;
cond_SD_2 = 3;
z = [cond_1, cond_2];
zz = [cond_SD_1, cond_SD_2];
hold on
newName=sprintf('cond_%d',i);
title(newName);
bar(1, cond_1, 'FaceColor', 'Red','LineWidth',2)
bar(2, newName, 'FaceColor', 'Blue','LineWidth',2)
errorbar(z,zz,'color',[.44 .44 .44],'linestyle','none','linewidth',2), grid on, grid minor, box off,
hold off
So let's say that we want to have two bars only, like in the example above. And we need to use custom variable to tell bar() which bar we want to change. We created the variable in this line (newName=sprintf('cond_%d',i);) - it gave as a char of cond_1. When we call bar() like that bar(1, cond_1, 'FaceColor', 'Red','LineWidth',2) everything works fine, but when we want to use bar(2, newName, 'FaceColor', 'Blue','LineWidth',2) it gives an error Unrecognized option "cond_1".. I also added there title() function, which doesn't have this problem and understands char variables perfectly. title(newName); with our custom variable name newName is set correctly as a title. I used this method several times before, and it seems that this problem occurs only with bar() function. Does anyone have any idea how to fix it?
Thanks in advance for your help!
BM
Don’t use variable names like cond_1 and cond_2. Instead, your array z is all you need. z(i) is the element you want to color differently. Let’s take a slightly larger dat set as example:
z = 9*rand(1,10) + 9;
zz = 2*rand(1,10) + 1;
k = find(z>14) % the bars to color red
x = 1:10;
hold on
bar(x,z, 'FaceColor', 'Blue','LineWidth',2)
bar(x(k),z(k), 'FaceColor', 'Red','LineWidth',2)
errorbar(x,z,zz, 'color',[.44 .44 .44],'linestyle','none','linewidth',2)
I am having a small issue with printing Matlab figure into the size I preferred. The current figure I can get is shown on the top and I want to get the figure as the bottom. I used the code from Matlab help to minimize the white space.
Thank You.
Your kindness help is appreciated.
ax = gca;
outerpos = ax.OuterPosition;
ti = ax.TightInset;
left = outerpos(1) + ti(1);
bottom = outerpos(2) + ti(2);
ax_width = outerpos(3) - ti(1) - ti(3);
ax_height = outerpos(4) - ti(2) - ti(4);
ax.Position = [left bottom ax_width ax_height];
set(gca,'YTick',[0:1:4])
set(gca,'XTick',[0:2:20])
set(gca,'XMinorTick','on','YMinorTick','on')
set(gca,'TickLength',[0.015 0.01])
set(gca,'FontSize',12)
set(gca,'FontName','Times New Roman')
set(gca,'layer','top')
set(gcf,'renderer','zbuffer');
There is a function in the MATLAB file exchange which may satisfy your needs: tightfig. It automatically reduces the maximum of whitespace possible.
Compare this (just running your code):
to this (just running your code with tightfig
% Your code example from above (all but last two lines skipped) ...
set(gca,'layer','top')
set(gcf,'renderer','zbuffer');
tightfig; % Calling tightfig function
(right click the figures and "open in new tab" to see the difference)
I have a plot with mutliple lines and I want to display the legend below the box (southoutside). The problem is that currently my legend is too long to fit on a single line. Therefore the question is how do I get a line break in my legend?
Currently I generate the legend as follows:
hLegend = legend([l1,l2,l3,l4], 'This is a very, very long legend text', 'Test2', ...
'A bit longer', 'This is quite long');
set(hLegend,'Fontsize',8,'Location', 'southoutside', 'Orientation','horizontal');
then this occurs:
As you can see I have four lines (there might come more) and the first one has a very long name.
I want to keep the orientation this way to reduce figure space needed and I want to put an automatic line break if the legend exceeds the picture width (i.e. before l3 or l4, here illustrated by the yellow or purple line).
Any ideas on this? I am using a plot width of 15.75 cm.
Edit
Thanks a lot for the answers so far. Although both of the answers provide some opportunities in splitting the legend into two lines, my main problem still occurs. If assuming now that the plot had more then four lines, lets say 20 and I want to have the legend southside horizontal in a way that it uses the least space, is there a way to split the legend not within one legend text, but after one entry. I generated a new figure generally depicting what I am looking for (its made in Paint so it really just shows the general idea).
Edit 2
The columnlegend package available in the Matlab File Exchange unfortunately does not support legends outside of the figure (at least the options are not specified in the description it only names the following possible locations: 'NorthWest', 'NorthEast', 'SouthEast', 'SouthWest'
Help is appreciated.
Intro:
Here's a proof-of-concept of legend text wrapping, using some undocumented outputs of legend and the MATLAB -> python interface. I will first show the code and then provide a brief explanation of why/how it works.
This is done in MATLAB 2016a.
Code:
function q39456339
%% Definitions:
MAX_LENGTH_IN_CHARS = 20;
OPTION = 2;
%% Plot something:
x = 1:10;
figure('Position',[450 400 800 270]);
plot(x,x,x,2*x,x,10-x,x,20-2*x);
%% Using python's TextWrapper to wrap entries:
% web(fullfile(docroot, 'matlab/matlab_external/call-python-from-matlab.html'))
switch OPTION
case 1
[~,hT] = legend({'This is a very, very long legend text', 'Test2', 'A bit longer', ...
'This is quite long'},'Location', 'SouthOutside', 'Orientation','Horizontal',...
'Fontsize',8,'Box','Off');
texts = hT(arrayfun(#(x)isa(x,'matlab.graphics.primitive.Text'),hT));
wrapLegendTexts(texts,MAX_LENGTH_IN_CHARS);
case 2
hL = legend({'This is a very, very long legend text', 'Test2', 'A bit longer', ...
'This is quite long'},'Location', 'SouthOutside', 'Orientation','Horizontal',...
'Fontsize',8,'Interpreter','tex');
TEX_NEWLINE = '\newline';
addNewlinesThroughPython(hL, MAX_LENGTH_IN_CHARS, TEX_NEWLINE);
end
end
%% Helper functions:
function wrapLegendTexts(textObjs,maxlen)
tw = py.textwrap.TextWrapper(pyargs('width', int32(maxlen)));
for ind1 = 1:numel(textObjs)
wrapped = cellfun(#char,cell(wrap(tw,textObjs(ind1).String)), 'UniformOutput', false);
textObjs(ind1).Text.String = reshape(wrapped,[],1);
end
end
function addNewlinesThroughPython(hLeg, maxlen, newlineStr)
tw = py.textwrap.TextWrapper(pyargs('width', int32(maxlen)));
for ind1 = 1:numel(hLeg.PlotChildren)
hLeg.PlotChildren(ind1).DisplayName = char(...
py.str(newlineStr).join(wrap(tw,hLeg.PlotChildren(ind1).DisplayName)));
end
end
Result:
Option 1:
Option 2:
Explanation (Option 1):
First, let's look at the signature of legend:
>> dbtype legend 1
1 function [leg,labelhandles,outH,outM] = legend(varargin)
We can see that the 2nd output returns some sort of handles. When we investigate further:
arrayfun(#class, hT, 'UniformOutput', false)
ans =
'matlab.graphics.primitive.Text'
'matlab.graphics.primitive.Text'
'matlab.graphics.primitive.Text'
'matlab.graphics.primitive.Text'
'matlab.graphics.primitive.Line'
'matlab.graphics.primitive.Line'
'matlab.graphics.primitive.Line'
'matlab.graphics.primitive.Line'
'matlab.graphics.primitive.Line'
'matlab.graphics.primitive.Line'
'matlab.graphics.primitive.Line'
'matlab.graphics.primitive.Line'
And:
hT(1)
ans =
Text (This is a very, very long legend text) with properties:
String: 'This is a very, very long legend text'
FontSize: 9
FontWeight: 'normal'
FontName: 'Helvetica'
Color: [0 0 0]
HorizontalAlignment: 'left'
Position: [0.0761 0.5128 0]
Units: 'data'
Show all properties
Aha! This is the first legend text entry. We see several interesting properties in the above list (more here), but what we care about is String.
Then it's a question of how to wrap said string. Fortunately, this is exactly the example provided in the MATLAB documentation for using the python interface, so I will not go into any details of that. Here's a link to the docs of python's textwrap. The correct version of the page (selectable by a dropdown on the top left) should correspond to your local python version (see output of pyversion).
The rest of my code is just a wrapper around the python interface, to process all legend entries.
Explanation (Option 2):
Here we don't use any extra outputs of legend, and instead modify hLeg.PlotChildren.DisplayName. This property doesn't accept cell arrays of strings (the way for multi-line strings are usually defined in MATLAB), so we need to insert newline "marks" based on syntax the interpreter recognizes (..or character 10 - the ASCII value of a "newline", as shown in excaza's answer). Finding the correct positions for the line break is still done using python, but this time the strings are joined (with the newline mark in between) instead of being converted to a cell column.
Notes:
The 1st option probably provides more control at the expense of some additional required tweaking. One may need to play around with the Texts' Position parameters after wrapping the strings to make the legend look a bit nicer
Assigning the 2nd output of legend changes it behavior slightly (you can see it from the overlapping legend entries in the top figure).
For an automated approach that does not require a local Python installation you can specify a maximum character width and use a regular expression to wrap your text strings accordingly.
For example:
function testcode
x = 1:10;
y1 = x;
y2 = 2*x;
y3 = 3*x;
y4 = 4*x;
l = plot(x, y1, x, y2, x, y3, x, y4);
maxwidth = 20; % Maximum character width of each legend string line
ls = {'This is a very very long legend text', 'Test2', 'A bit longer', 'This is quite long'};
ls = cellfun(#(x)wrapstr(x,maxwidth), ls, 'UniformOutput', false);
legend([l(1),l(2),l(3),l(4)], ls, 'Location', 'SouthOutside', 'Orientation', 'horizontal');
end
function [output] = wrapstr(s, width)
% Split input string s into:
% \S\S{width-1,}: sequences of 1 non-whitespace character followed by
% width-1 or more non-whitespace characters OR
% .{1, width}: sequences of 1 to width of any character.
%
% (?:\\s+|$): Each group is followed by either whitespace or the end of the string
exp = sprintf('(\\S\\S{%u,}|.{1,%u})(?:\\s+|$)', width-1, width);
tmp = regexp(s, exp, 'match');
output = strjoin(deblank(tmp), '\n');
end
Which produces:
The regexp matches Steve Eddin's FEX submission: linewrap
I don't know how to accomplish the following in MATLAB. I have a figure which looks like this:
In the figure, I have a panel with many subplots and a scrollbar that allows me to view a portion of the panel.
I want to save the whole contents of the panel to a PNG image file (not just the visible portion), i.e. I want to have a file which is a tall rectangle, and doesn't require scrolling.
The code for generating the figure is as follows:
function draw(obj)
figure;
panel1 = uipanel('Parent',1);
panel2 = uipanel('Parent',panel1);
panelheight = obj.nIterations / 2;
set(panel1,'Position',[0 0 0.97 1]);
set(panel2,'Position',[0 1-panelheight 1 panelheight]); %%
nPlot = 1;
for i=1:obj.nIterations
models = obj.iterations{i};
for nModel=1:length(models)
subplot(obj.nIterations,length(models)*2,nPlot);
nPlot = nPlot + 1;
drawTransitions(models{nModel});
set(gca,'Parent',panel2);
subplot(obj.nIterations,length(models)*2,nPlot);
nPlot = nPlot + 1;
drawRewards(models{nModel});
set(gca,'Parent',panel2);
end
end
s = uicontrol('Style','Slider','Parent',1,...
'Units','normalized','Position',[0.97 0 0.03 1],...
'Value',1,'Callback',{#slider_callback1,panel2,panelheight});
end
I have tried the following, without success.
The saveas funstion saves the whole figure, not just the panel. Also, it clips the invisible portion of the panel.
export_fig(panel2.'file.png') gives just a solid gray image.
Why don't you just scroll your panel and grab the frames and concatenate them all together? Here's some code that will basically do that. I would have posted am image, but I guess I don't have enough points for that. You may need to fiddle with the scrolling, and maybe making the slider invisible, but it works.
function printPanel(pnl,filename)
fig = figure(ancestor(pnl,'figure'));
pnl_units = get(pnl,'units');
fig_units = get(fig,'units');
set(pnl,'units','pixels')
set(fig,'units','pixels')
pnl_rect = getpixelposition(pnl);
fig_rect = getpixelposition(fig);
pnl_height = pnl_rect(4);
fig_height = fig_rect(4);
pnl_rect(2) = -pnl_height;
set(pnl,'position',pnl_rect)
N = ceil(pnl_height/fig_height);
CDATA = cell(N,1);
for i = 1:N
F = getframe(fig);
CDATA{i} = F.cdata;
pnl_rect(2) = pnl_rect(2)+fig_height;
set(pnl,'position',pnl_rect)
drawnow
end
set(pnl,'units',pnl_units)
set(fig,'units',fig_units)
imwrite(cat(1,CDATA{:}),filename)
end
You could get rid of the ui elements and just make a figure with all the subplots, and then export that one, using e.g. print -dpng ....
saveas takes a handle as a first argument. Maybe this does not have to be a figure or model handle, but could be a reference to the contents of the panel.