Skip to main content

graphics - Filling a polygon with a pattern of insets


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.



Answer



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.


Comments

Popular posts from this blog

plotting - How to draw lines between specified dots on ListPlot?

I would like to create a plot where I have unconnected dots and some connected. So far, I have figured out how to draw the dots. My code is the following: ListPlot[{{1, 1}, {2, 2}, {3, 3}, {4, 4}, {1, 4}, {2, 5}, {3, 6}, {4, 7}, {1, 7}, {2, 8}, {3, 9}, {4, 10}, {1, 10}, {2, 11}, {3, 12}, {4,13}, {2.5, 7}}, Ticks -> {{1, 2, 3, 4}, None}, AxesStyle -> Thin, TicksStyle -> Directive[Black, Bold, 12], Mesh -> Full] I have thought using ListLinePlot command, but I don't know how to specify to the command to draw only selected lines between the dots. Do have any suggestions/hints on how to do that? Thank you. Answer One possibility would be to use Epilog with Line : ListPlot[ {{1, 1}, {2, 2}, {3, 3}, {4, 4}, {1, 4}, {2, 5}, {3, 6}, {4, 7}, {1, 7}, {2, 8}, {3, 9}, {4, 10}, {1, 10}, {2, 11}, {3, 12}, {4, 13}, {2.5, 7}}, Ticks -> {{1, 2, 3, 4}, None}, AxesStyle -> Thin, TicksStyle -> Directive[Black, Bold, 12], Mesh -> Full, Epilog -> { Line[ ...

equation solving - Invert and fit implicitly defined curve

I need to fit an implicitly defined curve. I thought I could get some data out of Solve , and then using FindFit . Therefore, I would like to find the relation the parametric curve defined by $F(x,y)=0$: Solve[-(1/2) + 1/2 (0.41202 BesselK[0, 0.1 Sqrt[x^2 + y^2]] + (0.101483 x BesselK[1, 0.1 Sqrt[x^2 + y^2]])/Sqrt[x^2 + y^2]) == 0, y] But I can't get an output: Solve was unable to solve the system with inexact coefficients or the system obtained by direct rationalization of inexact numbers present in the system. Since many of the methods used by Solve require exact input, providing Solve with an exact version of the system may help. >> Edit: In particular, I would like to fit the data coming from the curve with the expression of another curve, and not with a function $f(x)$. In particular, since this clearly looks like a cardioid , I would like it to fit to something like it. What other strategies could I try?

dynamic - How can I make a clickable ArrayPlot that returns input?

I would like to create a dynamic ArrayPlot so that the rectangles, when clicked, provide the input. Can I use ArrayPlot for this? Or is there something else I should have to use? Answer ArrayPlot is much more than just a simple array like Grid : it represents a ranged 2D dataset, and its visualization can be finetuned by options like DataReversed and DataRange . These features make it quite complicated to reproduce the same layout and order with Grid . Here I offer AnnotatedArrayPlot which comes in handy when your dataset is more than just a flat 2D array. The dynamic interface allows highlighting individual cells and possibly interacting with them. AnnotatedArrayPlot works the same way as ArrayPlot and accepts the same options plus Enabled , HighlightCoordinates , HighlightStyle and HighlightElementFunction . data = {{Missing["HasSomeMoreData"], GrayLevel[ 1], {RGBColor[0, 1, 1], RGBColor[0, 0, 1], GrayLevel[1]}, RGBColor[0, 1, 0]}, {GrayLevel[0], GrayLevel...