GraphPlots的大小一致

时间:2010-11-20 02:00:22

标签: wolfram-mathematica

更新10/27 :我已经详细介绍了在答案中实现一致规模的步骤。基本上对于每个Graphics对象,您需要将所有填充/边距修复为0并手动指定plotRange和imageSize,使得1)plotRange包含所有图形2)imageSize = scale * plotRange

现在仍然确定1)如何完全通用,给出了一个适用于由点和粗线组成的图形的解决方案(AbsoluteThickness)


我在VertexRenderingFunction和“Ve​​rtexCoordinates”中使用“Inset”来保证图形子图之间的一致外观。使用“Inset”将这些子图绘制为另一个图的顶点。有两个问题,一个是图形周围没有裁剪得到的框(即,一个顶点的图形仍然放在一个大框中),另一个是尺寸之间有奇怪的变化(你可以看到一个框是垂直的) 。任何人都可以看到解决这些问题的方法吗?

这与如何保持顶点大小看起来相同的早期question有关,虽然Michael Pilat建议使用Inset可以保持顶点渲染的规模相同,但总体规模可能不同。例如,在左侧分支上,由顶点2,3组成的图形相对于顶部图形中的“2,3”子图拉伸,即使我使用绝对顶点定位

http://yaroslavvb.com/upload/bad-graph.png

(*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]

这是另一个例子,和以前一样的问题,但相对尺度的差异更明显。目标是让第二张图片中的部分与第一张图片中的部分完全匹配。

http://yaroslavvb.com/upload/bad-plot2.png

(* 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]]]

欢迎任何其他有关美观的图形操作可视化的建议。

4 个答案:

答案 0 :(得分:5)

以下是实现对图形对象相对比例的精确控制所需的步骤。

为了实现一致的比例,需要明确指定输入坐标范围(常规坐标)和输出坐标范围(绝对坐标)。常规坐标范围取决于PlotRangePlotRangePadding(可能还有其他选项?)。绝对坐标范围取决于ImageSizeImagePadding(可能还有其他选项?)。对于GraphPlot,只需指定PlotRangeImageSize即可。

要创建以预定比例呈现的Graphics对象,您需要确定完全包含对象所需的PlotRange,相应的ImageSize并使用这些设置返回Graphics对象指定。要在涉及粗线时找出必要的PlotRange,可以更轻松地处理AbsoluteThickness,将其称为abs。要完全包含这些线,您可以采用包含端点的最小PlotRange,然后将最小x和最大y边界偏移abs / 2,并将最大x和最小y边界偏移(abs / 2 + 1)。请注意,这些是输出坐标。

组合多个scale-calibrated图形对象时,需要重新计算PlotRange/ImageSize并为组合的Graphics对象显式设置它们。

要将scale-calibrated个对象插入GraphPlot,您需要确保用于自动GraphPlot定位的坐标位于相同的范围内。为此,您可以选择几个角节点,手动修复它们的位置,然后自动定位。

基元Line / JoinedCurve / FilledCurve根据线条是否(几乎)共线而不同地渲染连接/上限,因此需要手动检测共线性。

使用此方法,渲染图像的宽度应等于

(inputPlotRange*scale + 1) + lineThickness*scale + 1

第一个额外的1是为了避免“fencepost错误”而第二个额外的1是在右侧添加以确保粗线不被切断所需的额外像素

我已经通过对Rasterize合并Show并使用Texture使用Orthographic映射的对象光栅化3D绘图并使用Inset投影进行查看来验证此公式,并且它与预测结果。在对象GraphPlot上执行'复制/粘贴'到(**** Note, this uses JoinedCurve and Texture which are Mathematica 8 primitives. In Mathematica 7, JoinedCurve is not needed and can be removed *) (** Global variables **) scale = 50; lineThickness = 1/2; (* line thickness in regular coordinates *) (** Global utilities **) (* test if 3 points are collinear, needed to work around difference \ in how colinear Line endpoints are rendered *) collinear[points_] := Length[points] == 3 && (Det[Transpose[points]~Append~{1, 1, 1}] == 0) (* tales list of point coordinates, returns plotRange bounding box, \ uses global "scale" and "lineThickness" to get bounding box *) getPlotRange[lst_] := ( {xs, ys} = Transpose[lst]; (* two extra 1/ scale offsets needed for exact match *) {{Min[xs] - lineThickness/2, Max[xs] + lineThickness/2 + 1/scale}, {Min[ys] - lineThickness/2 - 1/scale, Max[ys] + lineThickness/2}} ); (* Gets image size for given plot range *) getImageSize[{{xmin_, xmax_}, {ymin_, ymax_}}] := ( imsize = scale*{xmax - xmin, ymax - ymin} + {1, 1} ); (* converts plot range to vertices of rectangle *) pr2verts[{{xmin_, xmax_}, {ymin_, ymax_}}] := {{xmin, ymin}, {xmax, ymin}, {xmax, ymax}, {xmin, ymax}}; (* lifts two dimensional coordinates into 3d *) lift[h_, coords_] := Append[#, h] & /@ coords (* convert Raster object to array specification of texture *) raster2texture[raster_] := Reverse[raster[[1, 1]]/255] Subset[a_, b_] := (a \[Intersection] b == a); inducedGraph[set_] := Select[edges, # \[Subset] set &]; values[dict_] := Map[#[[-1]] &, DownValues[dict]]; (** Graph Specific Stuff *) graphName = {"Grid", {3, 3}}; verts = Range[GraphData[graphName, "VertexCount"]]; edges = GraphData[graphName, "EdgeIndices"]; vcoords = Thread[verts -> GraphData[graphName, "VertexCoordinates"]]; 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]]; (* Generate diagram with explicit PlotRange,ImageSize and \ AbsoluteThickness *) plotHL[verts_, color_] := ( coords = verts /. vcoords; obj = JoinedCurve[Line[coords], CurveClosed -> Not[collinear[coords]]]; (* Figure out PlotRange and ImageSize needed to respect scale *) pr = getPlotRange[verts /. vcoords]; {{xmin, xmax}, {ymin, ymax}} = pr; imsize = scale*{xmax - xmin, ymax - ymin}; lineForm = {Opacity[.3], color, JoinForm["Round"], CapForm["Round"], AbsoluteThickness[scale*lineThickness]}; g = Graphics[{Directive[lineForm], obj}]; gg = GraphPlot[Rule @@@ inducedGraph[verts], VertexCoordinateRules -> vcoords]; Show[g, gg, PlotRange -> pr, ImageSize -> imsize] ); (* Initialize all graph plot images *) SeedRandom[1]; colors = RandomChoice[ColorData["WebSafe", "ColorList"], Length[jnodes]]; Clear[bags]; MapThread[(bags[#1] = plotHL[#1, #2]) &, {jnodes, colors}]; (** Ploting parent graph of subgraphs **) (* figure out coordinates of subgraphs close to edges of bounding \ box, use them to anchor parent GraphPlot *) 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; (* figure out new plot range needed to contain all objects *) fullPR = getPlotRange[verts /. vcoords]; fullIS = getImageSize[fullPR]; (*** Show bags together merged ***) image1 = Show[values[bags], PlotRange -> fullPR, ImageSize -> fullIS] (*** Show bags as vertices of another GraphPlot ***) GraphPlot[ Rule @@@ jedges, EdgeRenderingFunction -> ({Gray, Thick, Arrowheads[.05], Arrow[#1, 0.22]} &), VertexCoordinateRules -> Thread[Thread[extremeBags -> extremePoses]], VertexRenderingFunction -> (Inset[bags[#2], #] &), PlotRange -> fullPR, ImageSize -> 3*fullIS ] (*** Show bags as 3d slides ***) makeSlide[graphics_, pr_, h_] := ( Graphics3D[{ Texture[raster2texture[Rasterize[graphics, Background -> None]]], EdgeForm[None], Polygon[lift[h, pr2verts[pr]], VertexTextureCoordinates -> pr2verts[{{0, 1}, {0, 1}}]] }] ) yoffset = 1/2; slides = MapIndexed[ makeSlide[bags[#], getPlotRange[# /. vcoords], yoffset*First[#2]] &, jnodes]; Show[slides, ImageSize -> 3*fullIS] (*** Show 3d slides in orthographic projection ***) image2 = Show[slides, ViewPoint -> {0, 0, Infinity}, ImageSize -> fullIS, Boxed -> False] (*** Check that 3d and 2d images rasterize to identical resolution ***) Dimensions[Rasterize[image1][[1, 1]]] == Dimensions[Rasterize[image2][[1, 1]]] ,然后栅格化,我得到的图像比预测的像素薄一个。

http://yaroslavvb.com/upload/graphPlots.png

{{1}}

答案 1 :(得分:2)

好的,在您对我之前的答案的评论中(这是一种不同的方法),您说问题是GraphPlot / Inset / PlotRange之间的交互。如果您未指定Inset的大小,则会从插入ImageSize对象的Graphics继承其大小。

以下是我对第一个示例中最后一部分的编辑,这一次考虑了Inset图表的尺寸

(*visualize*)
vrfInner = Inset[Graphics[{White, EdgeForm[Black], Disk[{0, 0}, .05], Black, 
      Text[#2, {0, 0}]}, ImageSize -> 15], #, Center] &;
vrfOuter = Module[{edges = Rule @@@ induced[#2], prange, psize},
    prange = Union /@ Transpose[Union[Flatten[List @@@ edges]] /. vcoords];
    prange = {Min[#] - .5, Max[#] + .5} & /@ prange;
    psize = Subtract @@@ Reverse /@ prange;
    Inset[GraphPlot[edges, VertexRenderingFunction -> vrfInner, 
       VertexCoordinateRules -> vcoords, SelfLoopStyle -> None, 
       Frame -> True, ImageSize -> 100, PlotRange -> prange, 
       PlotRangePadding -> None], #, Center, Scaled[psize {.05, .04}],
       Background -> None ]] &;
TreePlot[edgesOuter, Automatic, nodes, 
 EdgeRenderingFunction -> ({Red, Arrow[#1, 0.25]} &), 
 VertexRenderingFunction -> vrfOuter, ImageSize -> 500]

alt text

n.b。随着外部图形的大小和布局发生变化,必须修改{.05, .04} ... 要使整个事物自动化,您可能需要一种很好的方法让内部和外部图形对象相互检查......

答案 2 :(得分:2)

您可以通过更改 vrfOuter 来修复您的第一个示例,如下所示:

vrfOuter =
  Inset[
    Framed@GraphPlot[
      Rule@@@induced[#2],
      VertexRenderingFunction -> vrfInner,
      VertexCoordinateRules -> vcoords,
      SelfLoopStyle -> None,
      ImageSize -> {100, 100},
      AspectRatio -> 1,
      PlotRange -> {{1, 3}, {1, 3}}
    ],
    #
  ] &;

我删除了 Frame->所有选项并添加了对 Framed 的包装调用。这是因为我发现我无法充分控制前者生成的帧之外的边距。我可能在某个地方遗漏了一些选项,但是 Framed 以我想要的方式工作而没有大惊小怪。

我为 ImageSize 选项添加了显式高度。没有它,Mathematica尝试使用一些算法来选择高度,这种算法主要产生令人满意的结果,但有时(如此处)会混淆。

我添加 AspectRatio 选项出于同样的原因--Mathematica尝试选择“令人愉悦”的宽高比(通常是黄金比例),但我们不希望这样。

我添加了 PlotRange 选项,以确保每个子图使用相同的坐标系。没有它,Mathematica通常会选择一个显示所有节点的最小范围。

结果如下所示。我把它作为练习留给读者来调整箭头,边距等;)

rendered result

编辑:添加 PlotRange 选项以回应@Yaroslav Bulatov的评论

答案 3 :(得分:1)

作为一个快速黑客,您可以引入鬼图来强制所有子图显示在同一网格上。这是我对你的第一个例子的最后一部分的修改 - 我的鬼图是原始图的副本,但是顶点数是负数。

(*visualize*)

ghost = GraphData[gname, "EdgeRules"] /. HoldPattern[a_ -> b_] :> -a -> -b;
vrfInner = If[#2 > 0, 
    Inset[Graphics[{White, EdgeForm[Black], Disk[{0, 0}, .05], Black, 
       Text[#2, {0, 0}]}, ImageSize -> 15], #], {}] &;
erfInner = {If[TrueQ[#2[[1]] > 0], Blue, White], Line[#1]} &;
vrfOuter = Inset[GraphPlot[Join[Rule @@@ induced[#2], ghost],
     VertexRenderingFunction -> vrfInner, 
     VertexCoordinateRules -> (Join[#,#/.HoldPattern[a_->b_]:>-a -> b]&[vcoords]), 
     EdgeRenderingFunction -> erfInner, SelfLoopStyle -> None, 
     Frame -> True, ImageSize -> 100], #] &;
TreePlot[edgesOuter, Automatic, nodes, 
 EdgeRenderingFunction -> ({Red, Arrow[#1, 0.2]} &), 
 VertexRenderingFunction -> vrfOuter, ImageSize -> 500]

alt text

你可以为你的第二个例子做同样的事情。 此外,如果您不想浪费垂直空间,您可以编写一个快速函数来检查要显示的节点,并仅将鬼影保留在所需的行上。

修改:只需为内部图形设置PlotRange -> {{1, 3}, {1, 3}}即可获得相同的输出...