22
$\begingroup$

How to create custom Graphics primitive?

It should have the following properties, resembling properties of built-in geometric figures, like Circle etc:

  1. Has constant head, say RoundedRectangle. It should not evaluate to the list of lines, so as normal Rectangle.

  2. Can serve as a subject for GeometricTransformation

$\endgroup$
10
  • 2
    $\begingroup$ Rectangle is a function turning some parameters into a graphics object. Such a function for rounded rectangles can be found in this post: mathematica.stackexchange.com/questions/1882/… $\endgroup$
    – C. E.
    Jun 18, 2013 at 8:19
  • $\begingroup$ Suzan, I don't understand what you are looking for in this question. Can you be a bit more explicit about what you are looking for? $\endgroup$
    – bill s
    Jun 18, 2013 at 10:05
  • $\begingroup$ Are you asking how to define an object that Mathematica will treat as a graphic primitive such as Disk or Circle? $\endgroup$
    – m_goldberg
    Jun 18, 2013 at 10:14
  • 1
    $\begingroup$ You might want to look into the old packages Graphics`Arrow` or Graphics`Spline` to see how they implemented primitives that were once not built-in. $\endgroup$ Jun 18, 2013 at 10:49
  • 1
    $\begingroup$ It seems that you are under a common misconception. As far as the kernel is concerned, Graphics is inert, i.e. it has no DownValues. It is only in the formatting end of things that it is turned into an image. The same applies to the primitives, like Rectangle, but they are only formatted when found within a Graphics(3D) object. So, Rectangle remains a Rectangle and GeometricTransformation can be applied to it. $\endgroup$
    – rcollyer
    Jun 18, 2013 at 12:31

2 Answers 2

28
$\begingroup$

Try this:

SetAttributes[createPrimitive, HoldAll]

createPrimitive[patt_, expr_] := 
 Typeset`MakeBoxes[p : patt, fmt_, Graphics] := 
  Typeset`MakeBoxes[Interpretation[expr, p], fmt, Graphics]

Example:

createPrimitive[face[x_: 0.1],
 {Circle[{0, 0}, 1], Circle[{-0.3, 0.5}, x],
  Circle[{0.3, 0.5}, x], Line[{{-0.4, -0.2}, {0.4, -0.2}}]}]

It works as expected in Graphics:

g = Graphics[face[]]

enter image description here

face has no DownValues so it remains as face in InputForm:

InputForm[g]
(*  Graphics[face[]]  *)

enter image description here

(*  Graphics[face[], ImageSize -> {63., Automatic}]  *)

It works with GeometricTransformation:

Graphics[GeometricTransformation[face[0.2], ShearingTransform[Pi/4, {1, 0}, {0, 1}]]]

enter image description here

A note about colours

A commenter asked "How would you rewrite this function to color the different components differently?" The answer is that colours can be used in the definition of the custom primitive just as in any other graphics expression, but note that the expression that goes into Typeset`MakeBoxes must be something that the Front End understands, e.g. RGBColor[1,0,0] rather than Red. If you want to use named colours like Red you will need to let the kernel evaluate the expression to convert them to RGBColor directives.

So for example you could:

Manually specify the colours as RGBColor directives:

createPrimitive[myprim[x_], {RGBColor[1, 0, 0], Circle[{0, 0}, x]}]

Use named colours and override the hold attribute with Evaluate:

createPrimitive[myprim[x_], Evaluate @ {Red, Circle[{0, 0}, x]}]

Or just remove the hold attribute completely:

ClearAttributes[createPrimitive, HoldAll];
createPrimitive[myprim[x_], {Red, Circle[{0, 0}, x]}]

In the last two cases you should guard against x already having a value, e.g. with Block or by using \[FormalX] instead of x

$\endgroup$
4
  • 2
    $\begingroup$ +1. What does Typeset`MakeBoxes do exactly? (lazy to start a spelunking battle) $\endgroup$
    – Rojo
    Dec 13, 2013 at 18:56
  • 2
    $\begingroup$ @Rojo, I don't understand the details, but it's used internally by MakeBoxes to convert graphics primitives to boxes. I don't think it's possible to give definitions for creating graphics boxes directly with MakeBoxes, you have to use Typeset`MakeBoxes instead. $\endgroup$ Dec 13, 2013 at 19:17
  • $\begingroup$ That's nice, thanks $\endgroup$
    – Rojo
    Dec 13, 2013 at 22:19
  • $\begingroup$ I don't quite get the whole MakeBoxes yet, but it is beautiful. $\endgroup$ Aug 21, 2020 at 13:48
2
$\begingroup$

I don't see how to do what you are asking for without modifying the built-in definition of Graphics, something I would be extremely reluctant to do. In my own work, the closest I have come to meeting your requirements is to define functions that define shapes by returning lists consisting of graphics directives and primitives. This has worked well enough to satisfy me so far.

Here is an example.

dashedPoly[pts : {{_, _} ..}, 
           fill : (_RGBColor | _GrayLevel | _Hue) : Transparent] :=
    {fill, EdgeForm[Dashing[Small]], Polygon[pts]}

And here are two examples of the function in use.

Module[{A, B, C},
  A = {0., 0.}; B = {0., 3.}; C = {4., 0.};
  Graphics[dashedPoly[{A, B, C}]]]

triangle1.png

With[{blue = ColorData["HTML", "DeepSkyBlue"]}, 
  Module[{A, B, C},
    A = {0., 0.}; B = {0., 3.}; C = {4., 0.};
    Graphics[dashedPoly[{A, B, C}, blue]]]]

triangle2.png

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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