## Generating hatched filling using Region functionality

22

11

Before v.10 came out there were several Q&A on generating hatched filling for Plot, ListPlot etc.

In v.10 we have new Region functionality and I wonder: Does it allow a straightforward way to produce vector hatched filling for arbitrary 2D Polygon?

Here is my first attempt to use Region functionality which produces ugly result in extremely inefficient way:

GraphicsMeshMeshInit[];
blob = PolygonData["Blob", "Polygon"];
Show[DiscretizeRegion[
RegionIntersection[
RegionUnion @@
Table[InfiniteLine[{-3, y}, {1, 1}], {y, -7, 2, .2}],
blob], {{-3, 3}, {-3, 3}}], Prolog -> blob,
PlotRange -> {{-3, 3}, {-3, 3}}, Frame -> True]


Is it a good idea to use Region for such purposes? Can anyone suggest an efficient solution?

P.S. I think that raster texture is not appropriate for hatching filling because it is not scalable. The goal is to have vector hatching.

23

Update 2: Finally ... in version 12.1 you can use the new directives HatchFilling and PatternFilling:

 Graphics[{EdgeForm[{Thick, Black}], #, blob}, ImageSize -> 300] & /@
{HatchFilling[], Directive[Red, HatchFilling[Pi/2, 2, 10]]} // Row


Graphics[{EdgeForm[{Thick, Black}], PatternFilling[#, ImageScaled[1/20]], blob},
ImageSize -> 300] & /@ {"Diamond", "XGrid"} // Row


Update: Using MeshFunctions and Mesh in RegionPlot:

RegionPlot[Evaluate[RegionRegionProperty[Rationalize /@ blob, {x, y},
"FastDescription"][[1, 2]]], {x, -3, 3}, {y, -3, 3}, Mesh -> 50,
MeshFunctions -> {#1 + #2 &, #1 - #2 &}, MeshStyle -> White,
PlotStyle -> Directive[{Thick, Blue}]]


With settings MeshStyle -> GrayLevel[.3], PlotStyle -> Directive[{Thick, LightBlue}]

With settings Mesh -> {40, 20}, MeshFunctions -> {# #2 &, Norm[{#, #2}] &}, MeshStyle -> White, MeshShading -> Dynamic@{{Hue@RandomReal[], Hue@RandomReal[]}, {Hue@RandomReal[], Hue@RandomReal[]}}, we get

Update 2: Mesh specifications

rpF = RegionPlot[
Evaluate[RegionRegionProperty[Rationalize /@ blob, {x, y},
"FastDescription"][[1, 2]]], {x, -3, 3}, {y, -3, 3}, Mesh -> #,
MeshFunctions -> {#1 + #2 &, #1 - #2 &},
MeshStyle -> GrayLevel[.3],
PlotStyle -> Directive[{Thick, LightBlue}]] &;

rp1 = rpF@{20, 75};
rp2 = rpF@{List /@ {-5, -4, -2.5, -2., -1.9, -1.8, -1.7, -1., -.5, Sequence @@ Range[0, 5, .2]},
List /@ {Sequence @@ Range[-5., -1, .3], Sequence @@ Range[-1., 1, .1], 1.5, 2., 2.5, 3.}};
rp3 = rpF@RandomReal[{-5, 5}, {2, 50, 1}];
rp4 = rpF@{Transpose[{RandomReal[{-5, 5}, 25], Table[Hue[RandomReal[]], {25}]}],
Transpose[{RandomReal[{-5, 5}, 50], Table[Directive[{Thick, Hue[RandomReal[]]}], {50}]}]};

Grid[{{rp1, rp2}, {rp3, rp4}}]


Change the MeshFunctions specification to

MeshFunctions -> {#1 &, #2 &}


to get

Use the option

MeshShading -> Dynamic@{{Hue@RandomReal[], Hue@RandomReal[]},
{Hue@RandomReal[], Hue@RandomReal[]}}


to get

Original version:

GraphicsMeshMeshInit[];
blob = PolygonData["Blob", "Polygon"];

RegionPlot[Evaluate[RegionRegionProperty[Rationalize /@ blob, {x, y},
"FastDescription"][[1, 2]]], {x, -3, 3}, {y, -3, 3}, PlotStyle -> texturea]


RegionPlot[Evaluate[RegionRegionProperty[Rationalize /@ blob, {x, y},
"FastDescription"][[1, 2]]], {x, -3, 3}, {y, -3, 3}, PlotStyle -> textureb]


where hatched textures texturea and textureb

texturea = Texture[Rasterize@hatchingF["cross", {{1, 1}, {1, 1}}, 100]]


textureb = Texture@Rasterize@hatchingF["cross", {{1, 1}, {1, 1}}, 100,
Dynamic@Directive[{Thick, Hue[RandomReal[]]}]]


are obtained using the function

ClearAll[hatchingF];
hatchingF[dir : ("single" | "cross") : "single",
slope : ({{_, _} ..}) : {{1, 1}}, mesh_Integer: 100,
style_: GrayLevel[.5], pltstyle_: None, opts : OptionsPattern[]] :=
Module[{meshf = Switch[dir, "single", {slope[[1, 1]] #1 + slope[[1, -1]] #2 &},
"cross", {slope[[1, 1]] #1 - slope[[1, -1]] #2 &,
slope[[-1, 1]] #1 + slope[[-1, -1]] #2 &}]},
ParametricPlot[{x, y}, {x, 0, 1}, {y, 0, 1}, Mesh -> mesh,
MeshFunctions -> meshf, MeshStyle -> style, BoundaryStyle -> None,
Axes -> False, PlotStyle -> pltstyle]]


More examples:

hatchingF["cross", {{1, 0}, {0, 1}}, 50, Red]


hatchingF["single", {{1, 1}, {0, 1}}, 50, Directive[{Thick,Green}]]


texture2 = Texture[Rasterize@ hatchingF["cross", {{1, 1}, {1, 1}}, 50, Directive[{Thick, Red}]]];
Plot3D[Sin[x y], {x, 0, 3}, {y, 0, 3}, PlotStyle -> texture2, Mesh -> None, Lighting -> "Neutral"]


I need vector hatching. – Alexey Popkov – 2014-10-27T02:18:48.500

@Alexey, would something like Texture[ImportString[ ExportString[hatchingF["cross", {{1, 1}, {1, 1}}, 100], "PNG"]]] work? – kglr – 2014-10-27T02:30:26.857

Texture works only with raster fills. I think that raster hatching is not appropriate for generating publication quality graphics. – Alexey Popkov – 2014-10-27T02:33:43.163

@Alexey, so Texture is out; that still leaves Mesh/MeshFunctions/MeshShading? :) – kglr – 2014-10-27T02:50:19.530

Yes, MeshFunctions is a solution. There is an extremely simple way to optimize the mesh: % /. Line[{{f_, __}, __, {__, l_}}] :> Line[{f, l}]. The only drawback is that Mesh does not allow to specify the distance between the hatches, only the number of hatches. Is there a way to generate completely customizable hatching? – Alexey Popkov – 2014-10-27T03:14:04.953

@Alexey, please see the update re what can be done with various Mesh specs. – kglr – 2014-10-27T08:05:47.240

8

Here is a solution which combines kguler's and MichaelE2's approaches:

ParametricPlot[{x, y}, {x, y} ∈ blob, Mesh -> 20,
MeshFunctions -> {#1 - #2 &}, MeshStyle -> Black,
BoundaryStyle -> Black, PlotStyle -> None, Axes -> False]


Note however that the syntax form ParametricPlot[{x, y}, {x, y} ∈ region] seems to be undocumented.

It is worth to mention that in a usual situation when only the hatching is needed there is straightforward way to optimize it by joining the adjacent line segments:

simplifyHatches = # /. Line[{f_Integer, __, l_Integer}] :> Line[{f, l}] &;

ParametricPlot[{x, y}, {x, y} ∈ blob, Mesh -> 20,
MeshFunctions -> {#1 - #2 &}, MeshStyle -> Black,
BoundaryStyle -> None, PlotStyle -> None,
Axes -> False] // simplifyHatches


7

Edit 2: Updated with a non-convex polygon

reg = With[{pts = RandomReal[{-3, 3}, {15, 2}]},
Polygon@SortBy[pts, Apply[ArcTan, # - Mean[pts]] &]];


You could make a texture and use RegionPlot:

RegionPlot[
reg,
PlotStyle -> Texture[ExampleData[{"ColorTexture", "MultiSpiralsPattern"}]]]


Update

Vector graphics through ContourShading:

ClearAll[f];
f[x_, y_] := x - y;
ContourPlot[f[x, y], {x, y} ∈ reg,
Contours -> 20, ContourShading -> {Blue, LightRed},
ContourStyle -> None]


A self-intersecting polygon:

reg = Polygon[RandomReal[{-3, 3}, {15, 2}]];
ContourPlot[f[x, y], {x, y} ∈ reg,,
Contours -> Flatten[Table[{c, c + 0.05}, {c, -6, 6, 0.3}]],
ContourShading -> {Blue, LightRed}, ContourStyle -> None]]


The goal is to have vector hatching, not raster. – Alexey Popkov – 2014-10-27T02:13:43.403

@AlexeyPopkov Perhaps that should be specified in the Q? – Michael E2 – 2014-10-27T02:29:23.563

I have updated the Q. – Alexey Popkov – 2014-10-27T02:31:50.507

It seems that you have accidentally deleted your ContourPlot-based solution. – Alexey Popkov – 2014-10-27T07:58:02.247

@AlexeyPopkov I was going to think about it, but I seem to have really deleted it. It was late and I couldn't figure out the difference between hatching and stripes. Also, I was running out of energy to address the main question of how to use the new Region functionality to make hatching. The answers so far don't really use it in the way you were trying. – Michael E2 – 2014-10-27T10:42:29.357

@AlexeyPopkov Another thing I was concerned about is that Lines are different than Polygons. This difference can be significant sometimes when exported to a vector graphics format. ContourShading creates only polygons, which behave better than a mixture. Is that a concern for you? (Using Lines for the hatching would be easier.) – Michael E2 – 2014-10-27T10:51:15.183

I expected hatches to be Lines, not stripes. This is also illustrated in the question: introduction of InfiniteLine and RegionIntersection give me a hope that now may be possible a straightforward way to get explicit hatching. My original idea was to generate a set of InfiniteLines and then take an intersection with the polygon. I noticed that Reduce solves this problem rather quickly. So may be efficient solution should go through Reduce. – Alexey Popkov – 2014-10-27T11:06:26.167

I should also notice that I have intentionally chosen non-convex and rather complicated "Blob" as an example of a polygon. Your new elegant {x, y} ∈ reg solution is surprisingly efficient as compared to the old RegionFunction -> Function[{x, y}, {x, y} ∈ reg] approach. – Alexey Popkov – 2014-10-27T11:38:43.670

Is the form ContourPlot[f[x, y], {x, y} ∈ reg] documented? I just have checked with the blob and found that one of the hatches (bottom right) is absent: ContourPlot[x-y,{x,y}∈blob,Contours->20,PlotRange->All,ContourShading->None,ContourStyle->Black,BoundaryStyle->Black].

– Alexey Popkov – 2014-10-27T12:59:57.973

@AlexeyPopkov It might be a bug. There are 20 contours, which suggests the subdivision of the range of f[x, y] is incorrect. Contours -> 70 is much worse. Disappointing. The domain specification {x, y} ∈ reg is not documented per se for plotting functions, but the guide "guide/GeometricSolvers" suggests that region functionality has been incorporated. However, things like ContourPlot[Norm[p], p ∈ reg] don't work, but they do in some other functions. (My difficulty with lines was that in math/M a line is 1D, while on paper it's just a thin stripe. I'd prefer stripes.) – Michael E2 – 2014-10-27T13:38:10.347

It seems that ParametricPlot[{x,y},{x,y}∈blob,Mesh->20,MeshFunctions->{#1-#2&},MeshStyle->Black,BoundaryStyle->Black,PlotStyle->None,Axes->False] works fine. This solution can be considered as consolidation of your's and @kguler's ideas. – Alexey Popkov – 2014-10-27T14:23:42.850