26

For various reasons related to workflow associated with creating figures for journals, I am creating functions that will accept graphics primitives and a set of options and spit out a Graphics object with those primitives and those options in some configuration.

(Of course, this sounds like all I'm doing is re-creating Graphics, in that I can call

Graphics[ <List of primitives>, <Sequence of options>]

but it's more complicated than that.)

In any case, I would like to be able to check whether the inputs to the function are graphics primitives or not. We can create our own GraphicsPrimitiveQ if we have a list of the Heads of graphics primitives. For instance,

Clear[GraphicsPrimitiveQ, listOfHeads]
listOfHeads = {Line, Polygon, Point, Arrow, Tube};
GraphicsPrimitiveQ[x_] := MatchQ[Head[x], Alternatives @@ listOfHeads]

in which case if we define

f[x__?GraphicsPrimitiveQ] := {x}

then

f[Point[{0, 0}], Line[{{1, 1}, {0, 0}}]]
(* {Point[{0, 0}], Line[{{1, 1}, {0, 0}}} *)

and

f[Point[{0, 0}], 1]
(* f[Point[{0, 0}], 1] *)
  • So: Is there something like a GraphicsPrimitiveQ?

  • Or: Is there hidden somewhere in Mathematica an easy way to get a list of all graphics primitives?

  • Alternatively, if there's no built-in ListOfGraphicsPrimitives[], then Is there a programmatic way of making the function GraphicsPrimitiveQ? (Added this because I for some reason didn't anticipate the clever answers below.)

J. M.'s missing motivation
  • 124,525
  • 11
  • 401
  • 574
march
  • 23,399
  • 2
  • 44
  • 100
  • 3
    Here's a parser I wrote for work. It has an almost complete list of primitives and directives. All that it is missing is the new stuff for 10.2. – rcollyer Jul 15 '15 at 21:15
  • @rcollyer. I saw this answer in my search before posting the question! I was hoping for something that could be called directly from Mathematica, but I can always deposit your primitives in a package if I need to. Thanks! – march Jul 15 '15 at 21:19
  • I think I have something that will work, programatically. – rcollyer Jul 15 '15 at 21:20
  • In the old days, I had to maintain a long list based on the list given in the docs. I like this question; maybe you should also ask about the possibility of a GraphicsDirectiveQ[]. :) – J. M.'s missing motivation Jul 16 '15 at 00:32
  • @march correction, now the parser has a complete list of primitives. I forgot to add the v10 and v10.2 primitives even in my local implementation. – rcollyer Jul 16 '15 at 13:02

4 Answers4

22

Edit: It was pointed out that the original form is not bullet-proof, e.g. GraphicsPrimitiveQ /@ {InputNotebook, Unique, Sequence} all returned True. However, when looking upon this answer about generating new graphics primitives, a superior answer came to light:

Clear[GraphicsPrimitiveQ];
GraphicsPrimitiveQ[s_Symbol | (s_)[___]] := 
 0 < Count[DownValues[Typeset`MakeBoxes], 
   Verbatim[HoldPattern][HoldPattern[Typeset`MakeBoxes[s[___], __, Graphics]]], -1]

which is now bullet-proof:

GraphicsPrimitiveQ /@ {Rectangle, Pyramid, Sequence}
(* {True, False, False} *)

I need to look deeper for one dealing with Graphics3D, though.


This is the old version. It works, but has issues.

This works for Graphics primitives:

Clear[GraphicsPrimitiveQ];
GraphicsPrimitiveQ[s_Symbol] := !MatchQ[ToBoxes@Graphics[s[]], GraphicsBox[_s]]


GraphicsPrimitiveQ /@ {Line, Bob, CapsuleShape (*3d primitive*)}
(* {True, False, False} *)

with the additional rule

GraphicsPrimitiveQ[e : (h_)[___]] := !MatchQ[ToBoxes@Graphics[e], GraphicsBox[_h]]

GraphicsPrimitiveQ[#[{{1,2},{3,4}}]]& /@ {Polygon, List}
(* {True, False} *)

This works because in the presence of Graphics, the primitives have a box form, e.g.

ToBoxes@Graphics@Line[{{1,2},{3,4}}]
(* GraphicsBox@LineBox[{{1,2},{3,4}}] *)

but non-primitives do not, even those that have box forms

ToBoxes@Subscript[x, 2]
(* SubscriptBox[x, 2] *)

ToBoxes@Graphics@Subscript[x, 2]
(* GraphicsBox[Subscript[x, 2]] *)

nor do primitives expand to their box form outside of Graphics, e.g.

ToBoxes@Line[]
(* RowBox[{"Line", "[", "]"}] *)

We can do the same with Graphics3D,

Clear[GraphicsPrimitiveQ];
Graphics3DPrimitiveQ[s_Symbol] := 
   !MatchQ[ToBoxes@Graphics3D[s[]], Graphics3DBox[_s]]
Graphics3DPrimitiveQ[e : (h_)[___]] := 
   !MatchQ[ToBoxes@Graphics3D[e], Graphics3DBox[_h]]

but it returns True for exclusively 2D primitives, e.g.

Graphics3DPrimitiveQ @ Rectangle
(* True *)

So, I do not know how to approach this, if you want something that will exclusively detect 3D primitives.

rcollyer
  • 33,976
  • 7
  • 92
  • 191
  • That's... mysterious. I'll try to parse that later. In the meantime, +1. (I'm out of votes for the day, but I'll upvote in a couple of hours.) I will need it to find 3D primitives, in general. Also, in order to match the primitive and not just the Head, could we do instead GraphicsPrimitiveQ[s_Symbol[__]]? – march Jul 15 '15 at 21:36
  • That's fine! All I wanted was the all-in-one package. I don't need 3D exclusively. Thanks again! – march Jul 16 '15 at 02:13
  • It was a guess, an educated guess, but a guess nonetheless, and I got lucky. :) – rcollyer Jul 16 '15 at 02:25
  • I like the idea but there is a potential problem: GraphicsPrimitiveQ /@ {InputNotebook, Unique, Sequence} – Mr.Wizard Jul 16 '15 at 02:30
  • 1
    @Mr.Wizard well, it's a good thing I found a better way. I'll post it shortly, I hope. – rcollyer Jul 16 '15 at 02:33
  • I'm afraid your new method is far from "bullet-proof:" GraphicsPrimitiveQ /@ {Polygon, Disk, Circle} :-( – Mr.Wizard Jul 16 '15 at 13:19
  • @Mr.Wizard I noticed that last night. There are several instances of primitives that do not have Graphics as their third argument, like Circle which has _. So, Graphics applies to it, but it is a bit squirrelly. Back to the drawing board. – rcollyer Jul 16 '15 at 13:34
  • 1
    Using your own list of primitives from (32719) reveals that GraphicsPrimitiveQ fails more often that it succeeds. In 10.1.0 failures are: {AASTriangle, ASATriangle, BezierCurve, BSplineCurve, BSplineSurface, Circle, Circumsphere, Cone, Cylinder, Disk, EmptyRegion, FullRegion, Hexahedron, Parallelepiped, Parallelogram, Polygon, Prism, Pyramid, Raster, Raster3D, SASTriangle, SSSTriangle, Tetrahedron, Tube} – Mr.Wizard Jul 16 '15 at 13:35
  • @Mr.Wizard AASTriangle, etc., should be removed from that list as they evaluate to Triangle via DownValues. BSplineSurface is actually a uniquely 3D primitive that works; who knew? The two regions were a mistake. This is going to require some fiddling. – rcollyer Jul 16 '15 at 13:53
  • 1
    You'll notice the conspicuous lack of an answer from me. Happy fiddling! ;-) – Mr.Wizard Jul 16 '15 at 13:54
  • @Mr.Wizard I've even considered using RegionQ, but alas no DownValues to query. – rcollyer Jul 16 '15 at 13:57
  • 1
    I decided to post the only simple answer I can think of. It's not a good match for what the OP requested but I think it has potentially useful behavior of its own. I expect you'll inform me of its failings. :-) – Mr.Wizard Jul 16 '15 at 14:24
  • Would checking the dimensions of arrays within primitives be good enough to distinguish 2D from 3D? – J. M.'s missing motivation Jul 16 '15 at 16:03
11

With WolframLanguageData your list of graphics primitives will stay up to date.

ListOfGraphicsPrimitives[] = Symbol /@ 
 WolframLanguageData[
  EntityClass["WolframLanguageSymbol", {"FunctionalityArea","GraphicsPrimitiveFunctions"}], "Name"]

{AASTriangle, AffineHalfSpace, AffineSpace, Annulus, Arrow, ASATriangle, Ball, BezierCurve, BSplineCurve, CapsuleShape, Circle, Circumsphere, Cone, ConicHullRegion, Cuboid, Cylinder, Disk, DiskSegment, Ellipsoid, EmptyRegion, FilledCurve, FullRegion, GraphicsComplex, HalfLine, HalfPlane, HalfSpace, Hexahedron, Hyperplane, Insphere, JoinedCurve, Line, Parallelepiped, Parallelogram, Point, Polygon, Prism, Rectangle, RegularPolygon, SASTriangle, Simplex, Sphere, SphericalShell, SSSTriangle, StadiumShape, Tetrahedron, Triangle, Tube}

ListOfGraphicsFunctions[] = Symbol /@ 
 WolframLanguageData[
  EntityClass["WolframLanguageSymbol", {"FunctionalityArea","GraphicsFunctions"}], "Name"]

{AbsoluteDashing, AbsolutePointSize, AbsoluteThickness, Antialiasing, Arrowheads, AspectRatio, Axes, AxesEdge, AxesLabel, AxesOrigin, AxesStyle, Axis, Back, Background, BezierFunction, BoundaryDiscretizeGraphics, Boxed, BoxRatios, BoxStyle, BSplineFunction, BSplineSurface, CapForm, ClippingStyle, ClipPlanes, ClipPlanesStyle, ClipRange, ContentSelectable, ContourLabels, Contours, ContourShading, CoordinatesToolOptions, CurveClosed, Dashing[{Small, Small}], Dashing, DataRange, DataReversed, DateTicksFormat, DefaultAxesStyle, DefaultBaseStyle, DefaultBoxStyle, DefaultFaceGridsStyle, DefaultFrameStyle, DefaultFrameTicksStyle, Directive, DiscretizeGraphics, DisplayFunction, Dashing[{0, Small, Small, Small}], Dashing[{0, Small}], EdgeForm, Epilog, Exclusions, ExtentElementFunction, ExtentMarkers, ExtentSize, FaceGrids, Filling, FillingStyle, Frame, FrameLabel, FrameMargins, FrameTicks, Front, FullGraphics, Graphics, Graphics3D, GraphicsColumn, GraphicsGrid, GraphicsGroup, GraphicsRow, GridLines, Inset, ItemAspectRatio, Joined, Lighting, LightingAngle, Mesh, MeshFunctions, MeshShading, NCache, NormalsFunction, Offset, Opacity, OpacityFunction, OpacityFunctionScaling, PixelConstrained, PointSize, PolarAxes, PolarAxesOrigin, PolarGridLines, PolarTicks, Prolog, Raster, Raster3D, Rasterize, RasterSize, RegionFunction, RevolutionAxis, Rotate, RotateLabel, RotationAction, RoundingRadius, Scale, Scaled, Show, Specularity, SphericalRegion, SplineClosed, SplineDegree, SplineKnots, SplineWeights, StreamScale, Texture, Thickness[Large], Thickness, Thickness[Tiny], Ticks, Translate, GrayLevel[ 0, 0], VertexColors, VertexDataCoordinates, VertexNormals, VertexTextureCoordinates, ViewAngle, ViewCenter, ViewMatrix, ViewPoint, ViewRange, ViewVector, ViewVertical}

Hop this helps.

Edmund
  • 42,267
  • 3
  • 51
  • 143
  • I tried to get this yesterday, but I think I will need a lot of time till I get intuition how to work entities :-/ – Kuba Jan 26 '16 at 07:13
  • On my Mac with v11.3, ListOfGraphicsPrimitives gives me different results than shown here and does not include the following primitives listed in the documentation for Graphics: Disk, InfiniteLine, Inset, GraphicsGroup, Line, Locator, Point, Polygon, Raster, SASTriangle, and Text. The bolded entries are also not in your results. – Bob Hanlon Oct 15 '18 at 00:51
10

The answer to the question depends upon what exactly should be called as "graphics primitive". In this answer from the practical point of view I define it as a container which can be found inside of Graphics or Graphics3D, which draws something and is not a graphical directive or Dynamic wrapper. This definition differs from the usual meaning but covers all the key cases. Here is (I hope) complete list of such containers for version 10.2:

{Point, PointBox, Line, LineBox, Arrow, ArrowBox, Rectangle, RectangleBox, Parallelogram, Triangle, JoinedCurve, JoinedCurveBox, FilledCurve, FilledCurveBox, StadiumShape, DiskSegment, Annulus, BezierCurve, BezierCurveBox, BSplineCurve, BSplineCurveBox, BSplineSurface, BSplineSurface3DBox, SphericalShell, CapsuleShape, Raster, RasterBox, Raster3D, Raster3DBox, Polygon, PolygonBox, RegularPolygon, Disk, DiskBox, Circle, CircleBox, Sphere, SphereBox, Ball, Ellipsoid, Cylinder, CylinderBox, Tetrahedron, TetrahedronBox, Cuboid, CuboidBox, Parallelepiped, Hexahedron, HexahedronBox, Prism, PrismBox, Pyramid, PyramidBox, Simplex, ConicHullRegion, ConicHullRegionBox, Hyperplane, HalfSpace, AffineHalfSpace, AffineSpace, ConicHullRegion3DBox, Cone, ConeBox, InfiniteLine, InfinitePlane, HalfLine, InfinitePlane, HalfPlane, Tube, TubeBox, GraphicsComplex, GraphicsComplexBox, GraphicsGroup, GraphicsGroupBox, GeoGraphics, Graphics, GraphicsBox, Graphics3D, Graphics3DBox, MeshRegion, BoundaryMeshRegion, GeometricTransformation, GeometricTransformationBox, Rotate, Translate, Scale, SurfaceGraphics, Text, TextBox, Inset, InsetBox, Inset3DBox, Panel, PanelBox, Legended, Placed, LineLegend, Texture}

Alexey Popkov
  • 61,809
  • 7
  • 149
  • 368
7

Since there seems no other simple answer at this time I propose the approach of rendering a Graphics expression and seeing if it has errors. By definition this will pass both primitives and directives, as well as inert expressions such as {}. I hope it nevertheless serves some purpose.

I rasterize the graphic and look for the tell-tale pink warning color. Of course it would be easy to create a false negative simply by creating an element with this very color but that is a corner case.

graphicsQ = 
  FreeQ[Union @@ ImageData @ Image[Graphics[#], ImageSize -> 30], 
    x_ /; x == {1.`, 0.9019607843137255`, 0.9019607843137255`}] &;

Positive examples:

graphicsQ /@ {Thick, Green, Rectangle[{0, -1}, {2, 1}], Red, Disk[], Blue, Circle[{2, 0}],
   Yellow, Polygon[{{2, 0}, {4, 1}, {4, -1}}], Purple, Arrowheads[Large], 
  Arrow[{{4, 3/2}, {0, 3/2}, {0, 0}}], Black, Dashed, Line[{{-1, 0}, {4, 0}}]}

{True, True, True, True, True, True, True, True, True, True, True, True, True, True, True}

Some negative examples:

graphicsQ /@ {Circle["x"], Arrow[], Line[], Pi, 2.2}

{False, False, False, False, False}

Specifically I included malformed primitives to demonstrate that these are not passed. This may or may not be desirable depending on your application.

Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
  • 1
    The pink color works, but I think the superior method is to adapt this answer, and yes this was my ultimate fall back method. – rcollyer Jul 16 '15 at 14:26
  • @rcollyer I'm sad to say I forgot about that answer! Would you like to post a second answer using that method and I shall delete mine? – Mr.Wizard Jul 16 '15 at 14:29
  • 1
    I'll might do that this afternoon. I might edit yours. Neither will happen until this afternoon. :P – rcollyer Jul 16 '15 at 14:34
  • 1
    I think the fact that it outputs False for malformed primitives (something that I didn't think of) is extremely useful, for a number of reasons, so this is a good addition. – march Jul 16 '15 at 16:40