Bar plot with a hatched fill pattern

I have a grouped bar plot, bb:
bb = bar(ax, x, y)
where 'ax' is the axis handle, x is a 1x7 datetime vector and y is a 5x7 double vector. For each of the seven dates, I get five bars with data.
I then specify the color of the bars:
for i = 1:5
bb(i).FaceColor = colmapLight(i,:);
bb(i).EdgeColor = colmapDark(i,:);
end
In addition to specifying the colors, I want to use a hatched fill pattern, e.g. horizontal lines in the first two bars in each group, and dots in the last three. I tried using the functions mentioned in this post (https://blogs.mathworks.com/pick/2011/07/15/creating-hatched-patches/), but I haven't managed to make any of them work. I think the hatchfill function (https://se.mathworks.com/matlabcentral/fileexchange/30733-hatchfill) suits my needs best (I want to keep my custom bar colors; plus I don't need a bitmap copy of the figure, want to keep it as a fig). However, the function works on 'patch' objects and I don't know how to get their handles. The following:
hPatch = findobj(bb, 'Type', 'patch');
returns an empty, 0x0 GraphicsPlaceholder.
Does anyone know a way to solve this? Thanks in advance!

 Accepted Answer

You'd want to avoid hatchfill(). That's so old that it predates the current graphics system. Things like contour and bar plots are constructed differently than they were prior to R2014. There is an updated version that seems to work okay for this.
y = 0.5*randn(3,5)+2; % a simplified example
hp = bar(y);
cm = colororder; % or replace with the desired colormap
hatchfill2(hp(1),'single','HatchAngle',0,'hatchcolor',cm(1,:));
hatchfill2(hp(2),'cross','HatchAngle',45,'hatchcolor',cm(2,:));
hatchfill2(hp(3),'single','HatchAngle',45,'hatchcolor',cm(3,:));
hatchfill2(hp(4),'single','HatchAngle',-45,'hatchcolor',cm(4,:));
hatchfill2(hp(5),'cross','HatchAngle',30,'hatchcolor',cm(5,:));
for b = 1:numel(hp)
hp(b).FaceColor = 'none';
end

31 Comments

Thanks a lot, I didn't know about the newer version. I had to change my original code a little, since the function didn't work with my datetime x axis, but I'll take it!
Is there a way to keep background colors specified in my colormap, and add a black/white hatched pattern? Setting the FaceColor seems to override the hatchfill2 function (the hatched pattern disappears). The only workaround I've come up with so far is making the bars partly transparent (FaceAlpha = 0.5), but it's not a perfect solution.
it seems to work for me
y = 0.5*randn(3,5)+2; % a simplified example
hp = bar(y);
hatchfill2(hp(1),'single','HatchAngle',0,'hatchcolor','k');
hatchfill2(hp(2),'cross','HatchAngle',45,'hatchcolor','k');
hatchfill2(hp(3),'single','HatchAngle',45,'hatchcolor','k');
hatchfill2(hp(4),'single','HatchAngle',-45,'hatchcolor','k');
hatchfill2(hp(5),'cross','HatchAngle',30,'hatchcolor','k');
EDIT: The way I'd set the hatch colors in the first example was based off of the figure's 'colororder' property, which is what is normally used for the bar face color. The default map used for colororder is lines(7).
If you didn't change the colormap that was being specified for the hatching, then it would've simply been the same color as the bar's face color.
y = 0.5*randn(3,5)+2; % a simplified example
hp = bar(y);
cm = hsv(6); % a colormap other than the one specified in 'colororder'
hatchfill2(hp(1),'single','HatchAngle',0,'hatchcolor',cm(1,:));
hatchfill2(hp(2),'cross','HatchAngle',45,'hatchcolor',cm(2,:));
hatchfill2(hp(3),'single','HatchAngle',45,'hatchcolor',cm(3,:));
hatchfill2(hp(4),'single','HatchAngle',-45,'hatchcolor',cm(4,:));
hatchfill2(hp(5),'cross','HatchAngle',30,'hatchcolor',cm(5,:));
Thanks, I get it now! In my code I didn't specify the hatchfill color, so I guess it must have used the one previously set as FaceColor, making the hatched pattern invisible. It works perfectly now. Thank you again!
Qiandong Dong
Qiandong Dong on 9 Aug 2022
Moved: Matt J on 1 Mar 2023
The legend can be also added via legendflex, which will be better.
jack carter
jack carter on 4 Oct 2022
Edited: jack carter on 4 Oct 2022
May I ask how shall I call the function in my coding? I copied the hatchfill2.m file in the folder where I have saved the script with your coding only and then ran the script but it is not working.
"Unrecognized function or variable 'hatchfill2'."
Also, how can I add legend to the resulting graph? Will the old legend command will work with it?
legend ({'group1','group2','group3','group4'});
Unless that entire directory is part of the MATLAB path, it won't be found. Any function files that you want MATLAB to use need to either be in a directory that's on the path, or have their directory added to the path. It might be best to set aside a directory where you can put things downloaded from the File Exchange.
As to the legend, there are a couple examples here. Last I checked, both should work.
The first example will require legendflex(), which is also freely available.
This is extremely helpful.
Is there a way to draw a hatched stacked bar?
Same example as above. I just set the 'stacked' option in the call to bar()
y = 0.5*randn(3,5)+2; % a simplified example
hp = bar(y,'stacked');
cm = hsv(6); % a colormap other than the one specified in 'colororder'
hatchfill2(hp(1),'single','HatchAngle',0,'hatchcolor',cm(1,:));
hatchfill2(hp(2),'cross','HatchAngle',45,'hatchcolor',cm(2,:));
hatchfill2(hp(3),'single','HatchAngle',45,'hatchcolor',cm(3,:));
hatchfill2(hp(4),'single','HatchAngle',-45,'hatchcolor',cm(4,:));
hatchfill2(hp(5),'cross','HatchAngle',30,'hatchcolor',cm(5,:));
Hi, when I use hatchfill2 to plot a bar graph, the legend is not showing the hatch but it is just a blank box. Please help.
Is it possible to make it available for a boxchart type?
DGM
DGM on 24 Feb 2023
Edited: DGM on 24 Feb 2023
Does it work as-is? No. Is it possible to make it work? As far as I can tell, yes it should be possible to modify hatchfill2() to support boxchart objects, but that would take some work.
I don't have a version new enough to have boxchart(), so I cannot do that directly. Trying to dig through object properties via the forum editor is simply impractical, and for some reason it won't even let me access hidden properties even though I can clearly see that they exist (maybe that's just how R2022b is, idk).
As hatchfill2() handles objects explicitly by their class, it would have to know how to handle an object of class 'boxchart'. Boxchart objects are built of quadrilateral primitives, just like bar objects are, but the names of the descendant quadrilateral objects differ, and there are likely other issues that would need to be addressed.
Thanks for the reply! Indeed I was trying to modify the hatchfill2.m by modifying the file adding the class 'boxchart' and I did arrive until the point to get the quadrilater primitives but then...I was blocked! Moreover I can't understand why the ishghandle(A, 'boxchart') gives me 0 as result... (A = boxchart(_)).
Do you recommend to try to modify the document or manually write a code to build boxplot from the dataset available? Thanks a lot in advance
I'm not sure. Trying to test things in the forum editor is difficult. If something behaves unexpectedly or doesn't work (e.g. property accessibility, ishghandle()), I can't know if that's because:
  • that's the way boxchart() is
  • that's the way things behave in R2022b
  • that's just some quirk of the forum editor
So I can't do much more than guess as to what might work. Fixing hatchfill2() seems like the ideal goal, since it already has a lot of the work done to make it robust and fairly-well generalized. It doesn't seem impossible, but maybe there are more obstacles than expected.
Alternatively, I suppose if you could get the vertex data, you could create patch objects and apply hatchfill2() to those. That seems like a crappy workaround, but maybe that would at least get the job done.
Thanks! I didmodify the hatchfill2.m file and now it works also for boxchart! The only thing is that I had to work around the ishghandle() function that somehow doesn't recognize the 'boxchart type'.
Thanks for help
That's good to hear. If you're feeling confident in your modification, you might consider posting it on the File Exchange. I'm pretty sure hatchfill2() isn't being maintained anymore.
If you're not so confident in it, or just don't want to deal with the FEX, you can always attach a copy in a comment here. At least that way future readers can work from what you've put together.
it seems to have problems with categorical X data.
X = categorical({'Small','Medium','Large','Extra Large'});
X = reordercats(X,{'Small','Medium','Large','Extra Large'});
Y = [10 21 33 52];
Hb=bar(X,Y);
hatchfill2(Hb,'cross','HatchAngle',45)
Error using sum
Invalid data type. First argument must be numeric or logical.

Error in mean (line 127)
y = sum(x, dim, flag) ./ mysize(x,dim);

Error in hatchfill2>transform_data (line 1006)
ref = [mean(xl) mean(yl) mean(zl)];

Error in hatchfill2>computeHatchData (line 420)
[v,T,islog] = transform_data(ax,V(f,:),[]); % transform the face

Error in hatchfill2>newhatch (line 197)
[X,Y,Z] = computeHatchData(handle(ancestor(A,'axes')),V,F,opts);

Error in hatchfill2 (line 108)
H{n} = newhatch(A(n),opts,props);
DGM
DGM on 1 Mar 2023
Edited: DGM on 1 Mar 2023
Yeah, I noticed some of the FEX comments mentioning that.
Does anyone know how to apply hatchfill2 to individual bars within a single bar group? How would I hatch just the green bar in the plot below?
Hb=bar(rand(1,4));
Hb.FaceColor = 'flat';
Hb.CData(2,:)=[0,1,0];
I don't really think there's a way of doing that conveniently. The vertex data used to place the lines describes the vertices of all the bars. They aren't separate at an object level like the CData is
Here are two ideas:
% simple bar setup
y = 0.5*randn(1,4)+2;
hb = bar(y);
% get vertex data for second bar
% the vertex list for N bars is 3x(N*4)
% where rows are [x; y; z]
drawnow
vertdat = hb.Face.VertexData(:,5:8);
% create a patch overlay
% this is required to provide the extents for hatching,
% but it can also be used as a means to change the face color
hp = patch(vertdat(1,:),vertdat(2,:),'g');
% otherwise, you can make the patch invisible
% and set the CData of the bar object as before
%hp.FaceColor = 'none';
%hp.EdgeColor = 'none';
% fill the patch object instead of the bar
hhf = hatchfill2(hp,'cross','HatchAngle',20,'hatchcolor','k');
Alternatively, the bar object can be filled and the relevant lines removed afterward.
% simple bar setup
y = 0.5*randn(1,4)+2;
hb = bar(y);
hb.FaceColor = 'flat';
hb.CData(2,:) = [0 1 0];
% apply the hatching to all bars
hhf = hatchfill2(hb(1),'cross','HatchAngle',20,'hatchcolor','k');
% find the indices of the relevant line segments
idx = find(isnan(hhf.ZData)); % bars are separated by a trailing NaN
idx = idx(1)+1:idx(2)-1; % to pick the second bar
% get rid of unwanted lines
hhf.XData = hhf.XData(idx);
hhf.YData = hhf.YData(idx);
hhf.ZData = hhf.ZData(idx);
... well, at least that works on my computer. I don't know why it doesn't work on the forum editor. It successfully deleted the line vertices, but it's persisting to draw them regardless. Forcing a redraw doesn't do anything. Setting the unwanted vertices to NaN doesn't do anything.
I can't understand why the ishghandle(A, 'boxchart') gives me 0 as result
boxchart() does not return an object of type BoxChart or anything like that. It returns a hggroup object. Which is already being tested for, with the code already recursing to handle the individual objects within the hggroup .
To handle boxchart you would need to either detect the hggroup structure used by boxchart() before testing for hggroup more generally, or else you would need to add handling of Line objects .
I upload the hatchfill2 version with the boxchart option. I hope it might be useful to someone!
Please let me know if it works and if some inprovement can be done!
Please let me know how to get proper legends.
Although I appreciate the effort of the developer, but without legends this is useless for me.
Is it correct that you are trying to use the hatchfille2_BoxChartOpt that @Edoardo_a posted, and you are hoping for legend entries that are hatch filled ?
DGM
DGM on 11 Sep 2023
Edited: DGM on 11 Sep 2023
These are the existing examples for setting up legends with hatchfill2():
And you can get legendflex() here:
Those are for the FEX version of hatchfill2() as applied to a bar chart or patch. I don't know if there are any complications when trying to use those examples with the modifications made for boxchart objects, since I don't have boxchart().
I am not aware of the "hatchfille2_BoxChartOpt". I simply used the code (provided as solution here). Then I tried using legend command, but it doesnot work as intended.
@DGM as suggested, I checked these links. These are fine although a bit complicated for an end user. But I noticed the hatch pattern looks distorted depending on the aspect ratio and scaling of the figure box.
Below are the examples showcasing this issue. Both are generated using same code and but the size of figures/legends have been changed manually. Shoudn't the angles and the spacing be consistent throughout?
Shoudn't the angles and the spacing be consistent throughout?
No. The code assumed a certain size and aspect ratio for the drawing area in the legend. You manually resized the drawing area, violating the assumptions of the code.
The only way to draw the representative sample in the legend area is to have an axes to draw into. line() coordinates are always in data units. If you change the shape of the axes without adjusting the axes XLim and YLim then you are going to distort the relationship between data coordinates and physical coordinates. And if you do adjust XLim and YLim to keep the relationship consistent, then you cannot expect that the lines will extend to full width or height of the new axes size.
legend() has never [deliberately] provided any callback mechanism to provide hooks to redraw the representative sample icons when the legend is resized. It would be possible to set a listener on a resize event for the axes used by legend... but only when the two-output "legacy" version of legend() is used (the newer single-output version does not give access to the drawing area.)
The whole thread illustrates profusely why hatching patterns should have been built into HG2 natively from the start...
Yeah, pretty much.
EDIT: I figured I could make it even more ridiculous by reverting to raster composition. (No I'm not serious)
% say you have any collection of colored plot objects
y = round(0.5*randn(3,5)+2);
hp = bar(y,'stacked');
legend({'meow','trill','purr','hiss','growl'},'location','eastoutside')
title('cat utterances')
xlabel('subject')
% take a raster screenshot
saveas(gcf,'myscreenshot.png')
% load it
inpict = imread('myscreenshot.png');
% these are the colors used in the image
% need the colors in the same class
CT = im2uint8(lines(5)); % there are 5 colors used
% patterns to use for the fills
% these are included with MIMT
pats = {imread('sources/patterns/ospat/OldSchool_A6_165.png');
imread('sources/patterns/ospat/OldSchool_A6_046.png');
imread('sources/patterns/ospat/OldSchool_A6_064.png');
imread('sources/patterns/ospat/OldSchool_A6_089.png');
imread('sources/patterns/ospat/OldSchool_A6_108.png')};
% FG colors
fgct = ccmap('hsyp',5); % MIMT
% BG colors
bgct = zeros(5,3); % all black
% replace the colored regions as specified
szo = size(inpict,1:3);
outpict = inpict;
for k = 1:size(CT,1)
% i'm assuming we can get an exact mask
% i.e. that there's no antialiasing or JPG crust
% i make this assumption on the basis that this example
% is ridiculous anyway
regionmk = all(inpict == permute(CT(k,:),[1 3 2]),3);
% create a pattern fill
patmask = ptile(im2gray(pats{k}),szo); % MIMT
patfill = replacepixels(fgct(k,:),bgct(k,:),patmask); % MIMT
% fill the selected region
outpict = replacepixels(patfill,outpict,regionmk); % MIMT
end
imwrite(outpict,'sillyhatch.png')
It's fun to use these old pattern fills again, but nobody would work at an appropriately low resolution for them to render well. I suppose the same method would work with larger pattern tiles, but it loses the charm.
... but maybe there's no accounting for taste.
As an aside, I think a lot of people regard hatch fills as antiquated, or maybe just cluttered. I see hatch fills mostly as a practical compromise for printability, and I think that's still a justifiable solution 30 years later. I assume the biggest factor against the need for hatch fills would be simply that fewer things get physically printed, but fewer isn't none. I suppose catering to colorblindness accessibility is probably more of a concern today than it was in the past, and hatch fills can have some utility there too.
" I think a lot of people regard hatch fills as antiquated..."
Apparently Mathworks is among them... :(
At the time, I was working where needed black/white presentation materials; color gradations eventually wash out into indistinguishable shades. I don't know that has completely gone away, either.

Sign in to comment.

More Answers (0)

Products

Release

R2020a

Asked:

on 13 Mar 2022

Commented:

dpb
on 1 Aug 2024

Community Treasure Hunt

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

Start Hunting!