Take the 2-minute tour ×
Mathematica Stack Exchange is a question and answer site for users of Mathematica. It's 100% free, no registration required.

I am trying to fill a shape with diagonal lines. I am aware of Texture, but it rasterizes the fill pattern, which is not desirable. Here was my crack at it:

Graphics[{FaceForm[White], EdgeForm[Black], 
  Polygon[{{0, 0}, {1, 0}, {1, 1}, {0.5, 1.5}, {0, 1}}], 
  Sequence@Table[
    Inset[Graphics[Line[{{0, 0}, {1, 1}}]], {0, 0.1 l}, {0, 0}, 
     Scaled[{1, 1}]], {l, -10, 10}]}]

Which produces the following:

enter image description here

Ideally, I would want to just clip the insets such that they do not appear outside of the shape, rather than having to calculate the intersection between the lines and the polygon (for which I have shown a relatively simple example, in principle I'd like this to work for more complicated shapes such as FilledCurve). Note that there is a very large whitespace around the figure as well, which only appears when I include the insets.

share|improve this question
    
You can use AbsoluteOptions to obtain the PlotRange of interest of the polygon (without the insets). Then, when you plot the polygon plus insets, you can use the same PlotRange. –  David Carraher Mar 30 '12 at 20:05
    
Just to augment the answer by @Yu-Sung (which seems to do what you want): you can use PlotRange with Graphics, and that allows you to get rid of the white space. Maybe that's what @David already hinted at above... so you could add e.g. PlotRange -> {{-.1, 1.5}, {-.1, 1.5}}. –  Jens Mar 30 '12 at 20:56
    
@Jens, Yes, that's what I was referring to. Using AbsoluteOptions[g, PlotRange] you can obtain the needed PlotRange. (My answer illustrates how this is used.) –  David Carraher Mar 30 '12 at 21:48
    
@David yes, I shouldn't have doubted you. Just wanted t make sure. Another observation about @Yu-Sung's answer: it could be simplified so that it doesn't require Inset. Then the white-space problem would likely be fixed too. –  Jens Mar 30 '12 at 22:15
    
@Jens I don't yet fully understand Yu-Sung's answers. Not sure why Inset was used. –  David Carraher Mar 30 '12 at 22:45
show 3 more comments

5 Answers 5

up vote 31 down vote accepted

The following solution is hacky work-around to overcome the lack of proper pattern directives. I don't like it 100%, but still it is usable.

Solution

The idea is simple. If you see FilledCurve documentation, it supports an object with holes:

hole

So what if you put a huge enough rectangle as an outer boundary? Then, your polygon becomes a hole and you can see through stuff behind it.

Now, few issues:

  1. How big the boundary should be? Ideally, it should be always outside of the screen. Thankfully, it is possible by using ImageScaled coordinates, which is supported by all 2D primitives.

  2. Now your background color of the screen should be the face color of the outer polygon.

  3. The hatch (pattern) should come first so that the outer polygon would be drawn on top of it.

This is the result.

Graphics[{
  (* The hatch comes first *)
  Sequence@
   Table[Inset[Graphics[Line[{{0, 0}, {1, 1}}]], {0, 0.1 l}, {0, 0}, 
     Scaled[{1, 1}]], {l, -10, 10}],

  (* FaceForm color should be your "background" color: usually white *)
  FaceForm[White], EdgeForm[Black],

  (* FilledCurve syntax is essentially the same as Polygon if used with Line *)
  FilledCurve[{
    (* Large outer boundary. Its edges are always outside of your screen *)
    {
       Line[{ImageScaled[{-2, -2}],ImageScaled[{2, -2}],
         ImageScaled[{2, 2}],ImageScaled[{-2, 2}]}]
    },
    (* Your original polygon *)
    {
       Line[{{0, 0}, {1, 0}, {1, 1}, {0.5, 1.5}, {0, 1}}]
    }
  }]
}]

Here is the result:

result

Generalization

The following example uses two different types of patterns (an image and a graphic), and locate them in two different places.

Graphics[{
  (* Pattern for the first object *)
  Inset[ExampleData[{"ColorTexture", "MultiSpiralsPattern"}],
   {-.25, -.25}, {Left, Bottom}, {1.5, 1.5}],
  (* Pattern for the second object *)
  Inset[Graphics[Table[Circle[{i, j}, .25], {i, 10}, {j, 15}]],
   {1.5, 0}, {Left, Bottom}, {1, 1.5}],

  FaceForm[White], EdgeForm[Black],
  FilledCurve[{
    (* Outer polygon *)
    {Line[{ImageScaled[{-2, 2}], ImageScaled[{2, -2}], 
       ImageScaled[{2, 2}], ImageScaled[{-2, 2}]}]},
    (* The first object *)
    {Line[Table[(.5 + .25 (-1)^t) {Cos[Pi t/5], 
       Sin[Pi t/5]} + {.5, .5}, {t, 0, 9}]]},
    (* The second object *)
    {Line[{{1.5, 0}, {2.5, 0}, {2.5, 1}, {2, 1.5}, {1.5, 1}}]}
    }]
  }]

Here is the result:

result2

Pros & Cons

The benefits of this approach is:

  1. You need to convert your polygon to inequalities to use RegionPlot or any other solutions using Plot with RegionFunction which is not always possible (such as country polygons).

  2. Anything can be a pattern as long as it is behind the polygon.

  3. The syntax is straightforward, as long as you stays in Polygon or even better FilledCurve. You just have to add one extra component.

The problems:

  1. It is tricky to handle many objects with different patterns, especially if your object contains a concave part.

  2. Circle support is hard. In fact, circles can be described as B-splines (See the first example of Applications section), but combining it with the rest of FilledCurve can be messy.


In fact, if you are familiar with Mac OS's graphics API (Quartz or Cocoa), this is exactly how they deal with filled polygons with patterns (link). It would have been a nice addition to Mathematica's graphics if it were built-in.

As a side note, FilledCurve syntax is quite complex, but if you want nice graphics, it is well-worth learning. It follows the concept of path in many other graphics languages (Postscript, SVG, or system APIs) and conversion to it is usually straightforward.

share|improve this answer
    
This is the answer I was looking for. The only minor complaint is that there still appears to be a large whitespace around the shape, I'm guessing its because there's something screwy going on with the Inset coordinates... –  Guillochon Mar 30 '12 at 19:43
1  
Great idea! I do see one drawback: all objects that you want to hatch must have the same hatching pattern. –  Sjoerd C. de Vries Mar 30 '12 at 19:44
1  
@Guillochon, yes it is due to your hatching patterns coordinate interfering with plot range computation. Could be a bug, although I think not. Usually when you have Scaled coordinates with PlotRange->All, FE is falling into chicken-egg trouble: To compute Scaled you need to know plot range. But to compute plot range, you need to know the boundaries of objects in the scene... So sometimes it does a guesstimation. –  Yu-Sung Chang Mar 30 '12 at 20:10
    
@SjoerdC.deVries, Yes, that's why I said that I don't like it 100%. You can do different patterns by carefully laying down patterns around, but it gets quite nasty / impossible, if your object has concave parts. System API doesn't have that trouble because it draws each filled path on different buffer, then combine later. –  Yu-Sung Chang Mar 30 '12 at 20:12
add comment

ragfield's solution is a good one that can easily be extended to arbitrarily complicated polygons if we create an inPolyQ function using winding numbers.

Here is a complicated polygon (the points only) from Mathematica's example data.

poly = Rescale[ExampleData[{"Statistics", "WesternUgandaBorder"}]];

This function determines whether a point is in the polygon.

inPolyQ = 
  Compile[{{polygon, _Real, 2}, {x, _Real}, {y, _Real}}, 
   Block[{polySides = Length[polygon], X = polygon[[All, 1]], 
     Y = polygon[[All, 2]], Xi, Yi, Yip1, wn = 0, i = 1}, 
    While[i < polySides, Yi = Y[[i]]; Yip1 = Y[[i + 1]]; 
     If[Yi <= y, 
      If[Yip1 > y, Xi = X[[i]]; 
        If[(X[[i + 1]] - Xi) (y - Yi) - (x - Xi) (Yip1 - Yi) > 0, 
         wn++;];];, 
      If[Yip1 <= y, Xi = X[[i]]; 
        If[(X[[i + 1]] - Xi) (y - Yi) - (x - Xi) (Yip1 - Yi) < 0, 
         wn--;];];]; i++]; ! wn == 0]];

We can use inPolyQ to generate our region for RegionPlot.

RegionPlot[inPolyQ[poly, x, y], {x, 0, .7}, {y, 0, 1}, Mesh -> 20,  
   MeshFunctions -> {#1 - #2 &}, MeshShading -> {None}, PlotPoints -> 25]

enter image description here

Edit:

As a proof of concept that this can be used for other sorts of textures..

text = ExampleData[{"ColorTexture", "MultiSpiralsPattern"}];

RegionPlot[inPolyQ[poly, x, y], {x, 0, .7}, {y, 0, 1.1}, 
 PlotStyle -> Texture[text], PlotPoints -> 50, Frame -> None]

enter image description here

share|improve this answer
add comment

Mathematica's graphics language lacks a clipping primitive. However, you can sometimes simulate the same appearance using the sophisticated visualization functions.

RegionPlot[((0 <= x <= .5) && (0 <= y <= 1 + x)) || ((.5 < x <= 1) && 
    0 <= y <= 2 - x), {x, -1, 2}, {y, -1, 2}, Mesh -> 20, 
 MeshFunctions -> {#1 - #2 &}, MeshShading -> {None}]

enter image description here

share|improve this answer
    
Can you supply complicated shapes such as with FilledCurve to RegionPlot? Seemed like that was what the OP had in mind... –  rm -rf Mar 30 '12 at 19:31
2  
@R.M You can do that with my PointinPoly function used in this answer. –  Sjoerd C. de Vries Mar 30 '12 at 19:56
add comment

The FilledCurve approach in the accepted answer by @Yu-Sung Chang is very interesting, so I tried to make my own version of it. I defined the whole thing as a function that accepts graphics directives for the objects to be masked (backgroundDirectives - e.g., the fill pattern or image), and an arbitrary polygon as the mask. This includes Polygon with several lists as arguments, specifying a group of separate polygons which then appear as separate holes. The color of the masking foreground is specified in maskDirectives.

mask[backgroundDirectives_, p_Polygon, maskDirectives_] := 
 Module[{pts = p[[1]]},
  {backgroundDirectives,
   FaceForm[maskDirectives], EdgeForm[],
   FilledCurve[
    Prepend[Map[List, Flatten[Map[Line, {pts}, {-3}]]], {
      Line[
       {ImageScaled[{0, 0}], ImageScaled[{1, 0}], ImageScaled[{1, 1}],
         ImageScaled[{0, 1}]}
       ]
      }
     ]
    ]
   }
  ]
mask1 = With[{polygon = 
     Polygon[{{{0, 0}, {1, 0}, {1, 1}, {0.5, 1.5}, {0, 1}}, 
       Most@Table[.3 {Cos[a], Sin[a]} + {-.3, .4}, {a, 0, 
          2 Pi, Pi/18}]}],
    plot = 
     DensityPlot[Cos[(x - y)/2], {x, -10, 10}, {y, -20, 20}, 
      PlotPoints -> 60,
      Frame -> None,
      ImageSize -> 360]
    },
   {
    mask[Inset[Show[plot, AspectRatio -> Automatic]], polygon, Cyan],
    EdgeForm[Directive[Thick, Dashed, Blue]], FaceForm[],
    polygon
    }
   ];
Graphics[mask1]

Density plot with mask applied

The image above shows how a background DensityPlot is masked by an area with two holes. Since I have separated the masking process as a function, it can be applied repeatedly, as I'm doing here with the previous output. Note that the output mask1 didn't have head Graphics so it doesn't need to be inserted as an Inset:

With[{polygon = Polygon[
    Most@Table[2.2 {Cos[a], Sin[a]}, {a, 0, 2 Pi, Pi/2}]]
  },
 Graphics[{
   mask[mask1, polygon, Orange],
   EdgeForm[Directive[Thin, Red]], FaceForm[],
   polygon
   }
  ]
 ]

Another mask applied to masked image

By having the mask function operate on graphics objects, we only have to use Inset when calling mask with an image or Graphics in the background (as I did in the first graphic). We could also still use Inset to control the size of the background, as e.g. with Inset[Graphics[mask1]] in the last plot.

The second plot shows that the original background (the DensityPlot) is scaled to the final image dimensions by default, so the "stripes" get thicker relative to the masking polygons.

What you also see in the examples is that the mask doesn't draw the masking polygon(s) themselves; it only uses their shapes. That way you can decide on your own whether and how to draw the outline of the mask, e.g. as dashes as I did above.

Edit

The relative size of the mask and the other graphics it reveals depends on the settings for ImageSize. For example, the plots I posted will change if the ImageSize option in plot (contained in mask1) is changed, or if the output is manually resized.

share|improve this answer
add comment

Paint Poly with background colored stripes

Just for fun, I painted the lines in the background color. The unnecessary white space is avoided by using AbsoluteOptions[g,PlotRange] from the polygon when graphed alone.

poly[n, color] makes a (solid colored) polygon with n vertices.

s[p, bc] puts stripes of a background color bc on polygon p. pr is the PlotRange for the solid polygon (left pane, below). This same PlotRange is used in the center and right panes. It crops the picture to the polygon.

poly[n_,color_:Blue]:=Graphics[{FaceForm[color],Polygon[RandomReal[1,{n,2}]]}]

s[p_,bc_:White]:= Module[{pr=AbsoluteOptions[p,PlotRange],pts,xMin,xMax,yMin,yMax},
  xMin=pr[[1,2,1,1]]; xMax=pr[[1,2,1,2]];
  yMin=pr[[1,2,2,1]]; yMax=pr[[1,2,2,2]];
  pts=Append[pList=InputForm[p][[1,1,2,1]],pList[[1]]];
  Show[p,
       Plot[Table[x+k,{k,yMin-xMax,xMin+yMax,.1}],{x,xMin,xMax},
           Axes-> None,PlotStyle->Directive[bc,Thickness[.06]]],
       Graphics[Line[pts]],pr[[1]],PlotRangePadding->Scaled[ .01]]]

Now lets look at three outputs. The middle pane may appear to have blue stripes. But the stripes are actually white. The right pane shows the full extent of the drawn stripes (now in pink).

GraphicsGrid[{{p1 = poly[7], s[p1], s[p1, Pink]}}, Frame -> All]

three polys

Left: polygon Center: blue polygon painted with white stripes Right: blue polygon painted with pink stripes

Background is white in all 3 cases.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.