It seems that it was chosen to have "graphs" have as FullForm an expression that has head Graph. A plot made by Plot has a FullForm whose head is not Plot and an "expression" made by BubbleChart does not have head BubbleChart. Expressions generated by BubbleChart and Plot have Head Graphics. It seems that the "graph" is indeed only "generated" when it has to be displayed. The following code "defines a graph"/ties the symbol g to an expression with Head Graph. It shows that whenever we evaluate g (and therefore the expression with Head Graph), Mathematica indeed goes once through all the rules, but that is just normal evaluation as the attribute NHoldAll is not like HoldAll in this sense. Now consider the code
With[
{
unevGraph =
Unevaluated @@ Hold[
Graph[
graphBody,
VertexShapeFunction -> optVal1,
EdgeShapeFunction -> optVal2,
ImageSize -> optVal3
]
] /.
{
graphBody :> {Print["entered Graph"]; 1 -> 2, 2 -> 3, 3 -> 1},
optVal1 :> (Print[
"vSFOption Evaluated"]; (Disk[
Print["printed by vertex shape"]; Print[#]; #, .1] &)),
optVal2 :> (Print[
"eSFOption Evaluated"]; (Line[Print["printed by edge shape"];
Print[#]; #] &)),
optVal3 :> (Print["iSOption Evaluated"]; {100, 100})
}
}
,
g := unevGraph;
]
g//FullForm
The code prints that the Options are evaluated and then shows the FullForm of g. It does not print the other messages. A little bit more explanation about the code above can be found in a section below.
We can print the messages without displaying the graph by calling
h = ToBoxes[g];
--prints--> 'all the messages'
We can then set
g2 = ToExpression[h];
So that g2 really refers to an expression with head Graphics. Indeed it does not print any messages!
For those interested, note that even
HoldForm @@ Hold[g2] /. OwnValues[g2]
yields the image of the graph, but I guess that was to be expected.
We seem to have effectively created an expression with head Graphics corresponding to our graph g, in a (at least somewhat) analogous manner as Plot and BubbleChart create such expressions. It now seems clear that the reason Graph does not automatically make such conversions is that expressions with Graph head can also be used in calculations. I.e. we want to be able to use an expression with head Graph as an argument of for example Subgraph, rather than some expression with head Graphics.
Things that do not work
Something that looks similar can be achieved by evaluating a graph (for example g) and then setting g4 = %. However, g4 then refers to an object with head Graph. Note that copying and pasting the graph that was displayed by evaluating g will also not work.
Remarks about the first code block above
The first code block may seem a bit complicated. Especially the fact that an intermediate expression g:=Unevaluated[stuff] occurs may be a bit confusing. Note that g:=stuff is equivalent to g:=Unevaluated[stuff], so there is nothing special there. In fact I use that these expressions are equivalent, to replace the Hold with Unevaluated, which will be automatically stripped by SetDelayed. The only reason I use the Hold and RuleDelayed is because I wanted to insert the "option evaluated" messages. All of this is really not necessary to convert a Graph to its graphical representation. This can be seen in the section below.
Compact example of making the expresion with Head Graphics
Consider the following code,
g3 =
ToExpression@ToBoxes@Graph[{1 -> 2, 2 -> 3, 3 -> 1},
VertexShapeFunction -> (Disk[Print["printed by vertex shape"];
Print[#]; #, .1] &),
EdgeShapeFunction -> (Line[Print["printed by edge shape"];
jPrint[#]; #] &),
ImageSize -> {100, 100}
];
After evaluation of the code, g3 refers to an expression with head Graphics. Evaluating g3 in a new cell (and indeed in many other places, everything is as expected) shows the graph.
I think we must conclude this is a feature, not a bug :)
Less important: Non graphical options
Note that non graphical options are not "evaluated" when we display the graph. There is actually pretty strange behavior here. If we set
graph =
Graph[{1 -> 2, 2 -> 3, 3 -> 1},
VertexShapeFunction -> (Disk[Print["printed by vertex shape"];
Print[#]; #, .1] &),
EdgeShapeFunction -> (Line[Print["printed by edge shape"];
Print[#]; #] &),
ImageSize -> {100, 100},
VertexWeight :> {4, Print["vertexWeight"]; 5, 6}
];
We have
graph
--print--> messages
--shows--> a graph
But "vertexWeight" is not among the messages. Furthermore, we have
PropertyValue[{graph, 1}, VertexWeight]
-> 4
Whereas
PropertyValue[{graph, 2}, VertexWeight]
--prints--> "vertexWeight"
-> 5
The point here is that options are "evaluated" as needed. Graphical options are used when ToBoxes is used/an output cell is made. I suppose that is also the efficient way of doing things if you also want to be able to do fast calculations with graphs.
Less important: Storage and query of options
We define g7 as follows,
mmm = 8000;
nnn = 1000;
With[
{mmm = mmm,
nnn = nnn},
g7 = Graph[Append[Array[# -> # + 1 &, mmm], mmm -> 1],
EdgeShapeFunction -> Function[c++; If[
(*Mod[c,nnn]==0*)False, Print[#]]; Line[#]]
];
]
We see that defining g7 in this way takes hardly any time at all. But we have
Timing[PropertyValue[{g, 2555}, VertexCoordinates]]
--> {3.701786, {1707.87, 2453.73}} (and prints nothing)
And
Timing[ToExpression[ToBoxes[g]]][[1]]
--prints--> 10 messages
-> 6.282605
This suggests to me Mathematica has a way of building the VertexCoordinates without making the entire graphics of the graph. However, this calculation of the VertexCoordinates is not done when setting g7=Graph[...]. This makes sense, as it has to do only with graphics, so that if you are interested in subgraphs, you really don't want this to be calculated. Calculating the same PropertyValue twice does not take shorter, so I really don't think anything is stored, unless you store the graph graphics in the way I described. But then we can probably not find the values as easily as with PropertyValue.
Less important remarks
Throughout this answer my use of language may have been a bit awkward. Of course, I just want to be precise. For example it is obviously not the case that the evaluation of g3 causes a graph to be displayed, as we can simply use CompoundExpression to not display the graph. It seems that Mathematica does two things in evaluating input (or at least this helps me to talk about things). First, it evaluates an "input" to some intermediate expression that does not change if you evaluate it again. Then it builds the output cell. It seems that whenever the expression that g3 is tied to appears in the FullForm of such an intermediate expression, the graph is displayed. The example with HoldForm above is an example of this. The same is of course not true for g (defined in the first code block), evaluating a cell with the content
Hold[g] /. OwnValues[g]
Does not display a graph. As another example, note that even
OwnValues[g3]
displays a graph.
I hope somebody will have something more to say about this :).