Согласованный размер для GraphPlots - PullRequest
9 голосов
/ 20 ноября 2010

Обновление 10/27 : я подробно описал шаги для достижения согласованной шкалы в ответе. В основном для каждого графического объекта вам нужно исправить все отступы / поля на 0 и вручную указать plotRange и imageSize, чтобы 1) plotRange включал всю графику 2) imageSize = scale * plotRange

Все еще теперь уверен, как это сделать 1) в полной общности, решение, которое работает для Графика, состоящей из точек и толстых линий (AbsoluteThickness), дается


Я использую «Inset» в VertexRenderingFunction и «VertexCoordinates», чтобы гарантировать согласованное отображение среди подграфов графа. Эти подграфы нарисованы как вершины другого графа с использованием «Inset». Есть две проблемы, одна из которых заключается в том, что получившиеся блоки не обрезаются вокруг графа (т. Е. Граф с одной вершиной все еще помещается в большую рамку), а другая заключается в том, что существуют странные различия между размерами (вы можете видеть, что одна ячейка вертикальная) , Кто-нибудь может найти способ обойти эти проблемы?

Это связано с более ранним вопросом о том, как сохранить размеры вершин одинаковыми, и хотя предложение Майкла Пилата об использовании 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 ]

5 голосов
/ 28 ноября 2010

Вот шаги, необходимые для достижения точного контроля относительных масштабов графических объектов.

Для достижения согласованного масштаба необходимо явно указать диапазон входных координат (регулярные координаты) и диапазон выходных координат (абсолютные координаты). Обычный диапазон координат зависит от PlotRange, PlotRangePadding (и, возможно, других параметров?). Абсолютный диапазон координат зависит от ImageSize, ImagePadding (и, возможно, других параметров?). Для GraphPlot достаточно указать PlotRange и ImageSize.

Чтобы создать объект 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 - это дополнительный пиксель, необходимый для добавления справа, чтобы убедиться, что толстые линии не обрезаются

Я проверил эту формулу, выполнив Rasterize для комбинированного Show и растеризовав трехмерный график с объектами, отображенными с помощью 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]]]
2 голосов
/ 20 ноября 2010

ОК, в своем комментарии к моему предыдущему ответу (это другой подход) вы сказали, что проблема заключалась во взаимодействии между 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

nb {.05, .04} придется изменять по мере изменения размера и компоновки внешнего графа ... Для автоматизации всего процесса может потребоватьсяхороший способ для внутренних и внешних графических объектов проверять друг друга ...

2 голосов
/ 21 ноября 2010

Вы можете исправить свой первый пример, изменив 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 и добавил вызов переноса в Рамка .Это потому, что я считаю, что не могу адекватно контролировать поля за пределами кадра, созданного первым.Я мог бы где-то пропустить какую-то опцию, но В рамке работает так, как я хочу, без суеты.

Я добавил явную высоту к опции ImageSize .Без этого Mathematica пытается выбрать высоту, используя некоторый алгоритм, который в основном дает приятные результаты, но иногда (как здесь) запутывается.

Я добавил опцию AspectRatio по той же причине -Mathematica пытается выбрать «приятное» соотношение сторон (обычно это «Золотое сечение»), но мы не хотим этого здесь.

Я добавил опцию PlotRange , чтобы убедиться, что каждый подграф используетта же система координат.Без этого Mathematica обычно выбирает минимальный диапазон, который показывает все узлы.

Результаты показаны ниже.Я оставляю читателю в качестве упражнения настройку стрелок, полей и т. Д .;)

rendered result

Редактировать : добавлен PlotRange опция в ответ на комментарий @Yaroslav Bulatov

1 голос
/ 20 ноября 2010

В качестве быстрого взлома вы можете ввести граф-призрак, чтобы заставить все подграфы отображаться на одной и той же сетке. Вот моя модификация последней части вашего первого примера - мой граф-призрак является копией вашего исходного графа, но с номерами вершин, сделанными отрицательными.

(*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}} для внутренних графиков ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...