【问题标题】:Consistent size for GraphPlotsGraphPlots 的大小一致
【发布时间】:2010-11-20 02:00:22
【问题描述】:

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

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


我在 VertexRenderingFunction 和“VertexCoordinates”中使用“Inset”来保证图的子图之间的一致外观。这些子图被绘制为另一个图的顶点,使用“Inset”。有两个问题,一个是生成的盒子没有在图形周围裁剪(即,一个顶点的图形仍然被放置在一个大盒子中),另一个是尺寸之间存在奇怪的变化(你可以看到一个盒子是垂直的) .谁能找到解决这些问题的方法?

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


(来源: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]

这是另一个示例,与之前的问题相同,但相对比例的差异更加明显。目标是让第二张图片中的部分与第一张图片中的部分完全匹配。


(来源: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]]]

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

【问题讨论】:

  • 我从来没有达到一致的图像大小,不仅是图形,叠加图像和绘图时也是如此。希望有人拿着魔杖借给我们……
  • 您会看到一些非常漂亮的图表。当你完成了你正在做的这个项目后,你会为我们其他人发布一个包吗?
  • 出于好奇,您为什么要编写自己的Intersection 版本?
  • 好的,正在努力实施“广义分配法”,我会尽快将其提供给大家。使用intersection 因为Intersection 排序列表

标签: wolfram-mathematica


【解决方案1】:

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

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

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

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

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

基元Line/JoinedCurve/FilledCurve 根据线是否(几乎)共线以不同方式渲染连接/大写,因此需要手动检测共线。

使用这种方法,渲染图像的宽度应该等于

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

第一个额外的1 是为了避免“栅栏错误”,第二个额外的 1 是需要在右侧添加的额外像素以确保粗线不会被截断

我已经通过在组合 Show 上执行 Rasterize 并使用使用 Texture 映射并使用 Orthographic 投影查看的对象栅格化 3D 图来验证此公式,它与预测结果相匹配。将对象Inset“复制/粘贴”到GraphPlot,然后进行光栅化,我得到的图像比预期的要薄一个像素。


(来源:yaroslavvb.com

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

【讨论】:

  • +1 非常好...我建议在代码标题中添加“Mathematica 8”警告。你可以毫无羞耻地接受你的答案:D
【解决方案2】:

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

这是我对第一个示例中最后一部分的编辑,这次考虑到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]

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

【讨论】:

  • 很好,看起来它适用于这个图表。我认为检查内部/外部图形会使其过于复杂。问题真的是——如何在给定的比例下在 VertexRenderingFunction 内的 Inset 内渲染 GraphPlots。 IE,我想要GraphPlot的10个像素的图像对应x个逻辑距离单位,其中x是全局变量。
【解决方案3】:

您可以通过更改 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->All 选项并添加了对 Framed 的包装调用。这是因为我发现我无法充分控制前者生成的框架之外的边距。我可能在某处遗漏了一些选项,但 Framed 可以按照我想要的方式工作,无需大惊小怪。

我在 ImageSize 选项中添加了一个明确的高度。没有它,Mathematica 会尝试使用一些算法来选择高度,这些算法通常会产生令人满意的结果,但有时(如这里)会感到困惑。

出于同样的原因,我添加了 AspectRatio 选项 -- Mathematica 尝试选择一个“令人愉悦”的纵横比(通常是黄金分割率),但我们不希望在这里。

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

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

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

【讨论】:

  • 更好,但比例仍然不统一,即带有“2,3”的部分相对于上图被拉伸
  • @Yaroslav Bulatov:我更新了我的答案,通过添加 PlotRange 选项来解决您的评论。
  • 谢谢,这解决了“非均匀比例”问题,虽然它增加了空间浪费的问题(与之前的解决方案相反)
【解决方案4】:

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

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

您可以对第二个示例执行相同的操作。 此外,如果您不想浪费垂直空间,您可以编写一个快速函数来检查要显示哪些节点,并且只在需要的行上保留重影。

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

【讨论】:

  • 我以为我可以在 vrfOuter 中使用“PlotRange->{0,4}”获得相同的效果,但结果更奇怪。目标是 1) 不浪费空间和 2) 大小一致。您提出的可能可行,我想我真正想要的是了解 GraphPlot/Inset/PlotRange 如何协同工作
猜你喜欢
  • 2012-07-06
  • 2017-09-29
  • 2018-04-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-04-10
  • 1970-01-01
  • 2023-03-13
相关资源
最近更新 更多