Update 10/27: I’ve put detailed steps for achieving consistent scale in an answer. Basically for each Graphics object you need to fix all padding/margins to 0 and manually specify plotRange and imageSize that such that 1) plotRange includes all graphics 2) imageSize=scale*plotRange
Still now sure how to do 1) in full generality, a solution that works for Graphics consisting of points and thick lines (AbsoluteThickness) is given
I’m using “Inset” in VertexRenderingFunction and “VertexCoordinates” to guarantee consistent appearance among subgraphs of a graph. Those subgraphs are drawn as vertices of another graph, using “Inset”. There are two problems, one is that resulting boxes are not cropped around the graph (ie, graph with one vertex still gets placed in a big box), and another is that there’s strange variation among sizes (you can see one box is vertical). Can anyone see a way around these problems?
This is related to an earlier question of how to keep vertex sizes looking the same, and while Michael Pilat’s suggestion of using Inset works to keep vertices rendering at the same scale, overall scale may be different. For instance on the left branch, the graph consisting of vertices 2,3 is stretched relative to the “2,3” subgraph in the top graph, even though I’m using absolute vertex positioning for both

(source: yaroslavvb.com)
(*utilities*)intersect[a_, b_] := Select[a, MemberQ[b, #] &];
induced[s_] := Select[edges, #~intersect~s == # &];
Needs["GraphUtilities`"];
subgraphs[
verts_] := (gr =
Rule @@@ Select[edges, (Intersection[#, verts] == #) &];
Sort /@ WeakComponents[gr~Join~(# -> # & /@ verts)]);
(*graph*)
gname = {"Grid", {3, 3}};
edges = GraphData[gname, "EdgeIndices"];
nodes = Union[Flatten[edges]];
AppendTo[edges, #] & /@ ({#, #} & /@ nodes);
vcoords = Thread[nodes -> GraphData[gname, "VertexCoordinates"]];
(*decompose*)
edgesOuter = {};
pr[_, _, {}] := None;
pr[root_, elim_,
remain_] := (If[root != {}, AppendTo[edgesOuter, root -> remain]];
pr[remain, intersect[Rest[elim], #], #] & /@
subgraphs[Complement[remain, {First[elim]}]];);
pr[{}, {4, 5, 6, 1, 8, 2, 3, 7, 9}, nodes];
(*visualize*)
vrfInner =
Inset[Graphics[{White, EdgeForm[Black], Disk[{0, 0}, .05], Black,
Text[#2, {0, 0}]}, ImageSize -> 15], #] &;
vrfOuter =
Inset[GraphPlot[Rule @@@ induced[#2],
VertexRenderingFunction -> vrfInner,
VertexCoordinateRules -> vcoords, SelfLoopStyle -> None,
Frame -> True, ImageSize -> 100], #] &;
TreePlot[edgesOuter, Automatic, nodes,
EdgeRenderingFunction -> ({Red, Arrow[#1, 0.2]} &),
VertexRenderingFunction -> vrfOuter, ImageSize -> 500]
Here’s another example, same problem as before, but the difference in relative scales is more visible. The goal is to have parts in the second picture match precisely the parts in the first picture.

(source: yaroslavvb.com)
(* Visualize tree decomposition of a 3x3 grid *)
inducedGraph[set_] := Select[edges, # \[Subset] set &];
Subset[a_, b_] := (a \[Intersection] b == a);
graphName = {"Grid", {3, 3}};
edges = GraphData[graphName, "EdgeIndices"];
vars = Range[GraphData[graphName, "VertexCount"]];
vcoords = Thread[vars -> GraphData[graphName, "VertexCoordinates"]];
plotHighlight[verts_, color_] := Module[{vpos, coords},
vpos =
Position[Range[GraphData[graphName, "VertexCount"]],
Alternatives @@ verts];
coords = Extract[GraphData[graphName, "VertexCoordinates"], vpos];
If[coords != {}, AppendTo[coords, First[coords] + .002]];
Graphics[{color, CapForm["Round"], JoinForm["Round"],
Thickness[.2], Opacity[.3], Line[coords]}]];
jedges = {{{1, 2, 4}, {2, 4, 5, 6}}, {{2, 3, 6}, {2, 4, 5, 6}}, {{4,
5, 6}, {2, 4, 5, 6}}, {{4, 5, 6}, {4, 5, 6, 8}}, {{4, 7, 8}, {4,
5, 6, 8}}, {{6, 8, 9}, {4, 5, 6, 8}}};
jnodes = Union[Flatten[jedges, 1]];
SeedRandom[1]; colors =
RandomChoice[ColorData["WebSafe", "ColorList"], Length[jnodes]];
bags = MapIndexed[plotHighlight[#, bc[#] = colors[[First[#2]]]] &,
jnodes];
Show[bags~
Join~{GraphPlot[Rule @@@ edges, VertexCoordinateRules -> vcoords,
VertexLabeling -> True]}, ImageSize -> Small]
bagCentroid[bag_] := Mean[bag /. vcoords];
findExtremeBag[vec_] := (
vertList = First /@ vcoords;
coordList = Last /@ vcoords;
extremePos =
First[Ordering[jnodes, 1,
bagCentroid[#1].vec > bagCentroid[#2].vec &]];
jnodes[[extremePos]]
);
extremeDirs = {{1, 1}, {1, -1}, {-1, 1}, {-1, -1}};
extremeBags = findExtremeBag /@ extremeDirs;
extremePoses = bagCentroid /@ extremeBags;
vrfOuter =
Inset[Show[plotHighlight[#2, bc[#2]],
GraphPlot[Rule @@@ inducedGraph[#2],
VertexCoordinateRules -> vcoords, SelfLoopStyle -> None,
VertexLabeling -> True], ImageSize -> 100], #] &;
GraphPlot[Rule @@@ jedges, VertexRenderingFunction -> vrfOuter,
EdgeRenderingFunction -> ({Red, Arrowheads[0], Arrow[#1, 0]} &),
ImageSize -> 500,
VertexCoordinateRules -> Thread[Thread[extremeBags -> extremePoses]]]
Any other suggestions for aesthetically pleasing visualization of graph operations are welcome.
Here are the steps needed to achieve precise control over relative scales of graphics objects.
To achieve consistent scale one needs to explicitly specify input coordinate range (regular coordinates) and output coordinate range (absolute coordinates). Regular coordinate range depends on
PlotRange,PlotRangePadding(and possibly others options?). Absolute coordinate range depends onImageSize,ImagePadding(and possibly other options?). ForGraphPlot, it is sufficient to specifyPlotRangeandImageSize.To create Graphics object that renders at a pre-determined scale, you need to figure out
PlotRangeneeded to fully include the object, correspondingImageSizeand returnGraphicsobject with these settings specified. To figure out the necessaryPlotRangewhen thick lines are involved it is easier to deal withAbsoluteThickness, call itabs. To fully include those lines you could take the smallestPlotRangethat includes endpoints, then offset minimum x and maximum y boundaries by abs/2, and offset maximum x and minimum y boundaries by (abs/2+1). Note that these are output coordinates.When combining several
scale-calibratedGraphics objects you need to recalculatePlotRange/ImageSizeand set them explicitly for the combined Graphics object.To Inset
scale-calibratedobjects intoGraphPlotyou need to make sure that coordinates used for automaticGraphPlotpositioning are in the same range. For that, you could pick several corner nodes, fix their positions manually, and let automatic positioning do the rest.Primitives
Line/JoinedCurve/FilledCurverender joins/caps differently depending on whether the line is (almost) collinear, so one needs to manually detect collinearity.Using this approach, rendered images should have width equal to
(inputPlotRange*scale + 1) + lineThickness*scale + 1First extra
1is to avoid the “fencepost error” and second extra 1 is the extra pixel needed to add on the right to make sure thick lines are not cut-offI’ve verified this formula by doing
Rasterizeon combinedShowand rasterizing a 3D plot with objects mapped usingTextureand viewed withOrthographicprojection and it matches the predicted result. Doing ‘Copy/Paste’ on objectsInsetintoGraphPlot, and then Rasterizing, I get an image that’s one pixel thinner than predicted.(source: yaroslavvb.com)