Obsah
1. Programová tvorba diagramů v jazyku Clojure s využitím knihovny Rhizome
2. Nástroj Rhizome spojující možnosti jazyka Clojure a nástroje Graphviz
3. Balíček vizualizačních nástrojů Graphviz
4. Základní typy grafů podporovaných nástrojem Graphviz
5. Instalace knihovny Rhizome s využitím nástroje Leiningen
6. Vyzkoušení základních možností projektu Rhizome z interaktivní smyčky REPL
9. Složitější graf, v němž mají uzly více potomků
10. Specifikace stylu vykreslení grafu
12. Modifikace barvy uzlu, popř. stylu vykreslení
13. Modifikace stylu vykreslení hran
14. Shlukování uzlů (clustering)
15. Konkrétní specifikace shlukovaných uzlů
16. Kombinace předchozích příkladů
17. Repositář s demonstračním příkladem
18. Odkazy na články s tématem programové tvorby grafů a diagramů
19. Odkazy na předchozí části seriálu o programovacím jazyku Clojure
1. Programová tvorba diagramů v jazyku Clojure s využitím knihovny Rhizome
Na stránkách Roota jsme se již několikrát setkali s nástroji určenými pro tvorbu různých typů grafů a diagramů (viz též osmnáctou kapitolu s odkazy na tyto články). V naprosté většině případů byla deklarace grafu provedena v nějakém doménově specifickém jazyce neboli DSL (Domain Specific Language). Připomeňme si, že se jednalo například o nástroje Graphviz (což je ve skutečnosti sada několika relativně samostatných nástrojů), dále o nástroj Ditaa pro přidání grafů a diagramů do dokumentů tvořených v Asciidocu, a zapomenout nesmíme ani na propracovaný nástroj plantUML, který podporuje hned několik typů diagramů, přičemž pro každý diagram je použit poněkud odlišný DSL. Do této kategorie může spadat i GNUplot.
Obrázek 1: Diagram vytvořený aplikací Ditaa.
Doménově specifické jazyky jsou velmi důležitou součástí informatiky a mnohé z nich jsou velmi úspěšné a rozšířené do mnoha oblastí. Za připomenutí stojí například jazyk pro popis regulárních výrazů, jenž je podporován jak mnoha nástroji, tak i knihovnami, popř. je přímo součástí některých obecných programovacích jazyků (Perl apod.). I pro popisy grafů a diagramů jsou DSL obecně velmi dobrou volbou, ovšem na druhou stranu je nutné podotknout, že pro určité použití je výhodnější nechat si graf či diagram vykreslit přímo na základě zdrojových kódů nějakého obecného (resp. přesněji řečeno univerzálního) programovacího jazyka. Velmi dobrým příkladem jsou například vývojové diagramy (flow chart), jenž mohou být užitečným komunikačním prostředkem mezi vývojovým týmem a zákazníkem, ovšem jejich ruční tvorba v DSL může být zdlouhavá a navíc i neintuitivní – výhodnější v tomto případě bude vytvoření vývojového diagramu přímo ze zdrojového kódu (což umožňuje například nástroj PyFlowchart, jímž se budeme zabývat příště).
Obrázek 2: Další diagram vytvořený aplikací Ditaa.
2. Nástroj Rhizome spojující možnosti jazyka Clojure a nástroje Graphviz
Dnes se seznámíme s velmi jednoduše použitelným nástrojem určeným pro vizualizaci grafů. Tento nástroj se jmenuje Rhizome (česky oddenek) a jeho primárním jazykem pro popis grafů není specializovaný DSL, ale univerzální programovací jazyk Clojure (to tedy znamená, že náš seriál začneme zdánlivě méně praktickým jazykem, v mnoha oblastech je však opak pravdou, i když Clojure stále nepatří mezi mainstream). Nástroj Rhizome, který bude popsán, používá pro vizualizaci grafů Linuxový nástroj Graphviz, ovšem nepřímo – uživatel-programátor nebude muset přímo s Graphviz pracovat ani se učit jeho DSL (který ovšem není pro většinu běžných typů grafů příliš složitý – jeden příklad psaný přímo pro Graphviz si ostatně ukážeme později).
Obrázek 3: Tento obrázek je použit jako logo nástroje Rhizome.
Zdroj: https://github.com/ztellman/rhizome
Nástroj Rhizome dokáže pracovat s mnoha typy grafů; ovšem základní dělení je na grafy s orientovanými hranami a na grafy s hranami neorientovanými. Graf je primárně reprezentován datovou strukturou programovacího jazyka Clojure (typicky nějakou formou mapy) a následně je interně převeden do doménově specifického jazyka nástroje Graphviz. Dále je Graphviz zavolán a výsledek je zobrazen v nově vytvořeném okně, popř. je uložen na disk ve formě rastrového obrázku, dokumentu PDF, vektorové kresby v PostScriptu atd.
Obrázek 4: Diagram aktivit vygenerovaný nástrojem plantUML. Ve výchozím nastavení jsou uzly umístěné pod sebe a šipky mezi nimi směřují vertikálně dolů (v případě, že není použito rozvětvení, kterým se budeme zabývat níže).
Jaké jsou přednosti tohoto řešení? Především je možné stále pracovat pouze v Clojure bez nutnosti znát interní jazyk nástroje Graphviz. Dále se – díky homoikonicitě programovacího jazyka Clojure – mohou namísto statických dat představujících uzly grafu a relace mezi uzly použít přímo funkce, které údaje o grafu dynamicky generují. A konečně je možné Rhizome relativně snadno navázat na celou další sadu nástrojů vytvořených pro Clojure a například využít možnosti generování grafu se závislostmi a tranzitivními závislostmi volaného přímo z nástroje Leiningen (atd. atd.).
Obrázek 5: PlantUML – jednoduché rozvětvení reprezentované v diagramu aktivit.
3. Balíček vizualizačních nástrojů Graphviz
V mnoha dokumentech, popř. v různých reportech (někdy i automaticky generovaných) je nutné vytvářet diagramy s uzly propojenými neorientovanými či orientovanými hranami. Pro tento účel je vhodné ve většině případů použít balíček nástrojů nazvaný Graphviz. V tomto balíčku nalezneme především utilitu nazvanou dot, která na základě textové definice orientovaného či neorientovaného grafu vytvoří rastrový či vektorový obrázek s grafem, přičemž je možné zvolit, jaký algoritmus bude použit pro rozmístění uzlů a hran na vytvořeném obrázku. Modifikovat lze i další vlastnosti grafu, především styl vykreslení hran, rozmístění uzlů grafu apod. Textová definice grafu používá jednoduchý popisný jazyk (samozřejmě doménově specifický), který je v současnosti podporován i několika dalšími nástroji a stává se tak nepsaným standardem pro mnoho programů pracujících s grafovými strukturami.
Obrázek 6: Nástroj Graphviz lze použít i pro vizualizaci objektů uložených v operační paměti (Python).
4. Základní typy grafů podporovaných nástrojem Graphviz
Podívejme se nyní na velmi jednoduchý demonstrační příklad – konkrétně na graf se třemi uzly a čtyřmi hranami (spojnicemi) mezi těmito uzly. Takový graf může být popsán v textovém souboru s následující strukturou:
Obsah souboru graph1.dot:
graph languages { Algol -- "K&R C"; "K&R C" -- "ANSI C"; Algol -- Perl; Algol -- "PL/I"; }
Povšimněte si, že u některých uzlů bylo zapotřebí uzavřít jejich názvy do uvozovek. Není těžké zjistit proč – objevují se zde totiž znaky, které by mohly být interpretovány odlišným způsobem (ampersand, mezera, lomítko) a uzavřením do uvozovek se (většina) těchto problémů automaticky vyřeší. Celý soubor začíná klíčovým slovem graph značící neorientovaný graf; ve skutečnosti je však možné celou hlavičku bloku vynechat (což ostatně dělají i některé nástroje), nebo naopak do hlavičky doplnit další informace o stylu zobrazení grafu. Neorientované hrany mezi jednotlivými uzly jsou naznačeny dvojicí znaků „–“ a jak uvidíme v následujícím odstavci, bude tato značka u orientovaných grafů nahrazena šipkou (což je dobrá mnemotechnická pomůcka).
O automatické rozmístění uzlů i hran a následné vykreslení diagramu do rastrového obrázku s formátem PNG se postará tento příkaz:
$ dot graph1.dot -T png & graph1.png
Výsledek si můžete prohlédnout na dalším obrázku zobrazeném pod tímto odstavcem. Příkaz dot v tomto případě vytvořil klasický strom s kořenem Algol, což nám v tomto případě zcela vyhovuje.
Obrázek 7: Neorientovaný graf vytvořený na základě definičního souboru graph1.dot popsaného výše.
Zajímavé je, že příkaz dot zapisuje obrázek (či popis vektorového výkresu) na standardní výstup, takže je možné použít přesměrování standardního výstupu do souboru. Alternativní výstupní formáty jsou svg, gif, jpeg a ps (PostScript):
$ dot graph1.dot -Tps & graph1.ps $ dot graph1.dot -Tsvg & graph1.svg $ dot graph1.dot -Tgif & graph1.gif $ dot graph1.dot -Tjpeg & graph1.jpeg
Kromě příkazu dot lze použít i příkazy neato, fdp či sfdp, které se odlišují algoritmem použitým pro rozmístění uzlů diagramu.
Podívejme se nyní na tvorbu grafu, v němž jsou neorientované hrany mezi uzly nahrazeny šipkami, tedy běžnými orientovanými hranami. Použijeme přitom stejné uzly (přesněji řečeno uzly se stejným textem), jako tomu bylo i v předchozím demonstračním příkladu. Změn v definičním textovém (zdrojovém) souboru je jen několik. První změnu můžeme vidět již na prvním řádku, protože se namísto klíčového slova graph používá slovo digraph. Dále se pak na dalších řádcích nahradila dvojice znaků „–“ za šipku „->“ naznačující přímo směr hrany:
Obsah souboru graph2.dot:
digraph languages { Algol -> "K&R C"; "K&R C" -> "ANSI C"; Algol -> Perl; Algol -> "PL/I"; }
Obrázek 8: Orientovaný graf vytvořený na základě definičního souboru graph2.dot.
U uzlů a hran je možné specifikovat různé vlastnosti a ovlivnit tak výsledné zobrazení grafu. Podívejme se na jednoduchou úpravu spočívající v tom, že se nejdříve deklaruje styl dvou uzlů (tvar uzlu + jeho barva) a následně se tyto uzly použijí pro specifikaci topologie grafu:
Obsah souboru graph3.dot:
digraph languages { Algol [shape=box]; Algol [color="#ff0000"]; "ANSI C" [color="#00ff00"]; Algol -> "K&R C"; "K&R C" -> "ANSI C"; Algol -> Perl; Algol -> "PL/I"; }
Obrázek 9: Orientovaný graf vytvořený na základě definičního souboru graph3.dot.
5. Instalace knihovny Rhizome s využitím nástroje Leiningen
Po krátké odbočce se vraťme k programovacímu jazyku Clojure a nástroji Rhizome.
Nástroj Rhizome je dodáván formou knihovny programovacího jazyka Clojure a lze ho tedy nainstalovat například s využitím nástroje Leiningen, s nímž jsme se již na stránkách Roota taktéž seznámili (viz odkazy v devatenácté kapitole). Pro instalaci nejdříve vytvoříme nový prázdný projekt naprogramovaný v jazyku Clojure. Pro vytvoření nového projektu použijeme tento příkaz:
$ lein new app rhizome-1
Výsledkem by měla být následující adresářová struktura:
. ├── CHANGELOG.md ├── doc │ └── intro.md ├── LICENSE ├── project.clj ├── README.md ├── resources ├── src │ └── rhizome_1 │ └── core.clj └── test └── rhizome_1 └── core_test.clj
Nyní do projektového souboru, jenž se jmenuje project.clj, doplníme nový balíček, na němž projekt závisí. Původní podoba projektového souboru může vypadat následovně (může se lišit podle nainstalované verze jazyka Clojure):
(defproject rhizome-1 "0.1.0-SNAPSHOT" :description "FIXME: write description" :url "http://example.com/FIXME" :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0" :url "https://www.eclipse.org/legal/epl-2.0/"} :dependencies [[org.clojure/clojure "1.10.1"]] :main ^:skip-aot rhizome-1.core :target-path "target/%s" :profiles {:uberjar {:aot :all :jvm-opts ["-Dclojure.compiler.direct-linking=true"]}})
Tento soubor upravíme následovně (změna je zvýrazněna tučným písmem):
(defproject rhizome-1 "0.1.0-SNAPSHOT" :description "FIXME: write description" :url "http://example.com/FIXME" :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0" :url "https://www.eclipse.org/legal/epl-2.0/"} :dependencies [[org.clojure/clojure "1.10.1"] [rhizome "0.2.9"]] :main ^:skip-aot rhizome-1.core :target-path "target/%s" :profiles {:uberjar {:aot :all :jvm-opts ["-Dclojure.compiler.direct-linking=true"]}})
Nyní je již vše připraveno pro instalaci nástroje Rhizome. K tomu použijeme tento příkaz:
$ lein deps Retrieving rhizome/rhizome/0.2.9/rhizome-0.2.9.pom from clojars Retrieving rhizome/rhizome/0.2.9/rhizome-0.2.9.jar from clojars
{[clojure-complete "0.2.5" :exclusions [[org.clojure/clojure]]] nil, [nrepl "0.7.0" :exclusions [[org.clojure/clojure]]] nil, [org.clojure/clojure "1.10.1"] {[org.clojure/core.specs.alpha "0.2.44"] nil, [org.clojure/spec.alpha "0.2.176"] nil}, [rhizome "0.2.9"] nil, [venantius/ultra "0.6.0"] {[grimradical/clj-semver "0.3.0" :exclusions [[org.clojure/clojure]]] nil, [io.aviso/pretty "0.1.35"] nil, [mvxcvi/puget "1.1.0"] {[fipp "0.6.14"] {[org.clojure/core.rrb-vector "0.0.13"] nil}, [mvxcvi/arrangement "1.1.1"] nil}, [mvxcvi/whidbey "2.1.0"] {[org.clojure/data.codec "0.1.1"] nil}, [org.clojars.brenton/google-diff-match-patch "0.1"] nil, [robert/hooke "1.3.0"] nil, [venantius/glow "0.1.5" :exclusions [[hiccup] [garden]]] {[clj-antlr "0.2.3"] {[org.antlr/antlr4-runtime "4.5.3"] nil, [org.antlr/antlr4 "4.5.3"] nil}, [instaparse "1.4.1"] nil}}}
6. Vyzkoušení základních možností projektu Rhizome z interaktivní smyčky REPL
Samotný nástroj Rhizome lze pochopitelně otestovat jednoduše takovým způsobem, že se bude upravovat zdrojový kód projektu (src/rhizome1/core.clj, například takto:
(ns rhizome-1.core) (require '[rhizome.viz :as viz]) (defn -main [& args] (let [graph {:a [:b :c] :b [:c] :c [:a]}] (viz/view-graph (keys graph) graph :node->descriptor (fn [n] {:label n}))))
A následně se bude projekt opakovaně spouštět příkazem:
$ lein run
S výsledkem:
Obrázek 10: Výsledek předchozího příkladu.
To však není – alespoň ne v případě programovacího jazyka Clojure – ten nejlepší možný způsob, protože inicializace Clojure i virtuálního stroje Javy není v žádném případě okamžitá. Namísto toho je výhodnější spustit interaktivní smyčku REPL (Read-eval-print loop) a možnosti nástroje Rhizome zkoušet interaktivně s možností návratu k předchozím datům či příkazům atd. A pravděpodobně nejvýhodnější je mít spuštěnu jak smyčku REPL, tak i váš oblíbený programátorský textový editor či vývojové prostředí, které s REPL dokáže komunikovat (příklady jsme si již ukazovali pro Vim, pro Emacs lze použít Cider – viz například tento odkaz). Spusťme si tedy smyčku REPL, a to přímo v tom adresáři, v němž se nachází výše zmíněný projekt:
$ lein repl
Po několika sekundách by se měla objevit zpráva o spuštění a inicializaci prostředí jazyka Clojure:
nREPL server started on port 43485 on host 127.0.0.1 - nrepl://127.0.0.1:43485 REPL-y 0.4.4, nREPL 0.7.0 Clojure 1.10.1 OpenJDK 64-Bit Server VM 1.8.0_191-b12 Docs: (doc function-name-here) (find-doc "part-of-name-here") Source: (source function-name-here) Javadoc: (javadoc java-object-or-class-here) Exit: Control+D or (exit) or (quit) Results: Stored in vars *1, *2, *3, an exception in *e rhizome-1.core=>
Obrázek 11: Kombinace REPLu jazyka Clojure a programátorského textového editoru. Jednotlivé formy se do REPLu posílají přes tmux.
Nyní je nutné knihovnu Rhizome načíst. To lze provést několika způsoby – buď s využitím formy use nebo pomocí formy require. Ve druhém případě je možné specifikovat jmenný alias, který bude použit:
rhizome-1.core=> (use 'rhizome.dot) rhizome-1.core=> (use 'rhizome.viz)
popř.:
rhizome-1.core=> (require '[rhizome.dot :as dot]) rhizome-1.core=> (require '[rhizome.viz :as viz])
7. Zobrazení grafů
Pro zobrazení grafů v novém okně (vytvořeném přímo v prostředí virtuálního stroje Javy) se používá funkce nazvaná view-graph ze jmenného prostoru rhizome.viz:
rhizome-1.core=> (doc viz/view-graph) ------------------------- rhizome.viz/view-graph ([nodes adjacent & {:keys [directed? vertical? options node->descriptor edge->descriptor cluster->parent node->cluster cluster->descriptor], :or {directed? true, vertical? true, node->descriptor (constantly nil), edge->descriptor (constantly nil), cluster->parent (constantly nil), node->cluster (constantly nil), cluster->descriptor (constantly nil)}, :as graph-descriptor}]) Takes a graph descriptor in the style of `graph->dot`, and displays a rendered image.
Alternativně je možné graf uložit do souboru (typicky do rastrového obrázku) funkcí rhizome.viz/save-graph:
rhizome-1.core=> (doc viz/save-graph) ------------------------- rhizome.viz/save-graph ([nodes adjacent & {:keys [filename], :as options}]) Takes a graph descriptor in the style of `graph->dot`, and saves the image to disk.
Kromě toho je možné graf převést do souboru .dot, který je dále zpracovatelný příkazem dot. Vygenerovaný soubor může vypadat následovně:
digraph { graph[dpi=100, rankdir=TP] node[fontname="Monospace"] edge[fontname="Monospace"] node5972[label="queue"] node5973[label="Kafka"] node5974[label="backend"] node5975[label="notificator"] node5976[label="storage"] node5977[label="service"] node5978[label="proxy"] node5979[label="db writer"] node5980[label="frontend"] node5981[label="ggregator"] node5972 -> node5974[label=""] node5973 -> node5975[label=""] node5973 -> node5979[label=""] node5974 -> node5980[label=""] node5975 -> node5972[label=""] node5976 -> node5981[label=""] node5977 -> node5978[label=""] node5978 -> node5974[label=""] node5979 -> node5976[label=""] node5981 -> node5978[label=""] }
Vraťme se však k zobrazení grafů. Definice grafu je tvořena mapou, v níž klíče jsou tvořeny názvy uzlů a hodnoty vektory, taktéž se jmény uzlů. Každý prvek v mapě tedy představuje jednu či více hran mezi uzly:
(def g {"Algol" ["K&R C"], "K&R C" ["ANSI C"]})
Vytvoříme soubor s prvním grafem:
; ulozeni grafu bez popisku uzlu (viz/save-graph (keys g) g :filename "g01.png")
Obrázek 12: Neúplný graf vytvořený prvním příkladem.
Tento graf neobsahuje jména uzlů, protože jsme žádným způsobem nespecifikovali, jak se mají vytvořit. Pro specifikaci funkce, která pro daný uzel vytvoří i jeho jméno (popř. další atributy – opět reprezentované v mapě) se použije nepovinný parametr :node->descriptor:
; ulozeni grafu i s popisky uzlu (viz/save-graph (keys g) g :filename "g02.png" :node->descriptor (fn [n] {:label n}))
Nyní bude výsledek vypadat následovně:
Obrázek 13: Neúplný graf vytvořený druhým příkladem.
8. Koncové uzly grafu
Aby se korektně zobrazil i koncový uzel grafu (či koncové uzly), je nutné je v grafu specifikovat formou nového prvku. Nestačí tedy, že specifikujeme, že hrana má vést k danému uzlu – uzel musí být v mapě uložen i formou klíče. Vzhledem k tomu, že z takového uzlu nevedou žádné hrany, bude hodnota prvku nil, popř. prázdný vektor nebo seznam:
; graf, kde i uzly existuji ve forme zaznamu (def g {"Algol" ["K&R C"], "K&R C" ["ANSI C"], "ANSI C" nil})
Uložení rastrového obrázku s grafem:
; ulozeni grafu i s popisky uzlu (viz/save-graph (keys g) g :filename "g03.png" :node->descriptor (fn [n] {:label n}))
Obrázek 14: Úplný graf vytvořený třetím příkladem.
9. Složitější graf, v němž mají uzly více potomků
Podívejme se nyní na nepatrně složitější graf, jehož uzly mají několik potomků. Opět nesmíme zapomenout na explicitní zápis koncových uzlů formou prvků s klíčem, ovšem bez hodnoty:
; slozitejsi graf, kde maji uzly vice potomku (def g {"Algol" ["K&R C", "Pascal", "Modula"], "K&R C" ["ANSI C", "C with classes"], "C with classes" ["C++"], "ANSI C" ["C89"], "C89" ["C99"], "Pascal" nil, "Modula" nil, "C++" nil, "C99" nil})
Vykreslení grafu:
; ulozeni grafu i s popisky uzlu (viz/save-graph (keys g) g :filename "g04.png" :node->descriptor (fn [n] {:label n}))
S výsledkem:
Obrázek 15: Úplný graf vytvořený čtvrtým příkladem.
10. Specifikace stylu vykreslení grafu
Nepovinným parametrem :directed? lze specifikovat, zda mají být hrany orientované nebo neorientované:
; zmena stylu vykresleni grafu - bez orientovanych hran (viz/save-graph (keys g) g :filename "g05.png" :node->descriptor (fn [n] {:label n}) :directed? nil)
Obrázek 16: Graf s neorientovanými hranami („bez šipek“).
Zvolit lze i vykreslení hran spíše horizontálním směrem (koncové uzly jsou v tomto případě zobrazeny vpravo):
; zmena stylu vykresleni grafu - horizontalne orientovany (viz/save-graph (keys g) g :filename "g06.png" :node->descriptor (fn [n] {:label n}) :vertical? false)
Výsledek:
Obrázek 17: Graf orientovaný horizontálně.
11. Popisky hran v grafu
Podobně jako je možné specifikovat data či funkci použitou pro přidání jmen uzlů do grafu, můžeme totéž udělat s hranami. Obecně se jméno hrany počítá ve funkci, která akceptuje dva uzly (které hrana spojuje) a výsledkem má být mapa obsahující v klíči :label jméno. To může obsahovat například i HTML entity atd.:
; ulozeni grafu i s popisky uzlu a hran (viz/save-graph (keys g) g :filename "g07.png" :node->descriptor (fn [n] {:label n}) :edge->descriptor (fn [n1 n2] {:label (str n1 "→" n2)}))
Výsledek:
Obrázek 18: Graf vykreslený předchozím příkladem.
Pokud vám nevyhovuje použití anonymních funkcí (lambd), samozřejmě je možné použít i běžné pojmenované funkce:
(defn make-node-descriptor [n] {:label n}) (defn make-edge-descriptor [n1 n2] {:label (str n1 "→" n2)}) ; dtto ovsem bez pouziti anonymnich funkci ; ulozeni grafu i s popisky uzlu a hran (viz/save-graph (keys g) g :filename "g08.png" :node->descriptor make-node-descriptor :edge->descriptor make-edge-descriptor)
Obrázek 19: Graf vykreslený předchozím příkladem.
12. Modifikace barvy uzlu, popř. stylu vykreslení
U uzlů je možné specifikovat různé styly vykreslení. Mezi měnitelný styl patří barva okraje, popř. barva pozadí. Podívejme se nyní na poněkud umělý příklad, v němž se barva uzlu mění programově na základě jeho jména (ovšem v praxi lze samozřejmě použít složitější funkci, popř. konstantní data):
(defn make-node-descriptor-2 [n] {:label n :color (if (= n "ANSI C") "darkgreen" "#000000")}) ; styly vykresleni uzlu (viz/save-graph (keys g) g :filename "g09.png" :node->descriptor make-node-descriptor-2 :edge->descriptor make-edge-descriptor)
Nyní bude barva uzlu „ANSI C“ odlišná od ostatních uzlů:
Obrázek 20: Graf vykreslený předchozím příkladem.
13. Modifikace stylu vykreslení hran
Nic nám nebrání ani ve změně barvy hran. V dalším příkladu se rozlišuje mezi třemi typy hran – hrana z uzlu „ANSI C“, hrana do uzlu „ANSI C“ a ostatní hrany:
(defn make-edge-descriptor-2 [n1 n2] (let [color (cond (= n1 "ANSI C") "red" (= n2 "ANSI C") "blue" :else "black")] {:label (str n1 "→" n2) :color color}))
Vykreslení grafu s využitím výše definované funkce make-edge-descriptor-2:
; styly vykresleni hran (viz/save-graph (keys g) g :filename "g10.png" :node->descriptor make-node-descriptor-2 :edge->descriptor make-edge-descriptor-2)
Obrázek 21: Graf vykreslený předchozím příkladem.
14. Shlukování uzlů (clustering)
Knihovna Rhizome podporuje shlukování uzlů, tj. provádět takzvaný clustering. V následujícím demonstračním příkladu si graf pro clustering pouze připravíme. Vzhledem k tomu, že funkce, která každému uzlu přiřadí cluster, je nastavená na identity, bude každý uzel umístěn ve svém vlastním clusteru:
; clustering (viz/save-graph (keys g) g :filename "g11.png" :node->descriptor make-node-descriptor :edge->descriptor make-edge-descriptor :node->cluster identity)
S výsledkem:
Obrázek 22: Graf vykreslený předchozím demonstračním příkladem.
user=> (doc identity) ------------------------- clojure.core/identity ([x]) Returns its argument.
15. Konkrétní specifikace shlukovaných uzlů
Jednou z možností specifikace, které uzly mají být sdruženy, je použití nepovinné volby :cluster->parent. Té se předává mapa se specifikací vztahu mezi dvojicí uzlů, například „ANSI C a C89 jsou sdruženy“ a současně „C89 a C99“ jsou sdruženy. Výsledkem je následující hierarchie:
; clustering (viz/save-graph (keys g) g :filename "g12.png" :node->descriptor make-node-descriptor :edge->descriptor make-edge-descriptor :node->cluster identity :cluster->parent {"ANSI C" "C89" "C89" "C99" })
S výsledkem:
Obrázek 23: Graf vykreslený předchozím demonstračním příkladem.
Poněkud odlišná specifikace vztahů mezi uzly:
; clustering (viz/save-graph (keys g) g :filename "g13.png" :node->descriptor make-node-descriptor :edge->descriptor make-edge-descriptor :node->cluster identity :cluster->parent {"ANSI C" "C89" "C with classes" "C++" "Pascal" "Modula"})
S výsledkem:
Obrázek 24: Graf vykreslený předchozím demonstračním příkladem.
V praxi může být užitečnější použití nepovinného parametru :node->cluster, v němž jsou jména uzlů namapována na jména clusterů (a tato jména mohou být klidně totožná se jménem uzlu). Opět si pochopitelně ukážeme jednoduchý demonstrační příklad se třemi clustery pojmenovanými „C“, „C++“ a „Wirth“:
; clustering (viz/save-graph (keys g) g :filename "g14.png" :node->descriptor make-node-descriptor :edge->descriptor make-edge-descriptor :node->cluster {"ANSI C" "C" "C89" "C" "C99" "C" "C with classes" "C++" "C++" "C++" "Pascal" "Wirth" "Modula" "Wirth"})
Nyní bude graf vypadat odlišně:
Obrázek 25: Graf vykreslený předchozím demonstračním příkladem.
16. Kombinace předchozích příkladů
Většinu voleb a možností je možné různým způsobem kombinovat (nevylučují se), což je ostatně ukázáno v dnešním posledním úryvku zdrojového kódu:
; slozitejsi graf, kde maji uzly vice potomku (def g {"Algol" ["K&R C", "Pascal", "Modula"], "K&R C" ["ANSI C", "C with classes"], "C with classes" ["C++"], "ANSI C" ["C89"], "C89" ["C99"], "Pascal" nil, "Modula" nil, "C++" nil, "C99" nil}) (defn make-node-descriptor-2 [n] {:label n :color (if (= n "ANSI C") "darkgreen" "#000000")}) (defn make-edge-descriptor-2 [n1 n2] (let [color (cond (= n1 "ANSI C") "red" (= n2 "ANSI C") "blue" :else "black")] {:label (str n1 "→" n2) :color color})) ; kombinace predchozich prikladu (viz/save-graph (keys g) g :filename "g15.png" :node->descriptor make-node-descriptor-2 :edge->descriptor make-edge-descriptor-2 :node->cluster {"ANSI C" "C" "C89" "C" "C99" "C" "C with classes" "C++" "C++" "C++" "Pascal" "Wirth" "Modula" "Wirth"} :directed? nil)
Obrázek 26: Graf vykreslený předchozím demonstračním příkladem s kombinací více možností ovlivňujících tvar i styl vykreslení grafu.
Na závěr si necháme vygenerovat soubor typu .dot s popisem grafu:
(spit "test.dot" (dot/graph->dot (keys g) g :node->descriptor make-node-descriptor-2 :edge->descriptor make-edge-descriptor-2 :node->cluster {"ANSI C" "C" "C89" "C" "C99" "C" "C with classes" "C++" "C++" "C++" "Pascal" "Wirth" "Modula" "Wirth"} :directed? nil))
S tímto výsledkem:
graph { graph[dpi=100, rankdir=TP] node[fontname="Monospace"] edge[fontname="Monospace"] node5972[label="K&R C", color="#000000"] node5973[label="Algol", color="#000000"] subgraph cluster5974 { graph[dpi=100, fontname="Monospace", rankdir=TP] node[fontname="Monospace"] edge[fontname="Monospace"] node5975[label="C89", color="#000000"] node5976[label="ANSI C", color="darkgreen"] node5977[label="C99", color="#000000"] } subgraph cluster5978 { graph[dpi=100, fontname="Monospace", rankdir=TP] node[fontname="Monospace"] edge[fontname="Monospace"] node5979[label="Pascal", color="#000000"] node5980[label="Modula", color="#000000"] } subgraph cluster5981 { graph[dpi=100, fontname="Monospace", rankdir=TP] node[fontname="Monospace"] edge[fontname="Monospace"] node5982[label="C++", color="#000000"] node5983[label="C with classes", color="#000000"] } node5972 -- node5976[label="K&R C→ANSI C", color="blue"] node5972 -- node5983[label="K&R C→C with classes", color="black"] node5975 -- node5977[label="C89→C99", color="black"] node5973 -- node5972[label="Algol→K&R C", color="black"] node5973 -- node5979[label="Algol→Pascal", color="black"] node5973 -- node5980[label="Algol→Modula", color="black"] node5976 -- node5975[label="ANSI C→C89", color="red"] node5983 -- node5982[label="C with classes→C++", color="black"]
17. Repositář s demonstračním příkladem
Projekt, v němž je definována závislost na knihovně s nástrojem Rhizome (a nepřímo na Graphviz), byl uložen do Git repositáře, který je dostupný na adrese https://github.com/tisnik/clojure-examples/. Konkrétně se jedná o projekt nazvaný rhizome-1 (https://github.com/tisnik/clojure-examples/tree/master/rhizome-1).
18. Odkazy na články s tématem programové tvorby grafů a diagramů
V této kapitole jsou uvedeny odkazy na předchozí články, v nichž jsme se zabývali tvorbou různých typů grafů a diagramů – a to v naprosté většině případů s využitím nějakého doménově specifického jazyka neboli DSL (Domain Specific Language):
- Nástroje pro tvorbu UML diagramů
https://www.root.cz/clanky/nastroje-pro-tvorbu-uml-diagramu/ - Nástroje pro tvorbu UML diagramů z příkazové řádky
https://www.root.cz/clanky/nastroje-pro-tvorbu-uml-diagramu-z-prikazove-radky/ - Nástroje pro tvorbu UML diagramů z příkazové řádky (II)
https://www.root.cz/clanky/nastroje-pro-tvorbu-uml-diagramu-z-prikazove-radky-ii/ - Nástroje pro tvorbu grafů a diagramů z příkazové řádky
https://www.root.cz/clanky/nastroje-pro-tvorbu-grafu-a-diagramu-z-prikazove-radky/ - Sledování správy paměti v Pythonu s využitím nástroje objgraph
https://www.root.cz/clanky/sledovani-spravy-pameti-v-pythonu-s-vyuzitim-nastroje-objgraph/
19. Odkazy na předchozí části seriálu o programovacím jazyku Clojure
- Clojure 1: Úvod
http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm/ - Clojure 2: Symboly, kolekce atd.
http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm-2-cast/ - Clojure 3: Funkcionální programování
http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm-3-cast-funkcionalni-programovani/ - Clojure 4: Kolekce, sekvence a lazy sekvence
http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm-4-cast-kolekce-sekvence-a-lazy-sekvence/ - Clojure 5: Sekvence, lazy sekvence a paralelní programy
http://www.root.cz/clanky/clojure-a-bezpecne-aplikace-pro-jvm-sekvence-lazy-sekvence-a-paralelni-programy/ - Clojure 6: Podpora pro paralelní programování
http://www.root.cz/clanky/programovaci-jazyk-clojure-6-futures-nejsou-jen-financni-derivaty/ - Clojure 7: Další funkce pro paralelní programování
http://www.root.cz/clanky/programovaci-jazyk-clojure-7-dalsi-podpurne-prostredky-pro-paralelni-programovani/ - Clojure 8: Identity, stavy, neměnné hodnoty a reference
http://www.root.cz/clanky/programovaci-jazyk-clojure-8-identity-stavy-nemenne-hodnoty-a-referencni-typy/ - Clojure 9: Validátory, pozorovatelé a kooperace s Javou
http://www.root.cz/clanky/programovaci-jazyk-clojure-9-validatory-pozorovatele-a-kooperace-mezi-clojure-a-javou/ - Clojure 10: Kooperace mezi Clojure a Javou
http://www.root.cz/clanky/programovaci-jazyk-clojure-10-kooperace-mezi-clojure-a-javou-pokracovani/ - Clojure 11: Generátorová notace seznamu/list comprehension
http://www.root.cz/clanky/programovaci-jazyk-clojure-11-generatorova-notace-seznamu-list-comprehension/ - Clojure 12: Překlad programů z Clojure do bajtkódu JVM I:
http://www.root.cz/clanky/programovaci-jazyk-clojure-12-preklad-programu-z-clojure-do-bajtkodu-jvm/ - Clojure 13: Překlad programů z Clojure do bajtkódu JVM II:
http://www.root.cz/clanky/programovaci-jazyk-clojure-13-preklad-programu-z-clojure-do-bajtkodu-jvm-pokracovani/ - Clojure 14: Základy práce se systémem maker
http://www.root.cz/clanky/programovaci-jazyk-clojure-14-zaklady-prace-se-systemem-maker/ - Clojure 15: Tvorba uživatelských maker
http://www.root.cz/clanky/programovaci-jazyk-clojure-15-tvorba-uzivatelskych-maker/ - Programovací jazyk Clojure – triky při práci s řetězci
http://www.root.cz/clanky/programovaci-jazyk-clojure-triky-pri-praci-s-retezci/ - Programovací jazyk Clojure – triky při práci s kolekcemi
http://www.root.cz/clanky/programovaci-jazyk-clojure-triky-pri-praci-s-kolekcemi/ - Programovací jazyk Clojure – práce s mapami a množinami
http://www.root.cz/clanky/programovaci-jazyk-clojure-prace-s-mapami-a-mnozinami/ - Programovací jazyk Clojure – základy zpracování XML
http://www.root.cz/clanky/programovaci-jazyk-clojure-zaklady-zpracovani-xml/ - Programovací jazyk Clojure – testování s využitím knihovny Expectations
http://www.root.cz/clanky/programovaci-jazyk-clojure-testovani-s-vyuzitim-knihovny-expectations/ - Programovací jazyk Clojure – některé užitečné triky použitelné (nejenom) v testech
http://www.root.cz/clanky/programovaci-jazyk-clojure-nektere-uzitecne-triky-pouzitelne-nejenom-v-testech/ - Enlive – výkonný šablonovací systém pro jazyk Clojure
http://www.root.cz/clanky/enlive-vykonny-sablonovaci-system-pro-jazyk-clojure/ - Nástroj Leiningen a programovací jazyk Clojure: tvorba vlastních knihoven pro veřejný repositář Clojars
http://www.root.cz/clanky/nastroj-leiningen-a-programovaci-jazyk-clojure-tvorba-vlastnich-knihoven-pro-verejny-repositar-clojars/ - Novinky v Clojure verze 1.8.0
http://www.root.cz/clanky/novinky-v-clojure-verze-1–8–0/ - Asynchronní programování v Clojure s využitím knihovny core.async
http://www.root.cz/clanky/asynchronni-programovani-v-clojure-s-vyuzitim-knihovny-core-async/ - Asynchronní programování v Clojure s využitím knihovny core.async (pokračování)
http://www.root.cz/clanky/asynchronni-programovani-v-clojure-s-vyuzitim-knihovny-core-async-pokracovani/ - Asynchronní programování v Clojure s využitím knihovny core.async (dokončení)
http://www.root.cz/clanky/asynchronni-programovani-v-clojure-s-vyuzitim-knihovny-core-async-dokonceni/ - Vytváříme IRC bota v programovacím jazyce Clojure
http://www.root.cz/clanky/vytvarime-irc-bota-v-programovacim-jazyce-clojure/ - Gorilla REPL: interaktivní prostředí pro programovací jazyk Clojure
https://www.root.cz/clanky/gorilla-repl-interaktivni-prostredi-pro-programovaci-jazyk-clojure/ - Multimetody v Clojure aneb polymorfismus bez použití OOP
https://www.root.cz/clanky/multimetody-v-clojure-aneb-polymorfismus-bez-pouziti-oop/ - Práce s externími Java archivy v programovacím jazyku Clojure
https://www.root.cz/clanky/prace-s-externimi-java-archivy-v-programovacim-jazyku-clojure/ - Clojure 16: Složitější uživatelská makra
http://www.root.cz/clanky/programovaci-jazyk-clojure-16-slozitejsi-uzivatelska-makra/ - Clojure 17: Využití standardních maker v praxi
http://www.root.cz/clanky/programovaci-jazyk-clojure-17-vyuziti-standardnich-maker-v-praxi/ - Clojure 18: Základní techniky optimalizace aplikací
http://www.root.cz/clanky/programovaci-jazyk-clojure-18-zakladni-techniky-optimalizace-aplikaci/ - Clojure 19: Vývojová prostředí pro Clojure
http://www.root.cz/clanky/programovaci-jazyk-clojure-19-vyvojova-prostredi-pro-clojure/ - Clojure 20: Vývojová prostředí pro Clojure (Vimu s REPL)
http://www.root.cz/clanky/programovaci-jazyk-clojure-20-vyvojova-prostredi-pro-clojure-integrace-vimu-s-repl/ - Clojure 21: ClojureScript aneb překlad Clojure do JS
http://www.root.cz/clanky/programovaci-jazyk-clojure-21-clojurescript-aneb-preklad-clojure-do-javascriptu/ - Leiningen: nástroj pro správu projektů napsaných v Clojure
http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure/ - Leiningen: nástroj pro správu projektů napsaných v Clojure (2)
http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-2/ - Leiningen: nástroj pro správu projektů napsaných v Clojure (3)
http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-3/ - Leiningen: nástroj pro správu projektů napsaných v Clojure (4)
http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-4/ - Leiningen: nástroj pro správu projektů napsaných v Clojure (5)
http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-5/ - Leiningen: nástroj pro správu projektů napsaných v Clojure (6)
http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-6/ - Programovací jazyk Clojure a databáze (1.část)
http://www.root.cz/clanky/programovaci-jazyk-clojure-a-databaze-1-cast/ - Pluginy pro Leiningen
http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-pluginy-pro-leiningen/ - Programovací jazyk Clojure a knihovny pro práci s vektory a maticemi
http://www.root.cz/clanky/programovaci-jazyk-clojure-a-knihovny-pro-praci-s-vektory-a-maticemi/ - Programovací jazyk Clojure a knihovny pro práci s vektory a maticemi (2)
http://www.root.cz/clanky/programovaci-jazyk-clojure-a-knihovny-pro-praci-s-vektory-a-maticemi-2/ - Programovací jazyk Clojure: syntéza procedurálních textur s využitím knihovny Clisk
http://www.root.cz/clanky/programovaci-jazyk-clojure-synteza-proceduralnich-textur-s-vyuzitim-knihovny-clisk/ - Programovací jazyk Clojure: syntéza procedurálních textur s využitím knihovny Clisk (2)
http://www.root.cz/clanky/programovaci-jazyk-clojure-synteza-proceduralnich-textur-s-vyuzitim-knihovny-clisk-2/ - Seesaw: knihovna pro snadnou tvorbu GUI v jazyce Clojure
http://www.root.cz/clanky/seesaw-knihovna-pro-snadnou-tvorbu-gui-v-jazyce-clojure/ - Seesaw: knihovna pro snadnou tvorbu GUI v azyce Clojure (2)
http://www.root.cz/clanky/seesaw-knihovna-pro-snadnou-tvorbu-gui-v-jazyce-clojure-2/ - Seesaw: knihovna pro snadnou tvorbu GUI v jazyce Clojure (3)
http://www.root.cz/clanky/seesaw-knihovna-pro-snadnou-tvorbu-gui-v-jazyce-clojure-3/ - Programovací jazyk Clojure a práce s Gitem
http://www.root.cz/clanky/programovaci-jazyk-clojure-a-prace-s-gitem/ - Programovací jazyk Clojure a práce s Gitem (2)
http://www.root.cz/clanky/programovaci-jazyk-clojure-a-prace-s-gitem-2/ - Programovací jazyk Clojure: syntéza procedurálních textur s využitím knihovny Clisk (dokončení)
http://www.root.cz/clanky/programovaci-jazyk-clojure-synteza-proceduralnich-textur-s-vyuzitim-knihovny-clisk-dokonceni/ - Pixie: lehký skriptovací jazyk s „kouzelnými“ schopnostmi
https://www.root.cz/clanky/pixie-lehky-skriptovaci-jazyk-s-kouzelnymi-schopnostmi/ - Programovací jazyk Pixie: funkce ze základní knihovny a použití FFI
https://www.root.cz/clanky/programovaci-jazyk-pixie-funkce-ze-zakladni-knihovny-a-pouziti-ffi/ - Novinky v Clojure verze 1.9.0
https://www.root.cz/clanky/novinky-v-clojure-verze-1–9–0/ - Validace dat s využitím knihovny spec v Clojure 1.9.0
https://www.root.cz/clanky/validace-dat-s-vyuzitim-knihovny-spec-v-clojure-1–9–0/ - Použití jazyka Gherkin při tvorbě testovacích scénářů pro aplikace psané v Clojure
https://www.root.cz/clanky/pouziti-jazyka-gherkin-pri-tvorbe-testovacich-scenaru-pro-aplikace-psane-v-nbsp-clojure/ - Použití jazyka Gherkin při tvorbě testovacích scénářů pro aplikace psané v Clojure (2)
https://www.root.cz/clanky/pouziti-jazyka-gherkin-pri-tvorbe-testovacich-scenaru-pro-aplikace-psane-v-nbsp-clojure-2/ - Incanter: prostředí pro statistické výpočty s grafickým výstupem založené na Clojure
https://www.root.cz/clanky/incanter-prostredi-pro-statisticke-vypocty-s-grafickym-vystupem-zalozene-na-clojure/ - Incanter: operace s maticemi
https://www.root.cz/clanky/incanter-operace-s-maticemi/ - Interpret programovacího jazyka Clojure integrovaný do Jupyter Notebooku
https://www.root.cz/clanky/interpret-programovaciho-jazyka-clojure-integrovany-do-jupyter-notebooku/ - Babashka: interpret Clojure určený pro rychlé spouštění utilit z příkazového řádku
https://www.root.cz/clanky/babashka-interpret-clojure-urceny-pro-rychle-spousteni-utilit-z-prikazoveho-radku/ - Pokročilý streaming založený na Apache Kafce, jazyku Clojure a knihovně Jackdaw
https://www.root.cz/clanky/pokrocily-streaming-zalozeny-na-apache-kafce-jazyku-clojure-a-knihovne-jackdaw/ - Pokročilý streaming založený na Apache Kafce, jazyku Clojure a knihovně Jackdaw (2. část)
https://www.root.cz/clanky/pokrocily-streaming-zalozeny-na-apache-kafce-jazyku-clojure-a-knihovne-jackdaw-2-cast/ - Pokročilý streaming založený na projektu Apache Kafka, jazyku Clojure a knihovně Jackdaw (streamy a kolony)
https://www.root.cz/clanky/pokrocily-streaming-zalozeny-na-projektu-apache-kafka-jazyku-clojure-a-knihovne-jackdaw-streamy-a-kolony/ - Řídicí struktury využitelné v programovacím jazyku Clojure
https://www.root.cz/clanky/ridici-struktury-vyuzitelne-v-programovacim-jazyku-clojure/ - Řídicí struktury využitelné v programovacím jazyku Clojure (dokončení)
https://www.root.cz/clanky/ridici-struktury-vyuzitelne-v-programovacim-jazyku-clojure-dokonceni/ - Formát EDN: extensible data notation
https://www.root.cz/clanky/format-edn-extensible-data-notation/ - Formát EDN: extensible data notation (dokončení)
https://www.root.cz/clanky/format-edn-extensible-data-notation-dokonceni/ - Čtyři různé podoby datové struktury map v programovacím jazyku Clojure
https://www.root.cz/clanky/ctyri-ruzne-podoby-datove-struktury-map-v-programovacim-jazyku-clojure/
20. Odkazy na Internetu
- Rhizome
https://github.com/ztellman/rhizome - Napkin
https://github.com/pinetr2e/napkin - Swagger to UML
https://github.com/nlohmann/swagger_to_uml - pydiagrams
https://github.com/billingtonm/pydiagrams - graphviz(3) – Linux man page
https://linux.die.net/man/3/graphviz - dot(1) – Linux man page
https://linux.die.net/man/1/dot - neato(1) – Linux man page
https://linux.die.net/man/1/neato - twopi(1) – Linux man page
https://linux.die.net/man/1/twopi - circo(1) – Linux man page
https://linux.die.net/man/1/circo - fdp(1) – Linux man page
https://linux.die.net/man/1/fdp - sfdp(1) – Linux man page
https://linux.die.net/man/1/sfdp - Plain-text diagrams take shape in Asciidoctor!
http://asciidoctor.org/news/2014/02/18/plain-text-diagrams-in-asciidoctor/ - Graphviz – Graph Visualization Software
http://www.graphviz.org/ - graphviz (Manual Page)
http://www.root.cz/man/7/graphviz/ - dot (Manual page)
http://www.root.cz/man/1/dot/ - dot (Manual v PDF)
https://graphviz.org/pdf/dot.1.pdf - Ditaa home page
http://ditaa.sourceforge.net/ - Ditaa introduction
http://ditaa.sourceforge.net/#intro - Ditaa usage
http://ditaa.sourceforge.net/#usage - Node, Edge and Graph Attributes
http://www.graphviz.org/doc/info/attrs.html - Graphviz (Wikipedia)
http://en.wikipedia.org/wiki/Graphviz