2010-11-20 51 views
9

更新10/27:我已經提出了在答案中實現一致縮放的詳細步驟。基本上對於每個圖形對象,您需要將所有填充/邊距修復爲0,並手動指定plotRange和imageSize,以便1)plotRange包含所有圖形2)imageSize = scale * plotRangeGraphPlots的一致大小

現在仍然可以確定如何執行1)in充分通用性,對於圖形由點及粗線有效的解決方案(AbsoluteThickness)給出


我使用的「插圖」在VertexRenderingFunction和「Ve​​rtexCoordinates」,以保證圖的子圖中一致的外觀。這些子圖被繪製爲另一個圖的頂點,使用「插入」。有兩個問題,一個是結果框沒有在圖形周圍裁剪(即,一個頂點的圖仍然放在一個大框中),另一個是大小之間存在奇怪的變化(你可以看到一個框是垂直的) 。任何人都可以看到解決這些問題的方法?

這與如何保持頂點大小看起來相同的早期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]]] 

的圖形操作的美觀的可視化任何其他建議都歡迎。

+1

圖像疊加和情節時,我從來沒有取得一致的圖像大小,不僅與圖形,而且還。希望有人持有魔杖並借給我們... – 2010-11-20 04:19:26

+0

你正在看到一些非常漂亮的圖表。當你完成這個項目時,你正在努力的是你會爲我們其他人發佈一個包嗎? – Simon 2010-11-20 08:18:12

+0

出於好奇,你爲什麼寫自己的'Intersection'版本? – Simon 2010-11-20 08:24:29

回答

5

以下是實現精確控制圖形對象相對比例所需的步驟。

要實現一致的縮放比例,需要明確指定輸入座標範圍(常規座標)和輸出座標範圍(絕對座標)。常規座標範圍取決於PlotRangePlotRangePadding(可能還有其他選項?)。絕對座標範圍取決於ImageSizeImagePadding(可能還有其他選項?)。對於GraphPlot,指定PlotRangeImageSize就足夠了。

要創建呈現在預先確定的縮放圖形對象時,你需要弄清楚PlotRange需要充分包含的對象,相應ImageSize並返回與指定的這些設置Graphics對象。要想弄清楚在涉及粗線時必要的PlotRange,處理AbsoluteThickness更容易,稱之爲abs。要完全包含這些行,可以採用包含端點的最小的PlotRange,然後用abs/2偏移最小x和最大y邊界,並用(abs/2 + 1)偏移最大x和最小y邊界。請注意,這些是輸出座標。

當組合多個scale-calibrated圖形對象時,需要重新計算PlotRange/ImageSize併爲組合的Graphics對象顯式設置它們。

要插入scale-calibrated對象到GraphPlot您需要確保用於定位的自動GraphPlot的座標位於相同範圍內。爲此,您可以選擇幾個角落節點,手動修復它們的位置,然後讓剩餘的自動定位完成。

基元Line/JoinedCurve/FilledCurve根據線是否(幾乎)共線來渲染連接/線帽,因此需要手動檢測共線性。

使用這種方法,渲染圖像應當具有寬度等於

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

首先額外1是避免「柵欄柱錯誤」和第二個加1是就向右添加所需的額外像素確保粗線不切斷

我已經對結合ShowRasterize和光柵化與對象的3D繪圖使用Texture映射的驗證了這個公式,並與Orthographic凸出觀看它與預測結果相符。將對象Inset上的「複製/粘貼」複製到GraphPlot中,然後進行光柵化處理,我得到的圖像比預測的要薄一個像素。

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

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

+1非常好...我建議添加一個「Mathematica 8」在代碼標題中發出警告。你可以接受你的答案而不會感到羞恥:D – 2010-11-28 00:55:50

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}}的內部圖...

+0

我以爲我可以得到相同的效果與「 PlotRange - > {0,4}「在vrfOuter中,但結果更奇怪。目標是1)沒有浪費的空間和2)一致的大小。你提出的建議可能會起作用,我想我真正想要的是理解GraphPlot/Inset/PlotRange如何協同工作 – 2010-11-20 08:45:21

2

OK,在我前面的回答您的評論(這是一種不同的方法)來獲得,你說的問題是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] 

alt text

注: {.05, .04}將不得不被修改爲外部圖形的大小和佈局更改... 要自動化整個事情,您可能需要一個很好的方式讓內部和外部圖形對象互相檢查...

+0

好,看起來像它適用於此圖。我認爲檢查內部/外部圖形會使它太複雜。這個問題真的是 - 如何以給定的比例在VertexRenderingFunction內部的Inset中渲染GraphPlot。 IE,我想要10個像素的GraphPlot圖像對應x個邏輯距離單位,其中x是一個全局變量。 – 2010-11-20 11:31:35

2

你可以通過改變vrfOuter如下解決您的第一個例子:

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

我刪除了幀 - >所有選項,並添加加框包裹調用。這是因爲我發現我無法充分控制前者產生的邊框外邊緣。我可能會在某處丟失一些選項,但誣陷按我想要的方式工作而不會大驚小怪。

我給ImageSize選項添加了一個明確的高度。如果沒有它,Mathematica會嘗試使用一些算法來選擇一個高度,這些算法通常會產生令人滿意的結果,但有時(如此處)會變得困惑。

我加爲同樣的原因ASPECTRATIO選項 - 數學會嘗試選擇「賞心悅目」的縱橫比(通常爲黃金比率),但我們不想在這裏。

我添加了PlotRange選項來確保每個子圖都使用相同的座標系。沒有它,Mathematica通常會選擇一個顯示所有節點的最小範圍。

結果如下所示。我把它作爲一個練習留給讀者來調整箭頭,利潤等)

rendered result

編輯:添加PlotRange選項響應由@Yaroslav Bulatov

評論
+0

它更好,但規模仍然不統一,即與「2,3」的部分相對於頂部圖被拉伸 – 2010-11-21 20:15:06

+0

@Yaroslav Bulatov:我通過添加PlotRange選項更新了我的答案以解決您的評論。 – WReach 2010-11-21 20:48:31

+0

謝謝,它修復了「非均勻尺度」問題,雖然它增加了浪費空間的問題(這是前面解決方案的另一種方式) – 2010-11-21 21:43:10

相關問題