Although this is hardly a debilitating bug I wondered what else might be affected so I decided to trace this further. I found that the bug affects TreeForm
by way of TreePlot
. Here is a reduced example of the call that originates in the exhibit above:
TreePlot[{1 -> 2, 1 -> 3, 1 -> 4}, Top, 1,
"VertexNames" -> {List, HoldForm["foo"], HoldForm["bar"], HoldForm["Plot"]},
VertexLabeling -> All
]
This ends up calling Network`GraphPlotDump`Private`TreePlot2
which uses Network`GraphPlotDump`Private`GraphPlotInternalRuleList23D
which ultimately becomes:
Network`GraphPlotDump`Private`GraphPlot23DInternalLabels[TreePlot,
SparseArray[Automatic, {4, 4}, 0, {1, {{0, 3, 3, 3, 3}, {{2}, {3}, {4}}}, {1, 1, 1}}],
2, "Plot", "RuleList", {1, 2, 3, 4},
Method -> {"LayeredDrawing", "Root" -> 1, "LayerSizeFunction" -> (1 &), "Rotation" -> 0},
"VertexNames" -> {List, "foo", "bar", "Plot"}, VertexLabeling -> All,
"EdgeLabels" -> None, "VertexTooltips" -> None, "EdgeTooltips" -> None]
And spelunking the trace of this command eventually leads us to:
{List, "foo", "bar", "Plot"} /. {"VertexTooltips" -> Automatic,
"EdgeTooltips" -> Automatic, "EdgeLabels" -> Automatic, "VertexNames" -> Automatic,
"VertexSizes" -> Automatic, "VertexColor" -> Automatic, "EdgeColor" -> Automatic,
"VertexFrameBackground" -> Automatic, "VertexFrameStyle" -> Automatic,
"VertexFrameMargins" -> Automatic, "VertexTextStyle" -> True, "Plot" -> True}
So it seems I have finally found the origin of the problem. Let's test it:
{"VertexTooltips", "EdgeTooltips", "EdgeLabels", "VertexNames", "VertexSizes",
"VertexColor", "EdgeColor", "VertexFrameBackground", "VertexFrameStyle",
"VertexFrameMargins", "VertexTextStyle", "Plot"} // TreeForm
Those ALL are incorrectly replaced.
Viewing the definition with:
Needs["GeneralUtilities`"]
PrintDefinitions @ Network`GraphPlotDump`Private`GraphPlot23DInternalLabels
we find a whole series of operations like:
vnames = (* contexts omitted for clarity *)
"VertexNames" /.
processOptionNames[Flatten[{opts}]] /.
processOptionNames[hiddenOptions[caller]]
(processOptionNames
is Network`GraphPlotDump`Private`processOptionNames
and it converts Option names into strings.)
Can you see the problem? The first ReplaceAll
finds the value of "VertexNames"
in explicit options, if it exists, but then the second ReplaceAll
operates on that value rather than "VertexNames"
! This is careless programming and it should never have lasted all this time in production code.
Since the problem is not limited to vertex names we can demonstrate other peculiar bugs such as this:
ticks = {{0.8, "Prelude"}, {0.7, "Epilog"},
{0.5, "Axes"}, {0.4, "Ticks"},
{0.2, "Foreground"}, {0.1, "Background"}};
TreePlot[{1 -> 2, 2 -> 3}, Frame -> True, FrameTicks -> {{True, ticks}, {False, False}}]
GraphPlot
is also affected:
GraphPlot[{1 -> 2, 2 -> 1, 3 -> 1, 3 -> 2, 4 -> 1, 4 -> 2, 4 -> 4},
Frame -> True, FrameTicks -> {{True, ticks}, {False, False}}]
A double application of ReplaceAll
should never be used to extract option values. Yet disturbingly it seems that is has been used repeatedly.
Fortunately Graph
appears unaffected, but one must wonder what other internal functions might also be affected?
TreeForm
should be a one-liner. No room to make mistake. $\endgroup$TracePrint[ToBoxes @ TreeForm @ "Plot", TraceInternal -> True]
(Be ready to kill the kernel.) $\endgroup$