Perfect vertical alignment (centering) of PointLegend markers and their labels

I think this is a font issue. Look at:

Style[Row[{\[FilledCircle],\[FilledSquare],\[FilledDiamond]}], FontFamily->"Arial"]

enter image description here

The centering looks better in Times :

Style[Row[{\[FilledCircle],\[FilledSquare],\[FilledDiamond]}], FontFamily->"Times"]

enter image description here

Perhaps the following layout function will serve:

layout[pairs_] := Row @ Map[
    Row[{
        Show[#[[1]], BaselinePosition->Center, BaseStyle->{FontFamily->"Times"}],
        Pane[#[[2]], BaselinePosition->Center]
    }, Frame->True]&,
    pairs
]

This is what I get:

p = PointLegend[
    "DefaultPlotStyle" /. (Method/.Charting`ResolvePlotTheme[Automatic,ListPlot]),
    Range[5],
    LegendMarkers->{Automatic, Large},
    LegendLayout->layout,
    LegendFunction->"Frame",
    LabelStyle->{FontFamily->"Arial", FontSize->20}
]

enter image description here


Direct answer to the question (rewritten after the comments)

It is well known that the default markers are font glyphs and Mathematica can't position font glyphs precisely. If you need precise positioning, you should use primitive-based LegendMarkers. For this purpose I recommend my package PolygonPlotMarkers` :

Needs["PolygonPlotMarkers`"]

markers1 = {
   Graphics[{FaceForm[ColorData[97][1]], EdgeForm[], PolygonMarker["Disk", 1]}],
   Graphics[{FaceForm[ColorData[97][2]], EdgeForm[], PolygonMarker["Square", 1]}],
   Graphics[{FaceForm[ColorData[97][3]], EdgeForm[], PolygonMarker["Diamond", 1]}],
   Graphics[{FaceForm[ColorData[97][4]], EdgeForm[], PolygonMarker["UpTriangle", 1]}],
   Graphics[{FaceForm[ColorData[97][5]], EdgeForm[], PolygonMarker["DownTriangle", 1]}]};

PointLegend[ColorData[97] /@ Range[5], Range[5], LegendMarkerSize -> 30, 
 LegendMarkers -> markers1, LegendLayout -> "Row", 
 LabelStyle -> {FontFamily -> "Arial", FontSize -> 30}]

legend

Let us add horizontal line and check how it looks at different font sizes:

overlay[legend_] := Module[{w, h}, {w, h} = ImageDimensions@Rasterize[legend, "Image"];
      Graphics[{Inset[legend, {0, 0}, {0, 0}, Automatic], Red, Thick, Opacity[.5], 
        Line[{{-1, 0}, {1, 0}}]}, AspectRatio -> h/w, ImageSize -> w]]

t = Table[PointLegend[ColorData[97] /@ Range[5], Range[5], LegendMarkerSize -> 30, 
    LegendMarkers -> markers1, LegendLayout -> "Row", 
    LabelStyle -> {FontFamily -> "Arial", FontSize -> fs}], {fs, {20, 30, 50, 60}}];

overlay /@ t // Column

legends

Note that in the above all the markers are centered relative to their bounding boxes, while on the plot they are placed at their centers of mass. Another problem is that they are inscribed into identical boxes (determined by LegendMarkerSize) and hence have visibly different areas. Both problems can be solved at once by specifying sufficiently large symmetric PlotRange (I also show axes which intersect at the center of mass {0, 0}):

markers2 = Append[#, {PlotRange -> 1, 
      Axes -> True, AxesOrigin -> {0, 0}, Ticks -> False}] & /@ markers1;

PointLegend[ColorData[97] /@ Range[5], Range[5], LegendMarkerSize -> 50, 
  LegendMarkers -> markers2, LegendLayout -> "Row", 
  LabelStyle -> {FontFamily -> "Arial", FontSize -> 30}] // overlay

legend

If one wish to have markers aligned relative to their bounding boxes and at the same time control their sizes, it is achievable by adding PlotRangePadding option without explicit PlotRange (the larger PlotRangePadding, the smaller will be marker):

markers1sized = 
  MapThread[Append[#1, PlotRangePadding -> #2] &, {markers1, {.1, .2, 0, 0, 0}}];

PointLegend[ColorData[97] /@ Range[5], Range[5], LegendMarkerSize -> 30, 
 LegendMarkers -> markers1sized, LegendLayout -> "Row", 
 LabelStyle -> {FontFamily -> "Arial", FontSize -> 30}]

legend

PointLegend is fairly complicated function which has its own set of undocumented "features" what make it difficult to obtain exactly what you wish in every situation. In practice when only a standalone legend is needed it is often easier to construct the legend manually:

Grid[{Flatten@Transpose[{Append[#, ImageSize -> 30] & /@ markers1, Range[5]}]}, 
 Alignment -> {Center, Center}, Spacings -> {{{0.8, 0.5}}, Automatic}, 
 BaseStyle -> {FontFamily -> "Arial", FontSize -> 30}]

legend

Table[overlay@
   Grid[{Flatten@Transpose[{Append[#, ImageSize -> 30] & /@ markers2, Range[5]}]}, 
    Alignment -> {Center, Center}, Spacings -> {{{0.8, 0.5}}, Automatic}, 
    BaseStyle -> {FontFamily -> "Arial", FontSize -> fs}], {fs, {20, 30, 40}}] // Column

column


Offset size, ImageSize and alignment of the markers

When the size of the markers specified in absolute units via Offset (please read "Description of the package" section in the linked post) the alignment of the markers may depend on LegendMarkerSize option of PointLegend (or ImageSize option of Graphics) when explicit PlotRange and/or ImagePadding for the marker isn't specified. This comes from how FrontEnd crops the plot when ImageSize becomes smaller. Here the red point shows the geometric center of the image, and red rectangle show complete plotting range (PlotRange + PlotRagePadding), axes intersect at the center of mass:

opts = {Axes -> True, AxesOrigin -> {0, 0}, Ticks -> False, Background -> LightBlue,
   (*ImagePadding -> 0, *)
   (*PlotRange -> {{-1, 1}, {-1, 1}},*)
   Epilog -> {Red, AbsolutePointSize[5], Point[ImageScaled[{.5, .5}]], 
     EdgeForm[{Red, Thick, Opacity[1]}], FaceForm[], 
     Rectangle[Scaled[{0, 0}], Scaled[{1, 1}]]}};

Table[Graphics[{FaceForm[ColorData[97][2]], EdgeForm[], 
    PolygonMarker["UpTriangle", Offset[40]]}, opts, ImageSize -> {size, size}, 
   BaselinePosition -> Axis], {size, {75, 60, 55, 50, 40, 30}}] // 
 Row[#, Spacer[5], Alignment -> {Center, Axis}] &

row

As one can see, when ImageSize is large, the center of the image coincides with the center of the plotting range almost exactly. But when ImageSize becomes smaller, relative plotting range is reduced and some ImagePadding is added around it non-symmetrically. Hence the center of the image doesn't coincide with the center of the plotting range anymore. We can fix it by disabling ImagePadding (uncomment ImagePadding -> 0 in opts above):

row

Uncommenting also PlotRange -> {{-1, 1}, {-1, 1}} doesn't affects the result because it is the default PlotRange when the plot doesn't contain "tangible" primitives:

row

But the straighforward (and correct) approach to align markers with Offset sizes at their centers of mass is to specify BaselinePosition -> Axis:

opts = {};
markers3 := {
   Graphics[{FaceForm[ColorData[97][1]], EdgeForm[], 
             PolygonMarker["Diamond", Offset[20]]}, opts], 
   Graphics[{FaceForm[ColorData[97][2]], EdgeForm[], 
             PolygonMarker["UpTriangle", Offset[20]]}, opts], 
   Graphics[{FaceForm[ColorData[97][3]], EdgeForm[], 
             PolygonMarker["DownTriangle", Offset[20]]}, opts]};

opts = {BaselinePosition -> Axis, Axes -> True, Ticks -> False};
l = PointLegend[ColorData[97] /@ Range[3], Range[3], LegendMarkerSize -> 35, 
 LegendMarkers -> markers3, LegendLayout -> "Row", 
 LabelStyle -> {FontFamily -> "Arial", FontSize -> 30}]

legend

Aligning such markers relative to bounding boxes is more difficult and can be achieved manually by adding ImageMargins option (which is defined outside of ImageSize):

markers4 = MapThread[
   Append[#1, ImageMargins -> {{0, 0}, #2}] &, {markers3, {{0, 0}, {0, 7}, {7, 0}}}];
PointLegend[ColorData[97] /@ Range[3], Range[3], LegendMarkerSize -> 35, 
 LegendMarkers -> markers4, LegendLayout -> "Row", 
 LabelStyle -> {FontFamily -> "Arial", FontSize -> 30}]

legend


Internal structure of generated legend

By applying ToBoxes to our legend we can see its internal structure on the box level. It is TemplateBox with first argument being the list of labels. The option DisplayFunction contains a function which will be dynamically applied to the first argument in order to generate a static box structure for on-screen display. We see that this function returns GridBox where the pairs consisted of GraphicsBox (containing InsetBox with our marker) and a placeholder for the corresponding label reside. Each GraphicsBox already contains options including BaselinePosition, rows are set to be aligned to Baseline:

Cases[ToBoxes@l, 
 gb_GridBox :> (gb /. 
    g_GraphicsBox :> 
     "GraphicsBox"[Skeleton[…], Options[g, BaselinePosition]]), -1, 1]
{GridBox[{{
    "GraphicsBox"[<<…>>, {BaselinePosition -> Scaled[0.157143] -> Baseline}], #1,
    "GraphicsBox"[<<…>>, {BaselinePosition -> Scaled[0.157143] -> Baseline}], #2, 
    "GraphicsBox"[<<…>>, {BaselinePosition -> Scaled[0.157143] -> Baseline}], #3}}, 
  GridBoxAlignment -> {"Columns" -> {Center, Left}, "Rows" -> {{Baseline}}}, 
  AutoDelete -> False, GridBoxDividers -> {"Columns" -> {{False}}, "Rows" -> {{False}}}, 
  GridBoxItemSize -> {"Columns" -> {{All}}, "Rows" -> {{All}}}, 
  GridBoxSpacings -> {"Columns" -> {{0.8, 0.5}}}]}

We see that BaselinePosition of GraphicsBox aligns to Baseline not its bottom, but the fraction 1/6 (= 0.157143) of its height (this number isn't constant). Fortunately when BaselinePosition is specified in the marker, the setting from the parent GraphicsBox is ignored.

These observations suggest a method to cure the legend. Just set up rows to be aligned at the Center:

alingRowLegend[legend_] := 
 RawBoxes[ToBoxes@legend /. ("Rows" -> {{Baseline}}) -> ("Rows" -> {{Center}})]