Skip to main content
47 of 48
deleted 48 characters in body
Alexey Popkov
  • 62.3k
  • 7
  • 154
  • 375

Based on Oleksandr's excellent design idea here is my re-implementation of his package which offers a much richer set of shapes.


UPDATE from July 2022

A minor update: now the form PolygonMarker[shape, spec, positions], where spec contains numeric specification for size, returns a list of Polygon graphics primitives with centroids placed at positions (instead of a Translate object, as it was earlier). This change makes straightforward producing explicit primitives intended for the Region-based functionality. As always, this version has no incompatible changes.

Added fouth example under the "Scope" section on the Documentation page for PolygonMarker, which uses the Region-based functionality for producing a high-quality vector figure. This example is also published in this post.

The GitHib version, the WFR version and this post are updated. The package code has now been removed from this post due to exceeding the 30,000 character limit per post.


UPDATE from February 2022

New version is published in the WFR! This version introduces new PolygonMarker[shape, {size, angle}] syntax form, which allows to specify the rotation angle for the shape. Added new built-in shapes: "DancingStar", "DancingStarRight", "DancingStarThick", "DancingStarThickRight", "FivePointedStarSlim", "SixfoldPinwheel", "SixfoldPinwheelRight", "SevenfoldPinwheel", "SevenfoldPinwheelRight". As always, this version has no incompatible changes.


UPDATE from July 2021

New version came out! Now it allows direct generation of Graphics objects that can be immediately used as markers for PlotMarkers. The new version contains no incompatible changes.

The Wolfram Function Repository version is also updated, but now it differs from the version published here and on GitHub in the sense that it does not include the general-purpose functions used to generate the built-in shapes on the fly at the package loading time. It was a decision made by the reviewer to define them simply as lists of points, probably for better performance. The functionality and syntax are the same.


UPDATE from October 2019

Now my function is published in the Wolfram Function Repository what means that it is available for users of Mathematica version 12.0 or higher as ResourceFunction["PolygonMarker"]. Users of previous versions should install the package as described below (the functionality is the same).


How to install the package

The most recent version of the package can be installed from GitHub by evaluating the following:

(* Load the package code *)
package = 
  Import["http://raw.github.com/AlexeyPopkov/PolygonPlotMarkers/master/PolygonPlotMarkers.m", "Text"];

(* Install the package (existing file will be overwritten!) *)
Export[FileNameJoin[{$UserBaseDirectory, "Applications", 
    "PolygonPlotMarkers.m"}], package, "Text"];

For manual installation copy the code from the bottom of this post and save it as "PolygonPlotMarkers.m" in the directory SystemOpen[FileNameJoin[{$UserBaseDirectory, "Applications"}]].


Description of the package

  • The basic usage syntax is PolygonMarker[shape, spec] where shape is a name of built-in shape or a list of 2D coordinates describing a non-selfintersecting polygon, and spec can be either size or {size, angle}.

  • The size can be given as a number or in Scaled or Offset form.

  • The angle in radians determines the angle of counterclocwise rotation of shape about its centroid.

  • PolygonMarker[All] and PolygonMarker[] return the list of names of built-in shapes.

  • PolygonMarker[shape, spec] returns Polygon graphics primitive which can be used in Graphics.

  • PolygonMarker[shape, size, style], where style is a list of graphics directives applied to shape, returns a Graphics object which can be used as a marker for PlotMarkers.

  • PolygonMarker[shape, size, style, options] returns a Graphics object with options applied.

  • With Offset size specification the plot marker has fixed size specified in printer's points independent of the size of the plot.

  • PolygonMarkers with identical size specifications have equal areas (not counting the area taken by the edge of generated Polygon). PolygonMarker[shape, size] returns shape with area size2 in the internal coordinate system of Graphics. PolygonMarker[shape, Offset[size]] returns shape with area size2 square printer's points.

  • The centroid of polygon returned by PolygonMarker[shape, size] is always placed at {0, 0} in the internal coordinate system of Graphics.

  • PolygonMarker[shape, spec, positions] where positions is a list of 2D coordinates evaluates and spec contains numeric specification for size, returns a list of Polygon graphics primitives with centroids placed at positions.

  • PolygonMarker[shape, spec, positions] where positions is a list of 2D coordinates and spec contains Scaled or Offset specification for size, evaluates to Translate[PolygonMarker[shape, size], positions]. It represents a collection of multiple identical copies of the shape with centroids placed at positions.


Basic examples of use

The complete list of built-in named shapes:

Needs["PolygonPlotMarkers`"]

allShapes = PolygonMarker[All]
Tooltip[PolygonMarker[#, 1,
    {FaceForm[Hue@Random[]], EdgeForm[{Black, AbsoluteThickness[0.5], JoinForm["Miter"]}]}, 
    {ImageSize -> 30, PlotRange -> 1.5, PlotRangePadding -> 0, ImagePadding -> 0}], #] & /@ allShapes
{"TripleCross", "Y", "UpTriangle", "UpTriangleTruncated", 
"DownTriangle", "DownTriangleTruncated", "LeftTriangle", 
"LeftTriangleTruncated", "RightTriangle", "RightTriangleTruncated", 
"ThreePointedStar", "Cross", "DiagonalCross", "Diamond", "Square", 
"FourPointedStar", "DiagonalFourPointedStar", "FivefoldCross", 
"Pentagon", "FivePointedStar", "FivePointedStarSlim", 
"FivePointedStarThick", "DancingStar", "DancingStarRight", 
"DancingStarThick", "DancingStarThickRight", "SixfoldCross", 
"Hexagon", "SixPointedStar", "SixPointedStarSlim", "SixfoldPinwheel", 
"SixfoldPinwheelRight", "SevenfoldCross", "SevenPointedStar",
"SevenPointedStarNeat", "SevenPointedStarSlim", "SevenfoldPinwheel", 
"SevenfoldPinwheelRight", "EightfoldCross", "Disk", "H", "I", "N", 
"Z", "S", "Sw", "Sl"}

all available shapes

Automatic plot legends (Mathematica 10 or higher) often require a larger value for the LegendMarkerSize option in order to avoid cropping. Filled markers which pick up PlotStyle and PlotTheme automatically:

fm[name_String, size_ : 8] := PolygonMarker[name, Offset[size], EdgeForm[]];

SeedRandom[25];
ListPlot[Table[Accumulate@RandomReal[1, 10] + i, {i, 6}], 
 PlotMarkers -> 
  fm /@ {"Triangle", "Y", "Diamond", "ThreePointedStar", "FivePointedStar", "TripleCross"}, 
 PlotStyle -> ColorData[54, "ColorList"], Joined -> True, 
 PlotLegends -> 
  PointLegend[Automatic, LegendMarkerSize -> {50, 37}, 
   LegendLayout -> (Column[Row /@ #, Spacings -> -1] &)], 
 ImageSize -> 450]

output

Empty markers which pick up PlotStyle and PlotTheme automatically:

em[name_String, size_ : 7] := PolygonMarker[name, Offset[size],
   {Dynamic@EdgeForm@Directive[CurrentValue["Color"], JoinForm["Round"], AbsoluteThickness[2], Opacity[1]], FaceForm[White]}, ImagePadding -> 6];

SeedRandom[2];
ListPlot[Table[Accumulate@RandomReal[1, 10] + i, {i, 3}], 
 PlotMarkers -> em /@ {"Triangle", "Square", "Diamond"}, 
 Joined -> True, 
 PlotLegends -> PointLegend[Automatic, LegendMarkerSize -> {40, 25}], ImageSize -> 450]

SeedRandom[3];
ListPlot[Table[Accumulate@RandomReal[1, 10] + i, {i, 3}], 
 PlotMarkers -> em /@ {"Triangle", "Square", "Diamond"}, 
 Joined -> True, 
 PlotLegends -> PointLegend[Automatic, LegendMarkerSize -> {40, 25}], 
 PlotTheme -> "Marketing", ImageSize -> 450]

output

output

Filled markers with lighter filling colors:

fm2[name_String, size_ : 9] := PolygonMarker[name, Offset@size, {
    Dynamic@EdgeForm[{CurrentValue["Color"], Opacity[1]}],
    Dynamic@FaceForm@Lighter[CurrentValue["Color"], 0.75]}];

data = Table[{x, BesselJ[k, x]}, {k, 0, 2}, {x, 0, 10, 0.5}];

ListPlot[data, 
 PlotMarkers -> fm2 /@ {"UpTriangle", "Square", "Circle"}, 
 Joined -> True, Frame -> True, Axes -> False, ImageSize -> 450, 
 PlotRangePadding -> {Scaled[.05], Scaled[.1]}]

output

Advanced usage

The third argument of PolygonMarker can be used to specify the coordinate(s) where the shape should be placed:

Graphics[{FaceForm[],EdgeForm[{AbsoluteThickness[1],JoinForm["Miter"]}],
       EdgeForm[Blue],PolygonMarker["Circle",Offset[7],RandomReal[{-1,1},{20,2}]],
       EdgeForm[Red],PolygonMarker["ThreePointedStar",Offset[7],RandomReal[{-1,1},{20,2}]],
       EdgeForm[Darker@Green],PolygonMarker["FourPointedStar",Offset[7],RandomReal[{-1,1},{20,2}]],
       EdgeForm[Darker@Yellow],PolygonMarker["FivePointedStar",Offset[7],RandomReal[{-1,1},{20,2}]]},
      AspectRatio->1/2,ImageSize->450,Frame->True]

output

Construct a list plot directly from graphics primitives:

data = Table[{x, BesselJ[k, x]}, {k, 0, 3}, {x, 0, 10, 0.5}];
markers = {"Circle", "ThreePointedStar", "FourPointedStar", "FivePointedStar"};
colors = {Blue, Red, Darker@Green, Darker@Yellow};
Graphics[Table[{colors[[i]], Line[data[[i]]], FaceForm[White], 
   EdgeForm[{colors[[i]], AbsoluteThickness[1], JoinForm["Miter"]}], 
   PolygonMarker[markers[[i]], Offset[7], data[[i]]]}, {i, 
   Length[data]}], AspectRatio -> 1/2, ImageSize -> 450, 
 Frame -> True]

output

Construct a custom list plot where open plot markers have transparent faces for each other (but not for the lines):

data = Table[{x, BesselJ[k, x]}, {k, 0, 4}, {x, 0, 10, 0.5}];
markers = {"Circle", "ThreePointedStar", "FourPointedStar", "DiagonalFourPointedStar", "FivePointedStar"};
colors = {Blue, Red, Green, Yellow, Orange};
background = Darker@Gray;
Graphics[{Table[{colors[[i]], AbsoluteThickness[1.5], Line[data[[i]]], FaceForm[background], EdgeForm[None], 
    PolygonMarker[markers[[i]], Offset[7], data[[i]]]}, {i, Length[data]}], 
  Table[{FaceForm[None], EdgeForm[{colors[[i]], AbsoluteThickness[1.5], JoinForm["Miter"]}], 
    PolygonMarker[markers[[i]], Offset[7], data[[i]]]}, {i, Length[data]}]}, AspectRatio -> 1/2, ImageSize -> 500, 
 Frame -> True, Background -> background, FrameStyle -> White, 
 ImagePadding -> {{30, 20}, {25, 20}}]

output

Neat Examples

Center markers which pick up PlotStyle and PlotTheme automatically:

cfm[name_String, size_ : 9] := Show[
   PolygonMarker[name, Offset@size, {FaceForm[White],
     Dynamic@EdgeForm[{CurrentValue["Color"], AbsoluteThickness[1], Opacity[1]}]}],
   PolygonMarker[name, Offset[size/2], EdgeForm[None]]];

data = Table[{x, BesselJ[k, x]}, {k, 0, 2}, {x, 0, 10, 0.5}];

ListPlot[data, 
 PlotMarkers -> cfm /@ {"UpTriangle", "Square", "Circle"}, 
 Joined -> True, Frame -> True, Axes -> False, ImageSize -> 450, 
 PlotRangePadding -> {Scaled[.05], Scaled[.1]}, 
 PlotLegends -> PointLegend[Automatic, LegendMarkerSize -> {40, 30}], 
 ImageSize -> 450]

output

Half filled markers which pick up PlotStyle and PlotTheme automatically:

hfm1[name_String, size_ : 9] := Show[
   PolygonMarker[name, Offset@size, {FaceForm[White],
     Dynamic@EdgeForm[{CurrentValue["Color"], AbsoluteThickness[1], Opacity[1]}]}],
   PolygonMarker[name, Offset@size, 
     EdgeForm[None]] /. {x_?Negative, y_?NumericQ} :> {0, y}];

data = Table[{x, BesselJ[k, x]}, {k, 0, 2}, {x, 0, 10, 0.5}];

ListPlot[data, 
 PlotMarkers -> hfm1 /@ {"UpTriangle", "Square", "Circle"}, 
 Joined -> True, Frame -> True, Axes -> False, ImageSize -> 450, 
 PlotRangePadding -> {Scaled[.05], Scaled[.1]}, 
 PlotLegends -> PointLegend[Automatic, LegendMarkerSize -> {40, 30}], 
 ImageSize -> 450]

output

hfm2[name_String, size_ : 9] := Show[
   PolygonMarker[name, Offset@size, {
     FaceForm[White],
     Dynamic@EdgeForm[{CurrentValue["Color"], AbsoluteThickness[1], Opacity[1]}]}],
   Graphics[{EdgeForm[None], 
     Replace[RegionDifference[PolygonMarker[name], 
       Rectangle[{-10, -10}, {10, 0}]], 
      p : {x_, y_} :> Offset[size p, {0, 0}], {-2}]}]];

data = Table[{x, BesselJ[k, x]}, {k, 0, 3}, {x, 0, 10, 0.5}];

ListPlot[data, 
 PlotMarkers -> 
  hfm2 /@ {"Diamond", "Square", "Circle", "RightTriangle"}, 
 Joined -> True, Frame -> True, Axes -> False, ImageSize -> 450, 
 PlotRangePadding -> {Scaled[.05], Scaled[.1]}, 
 PlotLegends -> PointLegend[Automatic, LegendMarkerSize -> {40, 30}], 
 ImageSize -> 450]

output

Contrast markers which pick up PlotStyle and PlotTheme automatically:

cfm2[name_String, size_ : 9] := Show[
   PolygonMarker[name, Offset@size, {
     FaceForm[White],
     Dynamic@EdgeForm[{CurrentValue["Color"], AbsoluteThickness[1], Opacity[1]}]}],
   Graphics[{EdgeForm[None], 
     Replace[RegionDifference[
       RegionDifference[PolygonMarker[name], 
        Triangle[{{-10, 10}, {10, 10}, {0, 0}}]], 
       Triangle[{{-10, -10}, {10, -10}, {0, 0}}]], 
      p : {x_, y_} :> Offset[size p, {0, 0}], {-2}]}]];

data = Table[{x, BesselJ[k, x]}, {k, 0, 3}, {x, 0, 10, 0.5}];

ListPlot[data, 
 PlotMarkers -> 
  cfm2 /@ {"Diamond", "Square", "Circle", "DiagonalFourPointedStar"}, 
 Joined -> True, Frame -> True, Axes -> False, ImageSize -> 450, 
 PlotRangePadding -> {Scaled[.05], Scaled[.1]}, 
 PlotLegends -> PointLegend[Automatic, LegendMarkerSize -> {40, 30}], ImageSize -> 450]

output

The package allows the usage of an arbitrary polygon as a plot marker. Here is an auxiliary function that converts a simple glyph into a set of points suitable for PolygonMarker:

pts[l_String] := 
  First[Cases[
    ImportString[
     ExportString[Style[l, FontFamily -> "Verdana", FontSize -> 20], "PDF"],
     If[$VersionNumber >= 12.2, {"PDF", "PageGraphics"}, {"PDF", "Pages"}]], 
    c_FilledCurve :> c[[2, 1]], Infinity]];

(This conversion is approximate. If the precise conversion is needed one can apply one of the methods described in "How can I adaptively simplify a curved shape?")

An example of use:

ListPlot[ConstantArray[Range[5],7]+Range[0,12,2],PlotStyle->Gray,Joined->True,PlotMarkers->{PolygonMarker[pts["U"],Scaled[0.05],{FaceForm[LightBlue],EdgeForm[Black]}],
  PolygonMarker[pts["S"],Scaled[0.05],{FaceForm[LightBlue],EdgeForm[Black]}],
  PolygonMarker["FivePointedStar",Scaled[0.05],{FaceForm[Red],EdgeForm[Black]}],
  PolygonMarker["FourPointedStar",Scaled[0.05],{FaceForm[Yellow],EdgeForm[Black]}],
  PolygonMarker["DownTriangle",Scaled[0.05],{FaceForm[Green],EdgeForm[Black]}],
  PolygonMarker["DiagonalSquare",Scaled[0.05],{FaceForm[Brown],EdgeForm[Black]}],
  Graphics[{FaceForm[Blue],EdgeForm[Black],Disk[{0,0},Scaled[0.05/Sqrt[\[Pi]]]]}]},PlotRange->{{0,6},{0,18}},ImageSize->450]

output

Here is an example of a black-and-white plot where the markers overlap considerably, I use here some of the symbols recommended by William Cleveland in his early works:

SeedRandom[11];
ListPlot[RandomReal[{-1,1},{6,20,2}],PlotMarkers->{
  PolygonMarker["Circle",Scaled[0.03],{FaceForm[None],EdgeForm[{Black,Thickness[.008]}]}],
  PolygonMarker["UpTriangle",Scaled[0.03],{FaceForm[None],EdgeForm[{Black,Thickness[.008]}]}],
  PolygonMarker["Cross",Scaled[0.03],{FaceForm[Black],EdgeForm[None]}],
  PolygonMarker[pts["U"],Scaled[0.03],{FaceForm[Black],EdgeForm[None]}],
  PolygonMarker["Sl",Scaled[0.03],{FaceForm[Black],EdgeForm[None]}],
  PolygonMarker[pts["W"],Scaled[0.03],{FaceForm[Black],EdgeForm[None]}]},
 Frame->True,FrameStyle->Black,Axes->False,PlotRangePadding->Scaled[.1],ImageSize->450]

output

Additional examples and explanations can be found in the following answers:

Alexey Popkov
  • 62.3k
  • 7
  • 154
  • 375