Aby hierarchia Graphviz przypominała wcięty konspekt na Macu, przypisz każdemu węzłowi wiersz i współrzędną wcięcia, a następnie renderuj za pomocą neato -n2. Zachowuje to krawędzie między rodzicami i dziećmi, a jednocześnie pozwala kontrolować kolejność czytania.

Ten przewodnik tworzy przykład z sześcioma węzłami i eksportuje go do PDF. Cel jest konkretny: jeden węzeł w każdym wierszu, potomkowie bezpośrednio pod swoim rodzicem oraz jednakowe wcięcie węzłów na tym samym poziomie. Graphviz opisuje renderowanie ze stałymi pozycjami za pomocą opcji wiersza poleceń -n.

Dlaczego zwykłe drzewo Graphviz nie wygląda jak konspekt?

Hierarchia opisuje relacje; wcięty konspekt określa również kolejność czytania. W tym przykładzie korzeń zawiera dwie gałęzie, a potomkowie pierwszej gałęzi muszą pojawić się przed drugą gałęzią. Oczekiwana kolejność wygląda tak:

Węzeł R
    Węzeł A
        Węzeł C
        Węzeł D
    Węzeł B
        Węzeł E

Relacje w grafie to: od korzenia do A, od korzenia do B, od A do C, od A do D i od B do E. Między D i B nie ma relacji tylko dlatego, że B znajduje się w następnym wierszu. W języku DOT skierowane krawędzie zapisuje się operatorem ->; deklaracje węzłów i ich atrybuty prezentacji to oddzielne elementy języka. Zobacz oficjalny opis języka DOT.

Silnik Graphviz dot tworzy rysunki hierarchiczne lub warstwowe. Kieruje krawędzie w tym samym kierunku, próbując jednocześnie ograniczyć ich przecinanie się i długość. Takie podejście sprawdza się w diagramie z rozgałęzieniami, ale nie definiuje układu konspektu z jednym węzłem w każdym wierszu, pokazanego powyżej. Zobacz dokumentację układu dot.

Kilka znanych atrybutów rozwiązuje tylko część problemu:

Ustawienie Opisane działanie Czego nie określa
rankdir=LR Zmienia kierunek układu na poziomy, od lewej do prawej Nie określa pionowej kolejności konspektu
rank=same Umieszcza węzły podgrafu na tej samej randze Nie przypisuje każdemu węzłowi osobnego wiersza konspektu
ordering=out Wymusza wyświetlanie wychodzących krawędzi od lewej do prawej, zgodnie z kolejnością w danych wejściowych Nie określa wcięć zależnych od głębokości ani współrzędnych wierszy

Te ograniczenia wynikają z oficjalnej dokumentacji rankdir, rank i ordering. W przypadku dokładnego układu wierszy z tego artykułu bezpośredniejsze jest użycie jawnych współrzędnych niż łączenie tych atrybutów.

Jak przygotować Graphviz na Macu?

Otwórz Terminal. Jeśli korzystasz już z Homebrew, zainstaluj Graphviz za pomocą poniższego polecenia. Oficjalna strona pobierania Graphviz wymienia tę metodę instalacji na Macu:

brew install graphviz

Jeśli zamiast tego korzystasz z MacPorts, ta sama strona pobierania podaje polecenie sudo port install graphviz. Wybierz menedżer pakietów, którego już używasz. Jeśli nie masz żadnego z nich, przed powrotem do tego przewodnika skorzystaj z odpowiedniego odnośnika instalacyjnego na tej stronie.

Sprawdź, czy renderer jest dostępny:

neato -V

Opcja -V wyświetla informacje o wersji Graphviz. Zachowaj podaną wersję, jeśli porównujesz wyniki na różnych komputerach; nie musisz używać konkretnej wersji wymienionej w tym artykule. Graphviz opisuje tę opcję w dokumentacji wiersza poleceń.

Jak utworzyć wciętą hierarchię?

Poniższy kompletny blok powłoki tworzy nowy folder roboczy i zapisuje w nim plik DOT. Uruchom go w Terminal; po zakończeniu zostanie wyświetlona ścieżka folderu. Cytowany ogranicznik heredoc zachowuje tekst DOT w podanej postaci.

outline_dir=$(mktemp -d "$HOME/graphviz-outline.XXXXXX")
if [ -n "$outline_dir" ] && cd "$outline_dir"; then
  cat > hierarchy.dot <<'DOT'
digraph Hierarchy {
  graph [splines=ortho, bgcolor=white];
  node [
    shape=box,
    width=1.2,
    height=0.35,
    fixedsize=true,
    fontname="Courier",
    fontsize=12
  ];

  r [label="Node R", pos="100,300"];
  a [label="Node A", pos="136,252"];
  c [label="Node C", pos="172,204"];
  d [label="Node D", pos="172,156"];
  b [label="Node B", pos="136,108"];
  e [label="Node E", pos="172,60"];

  r -> a;
  r -> b;
  a -> c;
  a -> d;
  b -> e;
}
DOT
  pwd
fi

Pozycje węzłów wyznaczają układ konspektu. Każdy kolejny wiersz przesuwa się w dół o 48 punktów, a każdy kolejny poziom głębokości — w prawo o 36 punktów. To przykładowe odstępy, a nie wartości domyślne Graphviz. Dokumentacja pos określa, że pozycje odnoszą się do środków węzłów, a neato interpretuje pozycje jako punkty, gdy używa się go z -n.

Węzeł Głębokość Wiersz, licząc od zera Pozycja środka
Węzeł R 0 0 100,300
Węzeł A 1 1 136,252
Węzeł C 2 2 172,204
Węzeł D 2 3 172,156
Węzeł B 1 4 136,108
Węzeł E 2 5 172,60

W tym układzie reguły współrzędnych to x = 100 + 36 × depth i y = 300 − 48 × row. Zwróć uwagę na przejście od węzła D do węzła B: wiersz się przesuwa, ale głębokość maleje. To przejście sprawia, że rysunek przypomina konspekt, a nie łańcuch.

Wszystkie pola mają te same wymiary, więc jednakowe współrzędne środków wyrównują również ich lewe krawędzie. Sztuczne etykiety o jednakowej długości ułatwiają porównanie położenia tekstu. Przy fixedsize=true Graphviz używa określonych wymiarów węzłów zamiast powiększać pola, aby zmieścić ich zawartość; zbyt długie etykiety powodują wyświetlenie ostrzeżenia. Zobacz opis fixedsize.

Jak renderować i sprawdzić wynik?

W tej samej sesji Terminal i folderze roboczym uruchom:

neato -n2 -Tpdf hierarchy.dot -o hierarchy.pdf

Opcja -n2 zachowuje podany układ węzłów bez korekty nakładania się i oblicza trasy krawędzi, które nie mają własnych pozycji. Każdy węzeł musi więc mieć wartość pos. Opcje -Tpdf i -o wybierają format PDF i jego miejsce docelowe. Te działania opisano w dokumentacji wiersza poleceń.

Otwórz folder roboczy w Finder, a następnie otwórz hierarchy.pdf. Wymagana jest kompilacja Graphviz zawierająca renderer Cairo, jak podano na stronie Graphviz poświęconej wyjściu PDF. Przykład sprawdzono pod kątem zgodności z dokumentacją, ale nie przetestowano renderowania w tym środowisku, ponieważ Graphviz nie był w nim dostępny.

Sprawdź osobno trzy rzeczy:

  1. Czytanie w dół: R, A, C, D, B, E powinny znajdować się w kolejnych wierszach.
  2. Porównanie wcięć: A i B powinny być wyrównane, a C, D i E — wyrównane o jeden krok dalej w prawo.
  3. Prześledzenie relacji: R łączy się z A i B; A z C i D; B z E.

Dwie pierwsze kontrole oceniają konstrukcję opartą na współrzędnych. Trzecia sprawdza sam graf. Rozdzielenie tych kontroli pomaga wykryć diagram, który wygląda uporządkowanie, ale zawiera błędną krawędź między rodzicem a dzieckiem.

Czy łączniki będą wyglądać dokładnie jak drzewo w przeglądarce plików?

Wiersze węzłów i wcięcia są określone jawnie; trasy łączników nadal są obliczane automatycznie. W przykładzie ustawiono splines=ortho, który prowadzi krawędzie odcinkami równoległymi do osi. Dokumentacja Graphviz stwierdza również, że ten sposób wyznaczania tras nie obsługuje portów, więc dodanie portów kompasowych nie jest udokumentowanym sposobem wymuszenia konkretnego układu załamań. Zobacz opis splines.

W rezultacie ta konstrukcja tworzy wciętą hierarchię z automatycznie wyznaczanymi łącznikami pod kątem prostym. Nie narzuca jednej wspólnej pionowej linii z dokładnie rozmieszczonymi punktami rozgałęzień. Jeśli taka geometria łączników jest niezbędna, wymaga dodatkowej konstrukcji z jawnie określonymi ścieżkami krawędzi; samo splines=ortho nie wyraża tego wymagania.

Aby uzyskać przejrzysty konspekt bez widocznych łączników, zmień ustawienie grafu na splines=none i uruchom renderowanie ponownie. Plik DOT nadal zawiera krawędzie między rodzicami i dziećmi, ale renderer pomija ich linie. To udokumentowana opcja renderowania opisana w tym samym opisie splines.

Jak dostosować przykład bez zmiany jego znaczenia?

Najpierw zapisz zamierzoną kolejność czytania niezależnie od listy krawędzi. Najpierw odwiedź rodzica, potem jego pierwsze dziecko i potomków tego dziecka, a następnie przejdź do kolejnego dziecka. Przypisz każdemu węzłowi w tej kolejności osobny wiersz. Potem przypisz wcięcie na podstawie głębokości.

Na przykład, aby dodać kolejne dziecko pod węzłem A po węźle D, wstaw je do kolejności czytania przed węzłem B. Ustaw jego głębokość na dwa, przesuń kolejne wiersze w dół i dodaj krawędź od A do nowego węzła. Istniejące relacje między rodzicami a dziećmi nie zmieniają się tylko dlatego, że zmieniono numery wierszy.

Jeśli zmodyfikowana wersja wygląda nieprawidłowo, zastosuj kolejno te wskazówki diagnostyczne:

  • Rysunek układa się inaczej: upewnij się, że polecenie nadal używa neato -n2. Przykład opiera się na podanych pozycjach, a nie na zwykłym układzie automatycznym.
  • Podczas renderowania zgłaszany jest brak pozycji: sprawdź wszystkie identyfikatory węzłów, również te pojawiające się tylko w instrukcjach krawędzi. W tym trybie pracy każdy z nich wymaga deklaracji z pozycją.
  • Etykieta wystaje poza pole: zwiększ wspólne wymiary pól albo skróć etykietę. Pola o stałym rozmiarze nie powiększają się automatycznie.
  • Po powiększeniu pól wiersze nakładają się na siebie: zwiększ odstęp między wierszami. Wybrany tryb renderowania zachowuje pozycje zamiast korygować nakładanie się.
  • Gałąź łączy się z niewłaściwym węzłem: popraw instrukcję krawędzi. Przesunięcie pola nie naprawi błędnej relacji.

Brak pozycji i nakładanie się węzłów opisano w dokumentacji Graphviz dotyczącej działania -n2; zachowanie etykiet opisano w części poświęconej fixedsize. Zwiększenie odstępów to odpowiednia korekta dla tego ręcznie rozmieszczonego przykładu.

Listę krawędzi traktuj jako źródło relacji w hierarchii, a tabelę współrzędnych jako źródło informacji o prezentacji. W przypadku niewielkiego diagramu na Macu taki podział ułatwia utrzymanie: zmieniaj krawędzie, gdy zmienia się hierarchia, wiersze — gdy zmienia się kolejność czytania, a odstępy — gdy etykiety potrzebują więcej miejsca.

Źródła