代码之家  ›  专栏  ›  技术社区  ›  Yaroslav Bulatov

葡萄园大小一致

  •  9
  • Yaroslav Bulatov  · 技术社区  · 15 年前

    更新10/27 :我已经给出了实现一致性的详细步骤。基本上,对于每个图形对象,您需要将所有填充/边距固定为0,并手动指定plotRange和imageSize,以便1)plotRange包括所有图形2)imageSize=scale*plotRange

    现在仍然可以肯定的是,1)在完全通用的情况下,给出了一个由点和粗线(绝对厚度)组成的图形的解决方案


    我在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]]]
    

    欢迎对图形操作的美观可视化提出任何其他建议。

    4 回复  |  直到 6 年前
        1
  •  5
  •   Glorfindel Doug L.    6 年前

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

    要实现一致的比例,需要显式指定输入坐标范围(常规坐标)和输出坐标范围(绝对坐标)。规则坐标范围取决于 PlotRange , PlotRangePadding (可能还有其他选择?).绝对坐标范围取决于 ImageSize , ImagePadding (可能还有其他选择?)。对于 GraphPlot ,仅需指定 绘图范围 图像大小 .

    要创建按预定比例呈现的图形对象,您需要了解 绘图范围 需要完全包含对象,对应 图像大小 然后回来 Graphics 指定了这些设置的对象。找出必要的 绘图范围 当涉及到粗线条时,处理起来更容易 AbsoluteThickness ,称之为 abs . 要完全包含这些行,您可以采取最小的 绘图范围 它包括端点,然后通过ABS/2偏移最小X和最大Y边界,并通过(ABS/ 2+1)偏移最大X和最小Y边界。注意这些是输出坐标。

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

    插入 刻度盘校准 对象到 葡萄园 你需要确保自动 葡萄园 定位在同一范围内。为此,您可以选择几个角节点,手动确定它们的位置,然后让自动定位来完成其余的工作。

    原语 Line / JoinedCurve / FilledCurve 根据直线是否(几乎)共线,以不同的方式渲染连接/封口,因此需要手动检测共线性。

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

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

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

    我已经验证了这个公式 Rasterize 论合并 Show 并使用 Texture 并以 Orthographic 投影结果与预测结果吻合。对对象执行“复制/粘贴” Inset 进入之内 葡萄园 ,然后光栅化,得到的图像比预期的薄一个像素。


    (来源: 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]]]
    
        2
  •  2
  •   Simon    15 年前

    好的,在您对我之前的回答(这是一种不同的方法)的评论中,您说问题在于GraphPlot/Inset/PlotRange之间的交互。如果不指定 Inset ,然后它从 ImageSize 插图的 Graphics 反对。

    下面是我对第一个例子中最后一部分的编辑,这次考虑到 大小 插图 图。

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

    注意 {.05, .04} 当外部图形的大小和布局改变时,必须修改。。。 要使整个过程自动化,您可能需要一种内部和外部图形对象相互检查的好方法。。。

        3
  •  2
  •   WReach    15 年前

    你可以通过改变 弗福特 具体如下:

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

    我移除了 框架->全部 选项并将包装调用添加到 装裱 . 这是因为我发现我无法充分控制由前者生成的帧外的边距。我可能错过了一些选择,但是 装裱 按我想的方式工作,不必大惊小怪。

    我在 图像大小 选择。如果没有它,Mathematica会尝试使用一些算法来选择高度,这些算法通常会产生令人满意的结果,但有时(如本文所示)会感到困惑。

    我添加了 纵横比 出于同样的原因——Mathematica试图选择一个“令人满意的”长宽比(通常是黄金比例),但我们这里不希望这样。

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

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

    rendered result

    编辑 :添加了 绘图范围 选项以响应@Yaroslav Bulatov的评论

        4
  •  1
  •   Simon    15 年前

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

    (*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}} 对于内部图。。。