A tool to navigate the achivement in the source tree

Reading time: 530 minutes and 15 seconds with 116656 words. 本文总阅读量

We go to github, then download the souce code, go on looking into the code, dame it! So many codes ,so many branches, how to figure out the develop process?

Maybe you can use the sourceTree to help you visulize the source tree, or the right below tool for helping navigating the source tree in the time and compare with each other continually.

color block languages for modify,maybe you need sometimes in your blogs writing life.

 
diff --git a/.gitmodules b/.gitmodules
index f1b9cf5..a63cbff 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,6 @@
 [submodule "Prolog-Library-Collection"]
 	path = Prolog-Library-Collection
 	url = https://github.com/wouterbeek/Prolog-Library-Collection.git
+[submodule "plHtml"]
+	path = plHtml
+	url = https://github.com/wouterbeek/plHtml.git
diff --git a/Prolog-Library-Collection b/Prolog-Library-Collection
index 4a51c6e..184c33e 160000
--- a/Prolog-Library-Collection
+++ b/Prolog-Library-Collection
@@ -1 +1 @@
-Subproject commit 4a51c6e0936179bff57fda23686219e6a6cd3c9b
+Subproject commit 184c33eecf561c109919a7f765de0b1e53d99d00
diff --git a/plHtml b/plHtml
new file mode 160000
index 0000000..2c45ebc
--- /dev/null
+++ b/plHtml
@@ -0,0 +1 @@
+Subproject commit 2c45ebcd3f946c1e207a69ece1e2d730d4d090ed
 
 
diff --git a/Prolog-Library-Collection b/Prolog-Library-Collection
index 184c33e..03d4b46 160000
--- a/Prolog-Library-Collection
+++ b/Prolog-Library-Collection
@@ -1 +1 @@
-Subproject commit 184c33eecf561c109919a7f765de0b1e53d99d00
+Subproject commit 03d4b46ba9c6f658828ff843e5785c221ffc3a57
diff --git a/plHtml b/plHtml
index 2c45ebc..50743da 160000
--- a/plHtml
+++ b/plHtml
@@ -1 +1 @@
-Subproject commit 2c45ebcd3f946c1e207a69ece1e2d730d4d090ed
+Subproject commit 50743daad8dd0f3664e1cafbc14523a423314f84
 
 
diff --git a/gv_attr_type.pl b/gv_attr_type.pl
index c62d146..200c034 100644
--- a/gv_attr_type.pl
+++ b/gv_attr_type.pl
@@ -41,7 +41,7 @@
   ]
 ).
 :- reexport(
-  gv(gv_color),
+  plGraphViz(gv_color),
   [
     color//1, % +Color:compound
     colorList//1 % +ColorList:list(compound)
diff --git a/gv_attrs.pl b/gv_attrs.pl
index 81d51df..728640e 100644
--- a/gv_attrs.pl
+++ b/gv_attrs.pl
@@ -68,7 +68,7 @@ check_minimum(V, Min1):-
 
 gv_attrs_download:-
   gv_attrs_url(Url),
-  download_html(Url, Dom, [html_dialect(html4)]),
+  download_html(Url, Dom, [html_dialect(html4),verbose(silent)]),
 
   xpath(Dom, //table(@align=center), TableDom),
   % @tbd This does not work, since in `record_name(Element, Name)`,
diff --git a/gv_color.pl b/gv_color.pl
index fd22d35..ad18728 100644
--- a/gv_color.pl
+++ b/gv_color.pl
@@ -81,7 +81,7 @@ gv_color_url('http://www.graphviz.org/doc/info/colors.html').
 
 gv_color_download:-
   gv_color_url(Url),
-  download_html(Url, Dom, [html_dialect(html4)]),
+  download_html(Url, Dom, [html_dialect(html4),verbose(silent)]),
   xpath(Dom, //table(1), TableDom1),
   xpath(Dom, //table(2), TableDom2),
   maplist(assert_color_table, [x11,svg], [TableDom1,TableDom2]).
 
 
diff --git a/gv_attrs.pl b/gv_attrs.pl
index 728640e..b360039 100644
--- a/gv_attrs.pl
+++ b/gv_attrs.pl
@@ -14,16 +14,21 @@
 */
 
 :- use_module(library(apply)).
+:- use_module(library(persistency)).
 :- use_module(library(xpath)).
 
 :- use_module(dcg(dcg_content)).
 :- use_module(dcg(dcg_generic)).
+:- use_module(generics(db_ext)).
+:- use_module(os(file_ext)).
 
 :- use_module(plHtml(html)).
 :- use_module(plHtml(html_table)).
 
 :- use_module(plGraphViz(gv_attr_type)). % DCGs implementing attribute types.
 
+:- db_add_novel(user:prolog_file_type(log, logging)).
+
 %! gv_attr(
 %!   ?Name:atom,
 %!   ?UsedBy:list(oneof([cluster,edge,graph,node,subgraph])),
@@ -33,9 +38,18 @@
 %!   ?Notes:atom
 %! ) is nondet.
 
-:- dynamic(gv_attr/6).
+:- persistent(
+  gv_attr(
+    name:atom,
+    used_by:list(oneof([cluster,edge,graph,node,subgraph])),
+    types:list(atom),
+    default,
+    minimum,
+    notes:atom
+  )
+).
 
-:- initialization(gv_attrs_download).
+:- initialization(gv_attrs_init).
 
 
 
@@ -64,7 +78,20 @@ check_minimum(V, Min1):-
 
 
 
-% Download attributes from graphviz.org
+% INITIALIZATION
+
+%! assert_gv_attr_row(+Row:list(atom)) is det.
+
+assert_gv_attr_row([Name,UsedBy1,Types1,Default1,Minimum,Notes]):-
+  dcg_phrase(translate_usedby(UsedBy2), UsedBy1),
+  once(dcg_phrase(translate_type(Types2), Types1)),
+  sort(UsedBy2, UsedBy3),
+  translate_default(Default1, Default2),
+  assert_gv_attr(Name, UsedBy3, Types2, Default2, Minimum, Notes).
+
+
+%! gv_attrs_downloads is det.
+% Downloads the table describing GraphViz attributes from `graphviz.org`.
 
 gv_attrs_download:-
   gv_attrs_url(Url),
@@ -76,16 +103,56 @@ gv_attrs_download:-
   %%%%xpath(Dom, /html/body/table, TableDom),
 
   html_to_table(TableDom, _, Rows),
-  maplist(assert_gv_attr, Rows).
+  maplist(assert_gv_attr_row, Rows).
+
+
+%! gv_attrs_file(-File:atom) is det.
+
+gv_attrs_file(File):-
+  absolute_file_name(
+    data(gv_attrs),
+    File,
+    [access(write),file_type(logging)]
+  ).
+
+
+%! gv_attrs_init is det.
+
+gv_attrs_init:-
+  gv_attrs_file(File),
+  safe_db_attach(File),
+  file_age(File, Age),
+  gv_attrs_update(Age).
+
+
+%! gv_attrs_update(+Age:float) is det.
+
+% The persistent store is still fresh.
+gv_attrs_update(Age):-
+  once(gv_attr(_, _, _, _, _, _)),
+  Age < 3600, !.
+% The persistent store has become stale, so refresh it.
+gv_attrs_update(_):-
+  retractall_gv_attr(_, _, _, _, _, _),
+  gv_attrs_download.
+
+
+%! gv_attrs_url(-Url:url) is det.
 
 gv_attrs_url('http://www.graphviz.org/doc/info/attrs.html').
 
-assert_gv_attr([Name,UsedBy1,Types1,Default1,Minimum,Notes]):-
-  dcg_phrase(translate_usedby(UsedBy2), UsedBy1),
-  once(dcg_phrase(translate_type(Types2), Types1)),
-  sort(UsedBy2, UsedBy3),
-  translate_default(Default1, Default2),
-  assert(gv_attr(Name, UsedBy3, Types2, Default2, Minimum, Notes)).
+
+%! safe_db_attach(+File:atom) is det.
+
+safe_db_attach(File):-
+  exists_file(File), !,
+  db_attach(File, []).
+safe_db_attach(File):-
+  touch_file(File),
+  safe_db_attach(File).
+
+
+%! translate_default(+Default1:atom, -Default2:atom) is det.
 
 % The empty string is represented by the empty atom.
 translate_default('""', ''):- !.
@@ -93,12 +160,20 @@ translate_default('""', ''):- !.
 translate_default('<none>', _):- !.
 translate_default(Default, Default).
 
+
+%! translate_type(-Types:list(atom))// is det.
+
 translate_type([H|T]) -->
   {gv_attr_type(H)},
   atom(H),
   whites,
   translate_type(T).
-translate_type([]) --> [].
+translate_type([]) --> !, [].
+
+
+%! translated_usedby(
+%!   -UsedBy:list(oneof([cluster,edge,graph,node,subgraph]))
+%! )// is det.
 
 translate_usedby([cluster|T]) --> `C`, !, translate_usedby(T).
 translate_usedby([edge|T]) --> `E`, !, translate_usedby(T).
diff --git a/gv_color.pl b/gv_color.pl
index ad18728..cf92f56 100644
--- a/gv_color.pl
+++ b/gv_color.pl
@@ -18,20 +18,25 @@
 
 :- use_module(library(apply)).
 :- use_module(library(lists)).
+:- use_module(library(persistency)).
 :- use_module(library(xpath)).
 
 :- use_module(dcg(dcg_abnf)).
 :- use_module(dcg(dcg_cardinal)).
+:- use_module(generics(db_ext)).
+:- use_module(os(file_ext)).
 :- use_module(sparql(sparql_char)).
 
 :- use_module(plHtml(html)).
 :- use_module(plHtml(html_table)).
 
+:- db_add_novel(user:prolog_file_type(log, logging)).
+
 %! gv_color(?Colorscheme:oneof([svg,x11]), ?Color:atom) is nondet.
 
-:- dynamic(gv_color/2).
+:- persistent(gv_color(colorscheme:oneof([svg,x11]),color:atom)).
 
-:- initialization(gv_color_download).
+:- initialization(gv_color_init).
 
 
 
@@ -75,9 +80,9 @@ wc_weight(Float) -->
 
 
 
-% Initialization.
+% INITIALIZATION
 
-gv_color_url('http://www.graphviz.org/doc/info/colors.html').
+%! gv_color_download is det.
 
 gv_color_download:-
   gv_color_url(Url),
@@ -91,6 +96,52 @@ assert_color_table(Colorscheme, TableDom):-
   append(Rows, Cells),
   forall(
     member(Cell, Cells),
-    assert(gv_color(Colorscheme, Cell))
+    assert_gv_color(Colorscheme, Cell)
+  ).
+
+
+%! gv_color_file(-File:atom) is det.
+
+gv_color_file(File):-
+  absolute_file_name(
+    data(gv_color),
+    File,
+    [access(write),file_type(logging)]
   ).
 
+
+%! gv_color_init is det.
+
+gv_color_init:-
+  gv_color_file(File),
+  safe_db_attach(File),
+  file_age(File, Age),
+  gv_color_update(Age).
+
+
+%! gv_color_update(+Age:float) is det.
+
+% The persistent store is still fresh.
+gv_color_update(Age):-
+  once(gv_color(_, _)),
+  Age < 3600, !.
+% The persistent store has become stale, so refresh it.
+gv_color_update(_):-
+  retractall_gv_color(_, _),
+  gv_color_download.
+
+
+%! gv_color_url(-Url:url) is det.
+
+gv_color_url('http://www.graphviz.org/doc/info/colors.html').
+
+
+%! safe_db_attach(+File:atom) is det.
+
+safe_db_attach(File):-
+  exists_file(File), !,
+  db_attach(File, []).
+safe_db_attach(File):-
+  touch_file(File),
+  safe_db_attach(File).
+
 
 
diff --git a/gv_attrs.pl b/gv_attrs.pl
index b360039..fa499d0 100644
--- a/gv_attrs.pl
+++ b/gv_attrs.pl
@@ -130,7 +130,7 @@ gv_attrs_init:-
 % The persistent store is still fresh.
 gv_attrs_update(Age):-
   once(gv_attr(_, _, _, _, _, _)),
-  Age < 3600, !.
+  Age < 8640000, !.
 % The persistent store has become stale, so refresh it.
 gv_attrs_update(_):-
   retractall_gv_attr(_, _, _, _, _, _),
diff --git a/gv_color.pl b/gv_color.pl
index cf92f56..70e0495 100644
--- a/gv_color.pl
+++ b/gv_color.pl
@@ -124,7 +124,7 @@ gv_color_init:-
 % The persistent store is still fresh.
 gv_color_update(Age):-
   once(gv_color(_, _)),
-  Age < 3600, !.
+  Age < 8640000, !.
 % The persistent store has become stale, so refresh it.
 gv_color_update(_):-
   retractall_gv_color(_, _),
 
 
diff --git a/Prolog-Library-Collection b/Prolog-Library-Collection
index 03d4b46..af1a250 160000
--- a/Prolog-Library-Collection
+++ b/Prolog-Library-Collection
@@ -1 +1 @@
-Subproject commit 03d4b46ba9c6f658828ff843e5785c221ffc3a57
+Subproject commit af1a250d6074d0ab61a525fd064bfd555c5815a1
diff --git a/plHtml b/plHtml
index 50743da..81eb731 160000
--- a/plHtml
+++ b/plHtml
@@ -1 +1 @@
-Subproject commit 50743daad8dd0f3664e1cafbc14523a423314f84
+Subproject commit 81eb731eb46d98979c8f2862cfbf0e0aca9c1ef8
 
 
diff --git a/load.pl b/load.pl
index d502d58..4b117d1 100644
--- a/load.pl
+++ b/load.pl
@@ -1,11 +1,11 @@
 % Load file for plGraphViz.
 
-:- multifile(user:prolog/3).
 :- dynamic(user:prolog/3).
-user:project(plGraphViz, 'GraphViz support for SWI-Prolog.', plGraphViz).
+:- multifile(user:prolog/3).
+   user:project(plGraphViz, 'GraphViz support for SWI-Prolog.', plGraphViz).
 
 :- use_module(load_project).
-:- load_project([
+:- load_project(plGraphViz, [
     plc-'Prolog-Library-Collection',
     plHtml
 ]).
diff --git a/load_project.pl b/load_project.pl
index 3eae83d..5599b5e 100644
--- a/load_project.pl
+++ b/load_project.pl
@@ -1,7 +1,8 @@
 :- module(
   load_project,
   [
-    load_project/1, % +ChildProjects:list(or([atom,pair(atom)]))
+    load_project/2, % +Parent:atom
+                    % +ChildProjects:list(or([atom,pair(atom)]))
     load_subproject/2, % +ParentFileSearchPath:atom
                        % +Child:or([atom,pair(atom)])
     set_data_subdirectory/1 % +ParentDirectory:atom
@@ -16,19 +17,24 @@ Generic code for loading a project:
   * Load the index of subprojects onto the file search path.
 
 @author Wouter Beek
-@version 2014/05/27
+@version 2014/06/14
 */
 
-:- use_module(library(ansi_term)).
+:- use_module(library(ansi_term)). % Colorized terminal messages.
 :- use_module(library(apply)).
 
+:- dynamic(user:project/2).
+:- multifile(user:project/2).
+:- dynamic(user:project/3).
+:- multifile(user:project/3).
 
 
-load_project(ChildProjects):-
-  user:project(_, _, ParentFsp),
+
+load_project(Parent, ChildProjects):-
+  parent_alias(Parent, ParentFsp),
 
   % Entry point.
-  source_file(load_project(_), ThisFile),
+  source_file(load_project(_,_), ThisFile),
   file_directory_name(ThisFile, ThisDir),
   assert(user:file_search_path(ParentFsp, ThisDir)),
   assert(user:file_search_path(project, ThisDir)),
@@ -38,7 +44,7 @@ load_project(ChildProjects):-
 
   % Load the root of submodules onto the file search path.
   maplist(load_subproject(ParentFsp), ChildProjects),
-  
+
   % Load the index into the file search path.
   load_project_index(ParentFsp).
 
@@ -77,8 +83,20 @@ load_subproject_file_search_path(_, ChildFsp, ChildDir):-
 
 load_project_index(Fsp):-
   Spec =.. [Fsp,index],
-  absolute_file_name(Spec, File, [access(read),file_type(prolog)]),
+  absolute_file_name(
+    Spec,
+    File,
+    [access(read),file_errors(fail),file_type(prolog)]
+  ), !,
   ensure_loaded(File).
+load_project_index(_).
+
+
+%! parent_alias(+Parent:atom, -ParentFsp:atom) is det.
+
+parent_alias(Parent, ParentFsp):-
+  user:project(Parent, _, ParentFsp), !.
+parent_alias(Parent, Parent).
 
 
 %! set_data_subdirectory(+ParentDirectory:atom) is det.
 
 
diff --git a/Prolog-Library-Collection b/Prolog-Library-Collection
index af1a250..5b34b01 160000
--- a/Prolog-Library-Collection
+++ b/Prolog-Library-Collection
@@ -1 +1 @@
-Subproject commit af1a250d6074d0ab61a525fd064bfd555c5815a1
+Subproject commit 5b34b01ab0cc82a56e6fd90fc26e75da22f5a0fe
diff --git a/plHtml b/plHtml
index 81eb731..08480c4 160000
--- a/plHtml
+++ b/plHtml
@@ -1 +1 @@
-Subproject commit 81eb731eb46d98979c8f2862cfbf0e0aca9c1ef8
+Subproject commit 08480c40acaee5f70bfb21e3c641bef1e7d258bd
 
 
diff --git a/gv_attr_type.pl b/gv_attr_type.pl
index 200c034..f9a0e7f 100644
--- a/gv_attr_type.pl
+++ b/gv_attr_type.pl
@@ -29,18 +29,13 @@
     smoothType//1, % +SmoothType:atom
     %splineType//1,
     %startType//1,
+    string//1, % ?Content:atom
     style//2 % +Context:oneof([cluster,edge,node])
              % +Style:atom
     %viewPort//1
   ]
 ).
 :- reexport(
-  library(dcg/basics),
-  [
-    string//1 % +String:atom
-  ]
-).
-:- reexport(
   plGraphViz(gv_color),
   [
     color//1, % +Color:compound
@@ -383,6 +378,13 @@ smoothType(triangle).
 % @tbd startType
 
 
+%! string(?Content:atom)// .
+% A GraphViz string.
+
+string(Content) -->
+  atom(Content).
+
+
 %! style(?Context:oneof([cluster,edge,node]), ?Style:atom) is nondet.
 
 style(Context, Style) -->
diff --git a/gv_attrs.pl b/gv_attrs.pl
index fa499d0..dd0145a 100644
--- a/gv_attrs.pl
+++ b/gv_attrs.pl
@@ -68,7 +68,7 @@ gv_attr(Context, N=V1, N=V2):-
   ;
     Dcg =.. [Type,V1]
   ),
-  dcg_phrase(Dcg, V2),
+  once(dcg_phrase(Dcg, V2)),
   check_minimum(V1, Minimum).
 
 check_minimum(_, ''):- !.
diff --git a/gv_color.pl b/gv_color.pl
index 70e0495..19d6e05 100644
--- a/gv_color.pl
+++ b/gv_color.pl
@@ -23,6 +23,7 @@
 
 :- use_module(dcg(dcg_abnf)).
 :- use_module(dcg(dcg_cardinal)).
+:- use_module(dcg(dcg_content)).
 :- use_module(generics(db_ext)).
 :- use_module(os(file_ext)).
 :- use_module(sparql(sparql_char)).
@@ -46,14 +47,17 @@
 %   2. `rgba(Red:nonneg,Green:nonneg,Blue:nonneg,Alpha:nonneg)`
 %   3. `hsv(Hue:between(0.0,1.0),Saturation:between(0.0,1.0),Value:between(0.0,1.0))`
 
-color(rgb(Red,Green,Blue)) -->
+color(rgb(Red,Green,Blue)) --> !,
   `#`,
   '#'(3, hex_color, [Red,Green,Blue]).
-color(rgbs(Red,Green,Blue,Alpha)) -->
+color(rgbs(Red,Green,Blue,Alpha)) --> !,
   `#`,
   '#'(4, hex_color, [Red,Green,Blue,Alpha]).
-color(hsv(Hue,Saturation,Value)) -->
+color(hsv(Hue,Saturation,Value)) --> !,
   '#'(3, hsv_color, [Hue,Saturation,Value]).
+color(Name) -->
+  {gv_color(_, Name)},
+  atom(Name).
 
 hex_color(I) -->
   {W1 is I / 16},
diff --git a/gv_file.pl b/gv_file.pl
index 827f680..fe63e28 100644
--- a/gv_file.pl
+++ b/gv_file.pl
@@ -1,16 +1,13 @@
 :- module(
   gv_file,
   [
-    graph_to_gv_file/3, % +Options:list(nvpair)
+    gif_to_gv_file/3, % +Options:list(nvpair)
                         % +GraphInterchangeFormat:compound
                         % ?ToFile:atom
     graph_to_svg_dom/3, % +Options:list(nvpair)
                         % +GraphInterchangeFormat:compound
                         % -SvgDom:list(compound)
-    open_dot/1, % +File:file
-    tree_to_gv_file/3 % +Options:list(nvpair)
-                      % +Tree:compound
-                      % ?ToFile:atom
+    open_dot/1 % +File:file
   ]
 ).
 
@@ -81,7 +78,7 @@ user:prolog_file_type(xdot, xdot).
 
 
 
-%! graph_to_gv_file(
+%! gif_to_gv_file(
 %!   +Options:list(nvpair),
 %!   +GIF:compound,
 %!   -ToFile:atom
@@ -99,7 +96,7 @@ user:prolog_file_type(xdot, xdot).
 % @arg GIF A compound term representing a graph.
 % @arg ToFile The atomic name of a file.
 
-graph_to_gv_file(O1, GIF, ToFile):-
+gif_to_gv_file(O1, GIF, ToFile):-
   once(phrase(gv_graph(GIF), Codes)),
   to_gv_file(O1, Codes, ToFile).
 
@@ -117,7 +114,7 @@ graph_to_gv_file(O1, GIF, ToFile):-
 graph_to_svg_dom(O1, GIF, SvgDom):-
   % Make sure the file type of the output file is SvgDom.
   merge_options([to_file_type=svg], O1, O2),
-  graph_to_gv_file(O2, GIF, ToFile),
+  gif_to_gv_file(O2, GIF, ToFile),
   file_to_svg(ToFile, SvgDom),
   safe_delete_file(ToFile).
 
@@ -133,33 +130,6 @@ open_dot(File):-
   run_program(Program, [File]).
 
 
-%! tree_to_gv_file(+Options:list(nvpair), +Tree:compound, ?ToFile:atom) is det.
-% Stores the given tree term into a GraphViz file.
-%
-% The following options are supported:
-%   * =|method(+Method:oneof([dot,sfdp])|=
-%     The algorithm used by GraphViz for positioning the tree nodes.
-%     Either =dot= (default) or =sfdp=.
-%   * =|to_file_type(+FileType:oneof([jpeg,pdf,svg,xdot])|=
-%     The file type of the generated GraphViz file.
-%
-% @arg Options A list of name-value pairs.
-% @arg Tree A compound term representing a tree.
-% @arg ToFile The atomic name of the generated file.
-
-tree_to_gv_file(O1, Tree, ToFile):-
-  once(phrase(gv_tree(O1, Tree), Codes)),
-  to_gv_file(O1, Codes, ToFile).
-
-gv_tree(O1, T) -->
-  {
-    tree_to_ugraph(T, UG),
-    merge_options([edge_labels(false)], O1, O2),
-    export_ugraph(O2, UG, G_Term)
-  },
-  gv_graph(G_Term).
-
-
 
 % SUPPORT PREDICATES %
 
diff --git a/gv_gif.pl b/gv_gif.pl
new file mode 100644
index 0000000..becf7e2
--- /dev/null
+++ b/gv_gif.pl
@@ -0,0 +1,109 @@
+:- module(
+  gv_gif,
+  [
+    create_gif/3, % +Edges:ordset
+                  % -Gif:compound
+                  % +Options:list(nvpair)
+    create_gif/4 % +Vertices:ordset
+                 % +Edges:ordset
+                 % -Gif:compound
+                 % +Options:list(nvpair)
+  ]
+).
+
+/** <module> GraphViz Graph Interchange Format (GIF)
+
+Support for creating GIF representations.
+
+@author Wouter Beek
+@version 2014/06
+*/
+
+:- use_module(library(apply)).
+:- use_module(library(ordsets)).
+
+:- use_module(generics(list_ext)).
+
+:- use_module(plRdf(rdf_name)). % Meta-DCG.
+
+
+
+%! create_gif(+Edges:ordset, -Gif:compound, +Options:list(nvpair)) is det.
+
+create_gif(Es, Gif, Options):-
+  edges_to_vertices(Es, Vs),
+  create_gif(Vs, Es, Gif, Options).
+
+%! create_gif(
+%!   +Vertices:ordset,
+%!   +Edges:ordset,
+%!   -Gif:compound,
+%!   +Options:list(nvpair)
+%! ) is det.
+
+create_gif(Vs, Es, graph(V_Terms,E_Terms,G_Attrs), Options):-
+  maplist(vertex_term0(Vs, Options), Vs, V_Terms),
+  maplist(edge_term0(Vs, Options), Es, E_Terms),
+  G_Attrs = [directed=true].
+
+edge_term0(Vs, Options, E, E_Term):-
+  edge_term(Vs, E, E_Term, Options).
+
+vertex_term0(Vs, Options, V, V_Term):-
+  vertex_term(Vs, V, V_Term, Options).
+
+
+%! edge_term(
+%!   +Vertices:ordset,
+%!   +Edge:pair,
+%!   -EdgeTerm:compound,
+%!   +Options:list(nvpair)
+%! ) is det.
+
+edge_term(Vs, E, edge(FromId,ToId,E_Attrs), _):-
+  edge_components(E, FromV, _, ToV),
+  nth0chk(FromId, Vs, FromV),
+  nth0chk(ToId, Vs, ToV),
+  E_Attrs = [].
+
+
+%! vertex_term(
+%!   +Vertices:ordset,
+%!   +Vertex,
+%!   -VertexTerm:compound,
+%!   +Options:list(nvpair)
+%! ) is det.
+
+vertex_term(Vs, V, vertex(Id,V,V_Attrs), Options):-
+  nth0chk(Id, Vs, V),
+  
+  % Label.
+  option(vertex_label(VertexLabel), Options, =),
+  call(VertexLabel, V, V_Label),
+  
+  V_Attrs = [label=V_Label].
+
+vertex_label(V, V_Label):-
+  dcg_with_output_to(atom(V_Label), rdf_term_name([literal_ellipsis(50)], V)).
+
+
+
+% Helpers
+
+%! edge_components(+Edge:compound, -FromVertex, -EdgeType, -ToVertex) is det.
+%! edge_components(-Edge:compound, +FromVertex, ?EdgeType, +ToVertex) is det.
+
+edge_components(FromV-EdgeType-ToV, FromV, EdgeType, ToV):-
+  nonvar(EdgeType).
+edge_components(FromV-ToV, FromV, EdgeType, ToV):-
+  var(EdgeType).
+
+
+%! edges_to_vertices(+Edges:ordset, -Vertices:ordset) is det.
+
+edges_to_vertices([], []):- !.
+edges_to_vertices([S-_-O|T], S3):-
+  edges_to_vertices(T, S1),
+  ord_add_element(S1, S, S2),
+  ord_add_element(S2, O, S3).
+
diff --git a/gv_tree.pl b/gv_tree.pl
new file mode 100644
index 0000000..4412a90
--- /dev/null
+++ b/gv_tree.pl
@@ -0,0 +1,39 @@
+:- module(
+  gv_tree,
+  [
+    tree_to_gv_file/3 % +Options:list(nvpair)
+                      % +Tree:compound
+                      % ?ToFile:atom
+  ]
+).
+
+/** <module> GraphViz tree
+
+Export trees to GraphViz.
+
+@author Wouter Beek
+@version 2014/06
+*/
+
+:- use_module(library(aggregate)).
+
+:- use_module(generics(trees)).
+
+:- use_module(plGraphViz(gv_file)).
+:- use_module(plGraphViz(gv_gif)).
+
+
+%! tree_to_gv_file(+Options:list(nvpair), +Tree:compound, ?ToFile:atom) is det.
+% Stores the given tree term into a GraphViz file.
+%
+% Options are passed on to create_gif/3 and gif_to_gv_file/3.
+
+tree_to_gv_file(Options, Tree, ToFile):-
+  tree_to_gif(Tree, Gif, Options),
+  gif_to_gv_file(Options, Gif, ToFile).
+
+
+tree_to_gif(H-T, Gif, Options):-
+  tree_to_vertices_edges(Tree, Vs, Es),
+  create_gif(Vs, Es, Gif, Options).
+
 
 
diff --git a/gv_attr_type.pl b/gv_attr_type.pl
index f9a0e7f..7a64c82 100644
--- a/gv_attr_type.pl
+++ b/gv_attr_type.pl
@@ -191,7 +191,7 @@ doubleList1(Float) -->
   double(Float).
 
 
-%! escString(+String:atom)// ,
+%! escString(+String:atom)// .
 % @tbd Support for context-dependent replacements.
 
 escString(String) -->
 
 
diff --git a/gv_file.pl b/gv_file.pl
index fe63e28..c780692 100644
--- a/gv_file.pl
+++ b/gv_file.pl
@@ -1,12 +1,12 @@
 :- module(
   gv_file,
   [
-    gif_to_gv_file/3, % +Options:list(nvpair)
-                        % +GraphInterchangeFormat:compound
-                        % ?ToFile:atom
-    graph_to_svg_dom/3, % +Options:list(nvpair)
-                        % +GraphInterchangeFormat:compound
+    gif_to_gv_file/3, % +GraphInterchangeFormat:compound
+                      % ?ToFile:atom
+                      % +Options:list(nvpair)
+    graph_to_svg_dom/3, % +GraphInterchangeFormat:compound
                         % -SvgDom:list(compound)
+                        % +Options:list(nvpair)
     open_dot/1 % +File:file
   ]
 ).
@@ -20,11 +20,12 @@ Also converts between GraphViz DOT formatted files
 and GraphViz output files or SVG DOM structures.
 
 @author Wouter Beek
-@version 2011-2013/09, 2013/11-2014/01, 2014/05
+@version 2011-2013/09, 2013/11-2014/01, 2014/05, 2014/07
 */
 
 :- use_module(library(option)).
 :- use_module(library(process)).
+:- use_module(library(predicate_options)). % Declarations.
 
 :- use_module(generics(codes_ext)).
 :- use_module(generics(db_ext)).
@@ -48,73 +49,76 @@ and GraphViz output files or SVG DOM structures.
 :- multifile(user:prolog_file_type/2).
 
 % Register DOT.
-user:prolog_file_type(dot, dot).
-user:prolog_file_type(dot, graphviz).
-user:file_type_program(dot, dotty).
-user:file_type_program(dot, dotx).
-user:module_uses(gv_file, file_type(dot)).
+:- db_add_novel(user:prolog_file_type(dot, dot)).
+:- db_add_novel(user:prolog_file_type(dot, graphviz)).
+:- db_add_novel(user:file_type_program(dot, dotty)).
+:- db_add_novel(user:file_type_program(dot, dotx)).
+:- db_add_novel(user:module_uses(gv_file, file_type(dot))).
 
 % Register JPG/JPEG.
-user:prolog_file_type(jpeg, jpeg).
-user:prolog_file_type(jpeg, graphviz_output).
-user:prolog_file_type(jpg, jpeg).
-user:prolog_file_type(jpg, graphviz_output).
+:- db_add_novel(user:prolog_file_type(jpeg, jpeg)).
+:- db_add_novel(user:prolog_file_type(jpeg, graphviz_output)).
+:- db_add_novel(user:prolog_file_type(jpg, jpeg)).
+:- db_add_novel(user:prolog_file_type(jpg, graphviz_output)).
 
 % Register PDF.
-user:prolog_file_type(pdf, pdf).
-user:prolog_file_type(pdf, graphviz_output).
+:- db_add_novel(user:prolog_file_type(pdf, pdf)).
+:- db_add_novel(user:prolog_file_type(pdf, graphviz_output)).
 
 % Register PNG.
-user:prolog_file_type(png, png).
-user:prolog_file_type(png, graphviz_output).
+:- db_add_novel(user:prolog_file_type(png, png)).
+:- db_add_novel(user:prolog_file_type(png, graphviz_output)).
 
 % Register SVG.
-user:prolog_file_type(svg, graphviz_output).
-user:prolog_file_type(svg, svg).
+:- db_add_novel(user:prolog_file_type(svg, graphviz_output)).
+:- db_add_novel(user:prolog_file_type(svg, svg)).
 
 % Register XDOT.
-user:prolog_file_type(xdot, graphviz_output).
-user:prolog_file_type(xdot, xdot).
-
-
-
-%! gif_to_gv_file(
-%!   +Options:list(nvpair),
-%!   +GIF:compound,
-%!   -ToFile:atom
-%! ) is det.
+:- db_add_novel(user:prolog_file_type(xdot, graphviz_output)).
+:- db_add_novel(user:prolog_file_type(xdot, xdot)).
+
+:- predicate_options(graph_to_svg_dom/3, 3, [
+     pass_to(gif_to_gv_file/3, 3)
+   ]).
+:- predicate_options(gif_to_gv_file/3, 3, [
+     pass_to(to_gv_file/3, 3)
+   ]).
+:- predicate_options(to_gv_file/3, 3, [
+     pass_to(convert_gv/3, 3)
+   ]).
+:- predicate_options(convert_gv/3, 3, [
+     method(+oneof([dot,sfdp])),
+     to_file_type(+oneof([dot,jpeg,pdf,svg,xdot]))
+   ]).
+
+
+
+%! gif_to_gv_file(+Gif:compound, -ToFile:atom, +Options:list(nvpair)) is det.
 % Returns a file containing a GraphViz visualization of the given graph.
 %
 % The following options are supported:
 %   * =|method(+Method:oneof([dot,sfdp])|=
 %     The algorithm used by GraphViz for positioning the tree nodes.
 %     Either =dot= (default) or =sfdp=.
-%   * =|to_file_type(+FileType:oneof([jpeg,pdf,svg,xdot])|=
+%   * =|to_file_type(+FileType:oneof([dot,jpeg,pdf,svg,xdot])|=
 %     The file type of the generated GraphViz file.
-%
-% @arg Options A list of name-value pairs.
-% @arg GIF A compound term representing a graph.
-% @arg ToFile The atomic name of a file.
+%     Default: `pdf`.
 
-gif_to_gv_file(O1, GIF, ToFile):-
-  once(phrase(gv_graph(GIF), Codes)),
-  to_gv_file(O1, Codes, ToFile).
+gif_to_gv_file(Gif, ToFile, Options):-
+  once(phrase(gv_graph(Gif), Codes)),
+  to_gv_file(Codes, ToFile, Options).
 
 
 %! graph_to_svg_dom(
-%!   +Options:list(nvpair),
 %!   +GraphInterchangeFormat:compound,
-%!   -SvgDom:list(compound)
+%!   -SvgDom:list(compound),
+%!   +Options:list(nvpair)
 %! ) is det.
-% The following options are supported:
-%   * =|method(+Method:oneof([dot,sfdp])|=
-%     The algorithm used by GraphViz for positioning the tree nodes.
-%     Either =dot= (default) or =sfdp=.
 
-graph_to_svg_dom(O1, GIF, SvgDom):-
+graph_to_svg_dom(Gif, SvgDom, Options1):-
   % Make sure the file type of the output file is SvgDom.
-  merge_options([to_file_type=svg], O1, O2),
-  gif_to_gv_file(O2, GIF, ToFile),
+  merge_options([to_file_type=svg], Options1, Options2),
+  gif_to_gv_file(Gif, ToFile, Options2),
   file_to_svg(ToFile, SvgDom),
   safe_delete_file(ToFile).
 
@@ -133,31 +137,23 @@ open_dot(File):-
 
 % SUPPORT PREDICATES %
 
-%! convert_gv(+Options:list(nvpair), +FromFile:atom, ?ToFile:atom) is det.
+%! convert_gv(+FromFile:atom, ?ToFile:atom, +Options:list(nvpair)) is det.
 % Converts a GraphViz DOT file to an image file, using a specific
 % visualization method.
-%
-% The following options are supported:
-%   * =|method(+Method:oneof([dot,sfdp])|=
-%     The algorithm used by GraphViz for positioning the tree nodes.
-%     Either =dot= (default) or =sfdp=.
-%   * =|to_file_type(+FileType:oneof([jpeg,pdf,svg,xdot])|=
-%     The file type of the generated GraphViz file.
-%
-% @arg Options
-% @arg FromFile
-% @arg ToFile
 
-convert_gv(O1, FromFile, ToFile):-
+convert_gv(FromFile, ToFile, Options):-
+  option(to_file_type(dot), Options), !,
+  rename_file(FromFile, ToFile).
+convert_gv(FromFile, ToFile, Options):-
   % The input file must be readable.
   access_file(FromFile, read),
 
   % The method option.
-  option(method(Method), O1, dot),
+  option(method(Method), Options, dot),
   must_be(oneof([dot,sfdp]), Method),
 
   % The file type option.
-  option(to_file_type(ToFileType), O1, pdf),
+  option(to_file_type(ToFileType), Options, pdf),
   prolog_file_type(ToExtension, ToFileType),
   prolog_file_type(ToExtension, graphviz_output), !,
 
@@ -189,15 +185,10 @@ convert_gv(O1, FromFile, ToFile):-
   process_wait(PID, exit(ShellStatus)),
   exit_code_handler('GraphViz', ShellStatus).
 
-%! to_gv_file(+Options:list(nvpair), +Codes:list(code), ?ToFile:atom) is det.
-% The following options are supported:
-%   * =|method(+Method:oneof([dot,sfdp])|=
-%     The algorithm used by GraphViz for positioning the tree nodes.
-%     Either =dot= (default) or =sfdp=.
-%   * =|to_file_type(+FileType:oneof([jpeg,pdf,svg,xdot])|=
-%     The file type of the generated GraphViz file.
 
-to_gv_file(O1, Codes, ToFile):-
+%! to_gv_file(+Codes:list(code), ?ToFile:atom, +Options:list(nvpair)) is det.
+
+to_gv_file(Codes, ToFile, Options):-
   absolute_file_name(
     data(tmp),
     FromFile,
@@ -208,13 +199,13 @@ to_gv_file(O1, Codes, ToFile):-
     put_codes(Out, Codes),
     close(Out)
   ),
-  convert_gv(O1, FromFile, ToFile),
+  convert_gv(FromFile, ToFile, Options),
 
-  % DEB: Store DOT file.
-  ignore((
-    file_type_alternative(ToFile, graphviz, DOT_File),
-    safe_copy_file(FromFile, DOT_File)
-  )),
+  %%%%% DEB: Store DOT file.
+  %%%%ignore((
+  %%%%  file_type_alternative(ToFile, graphviz, DOT_File),
+  %%%%  safe_copy_file(FromFile, DOT_File)
+  %%%%)),
 
   safe_delete_file(FromFile).
 
diff --git a/gv_tree.pl b/gv_tree.pl
index 4412a90..afeb8f6 100644
--- a/gv_tree.pl
+++ b/gv_tree.pl
@@ -1,9 +1,9 @@
 :- module(
   gv_tree,
   [
-    tree_to_gv_file/3 % +Options:list(nvpair)
-                      % +Tree:compound
+    tree_to_gv_file/3 % +Tree:compound
                       % ?ToFile:atom
+                      % +Options:list(nvpair)
   ]
 ).
 
@@ -12,7 +12,7 @@
 Export trees to GraphViz.
 
 @author Wouter Beek
-@version 2014/06
+@version 2014/06-2014/07
 */
 
 :- use_module(library(aggregate)).
@@ -23,14 +23,18 @@ Export trees to GraphViz.
 :- use_module(plGraphViz(gv_gif)).
 
 
-%! tree_to_gv_file(+Options:list(nvpair), +Tree:compound, ?ToFile:atom) is det.
+%! tree_to_gv_file(
+%!   +Tree:compound,
+%!   ?ToFile:atom,
+%!   +Options:list(nvpair)
+%! ) is det.
 % Stores the given tree term into a GraphViz file.
 %
 % Options are passed on to create_gif/3 and gif_to_gv_file/3.
 
-tree_to_gv_file(Options, Tree, ToFile):-
+tree_to_gv_file(Tree, ToFile, Options):-
   tree_to_gif(Tree, Gif, Options),
-  gif_to_gv_file(Options, Gif, ToFile).
+  gif_to_gv_file(Gif, ToFile, Options).
 
 
 tree_to_gif(H-T, Gif, Options):-
 
 
diff --git a/gv_file.pl b/gv_file.pl
index c780692..9cb1f95 100644
--- a/gv_file.pl
+++ b/gv_file.pl
@@ -4,9 +4,9 @@
     gif_to_gv_file/3, % +GraphInterchangeFormat:compound
                       % ?ToFile:atom
                       % +Options:list(nvpair)
-    graph_to_svg_dom/3, % +GraphInterchangeFormat:compound
-                        % -SvgDom:list(compound)
-                        % +Options:list(nvpair)
+    gif_to_svg_dom/3, % +GraphInterchangeFormat:compound
+                      % -SvgDom:list(compound)
+                      % +Options:list(nvpair)
     open_dot/1 % +File:file
   ]
 ).
@@ -77,7 +77,7 @@ and GraphViz output files or SVG DOM structures.
 :- db_add_novel(user:prolog_file_type(xdot, graphviz_output)).
 :- db_add_novel(user:prolog_file_type(xdot, xdot)).
 
-:- predicate_options(graph_to_svg_dom/3, 3, [
+:- predicate_options(gif_to_svg_dom/3, 3, [
      pass_to(gif_to_gv_file/3, 3)
    ]).
 :- predicate_options(gif_to_gv_file/3, 3, [
@@ -109,13 +109,13 @@ gif_to_gv_file(Gif, ToFile, Options):-
   to_gv_file(Codes, ToFile, Options).
 
 
-%! graph_to_svg_dom(
+%! gif_to_svg_dom(
 %!   +GraphInterchangeFormat:compound,
 %!   -SvgDom:list(compound),
 %!   +Options:list(nvpair)
 %! ) is det.
 
-graph_to_svg_dom(Gif, SvgDom, Options1):-
+gif_to_svg_dom(Gif, SvgDom, Options1):-
   % Make sure the file type of the output file is SvgDom.
   merge_options([to_file_type=svg], Options1, Options2),
   gif_to_gv_file(Gif, ToFile, Options2),
 
 
diff --git a/gv_file.pl b/gv_file.pl
index 9cb1f95..98cb0a2 100644
--- a/gv_file.pl
+++ b/gv_file.pl
@@ -1,8 +1,13 @@
 :- module(
   gv_file,
   [
+    file_to_gv/2, % +File:atom
+                  % +Options:list(nvpair)
+    file_to_gv/3, % +FromFile:atom
+                  % ?ToFile:atom
+                  % +Options:list(nvpair)
     gif_to_gv_file/3, % +GraphInterchangeFormat:compound
-                      % ?ToFile:atom
+                      % +ToFile:atom
                       % +Options:list(nvpair)
     gif_to_svg_dom/3, % +GraphInterchangeFormat:compound
                       % -SvgDom:list(compound)
@@ -23,6 +28,7 @@ and GraphViz output files or SVG DOM structures.
 @version 2011-2013/09, 2013/11-2014/01, 2014/05, 2014/07
 */
 
+:- use_module(library(memfile)).
 :- use_module(library(option)).
 :- use_module(library(process)).
 :- use_module(library(predicate_options)). % Declarations.
@@ -77,74 +83,60 @@ and GraphViz output files or SVG DOM structures.
 :- db_add_novel(user:prolog_file_type(xdot, graphviz_output)).
 :- db_add_novel(user:prolog_file_type(xdot, xdot)).
 
-:- predicate_options(gif_to_svg_dom/3, 3, [
-     pass_to(gif_to_gv_file/3, 3)
-   ]).
-:- predicate_options(gif_to_gv_file/3, 3, [
-     pass_to(to_gv_file/3, 3)
+:- predicate_options(codes_to_gv_file/3, 3, [
+     pass_to(file_to_gv/3, 3)
    ]).
-:- predicate_options(to_gv_file/3, 3, [
-     pass_to(convert_gv/3, 3)
+:- predicate_options(file_to_gv/2, 2, [
+     pass_to(file_to_gv/3, 3)
    ]).
-:- predicate_options(convert_gv/3, 3, [
+:- predicate_options(file_to_gv/3, 3, [
      method(+oneof([dot,sfdp])),
      to_file_type(+oneof([dot,jpeg,pdf,svg,xdot]))
    ]).
+:- predicate_options(gif_to_svg_dom/3, 3, [
+     pass_to(gif_to_gv_file/3, 3)
+   ]).
+:- predicate_options(gif_to_gv_file/3, 3, [
+     pass_to(codes_to_gv_file/3, 3)
+   ]).
 
 
 
-%! gif_to_gv_file(+Gif:compound, -ToFile:atom, +Options:list(nvpair)) is det.
-% Returns a file containing a GraphViz visualization of the given graph.
-%
-% The following options are supported:
-%   * =|method(+Method:oneof([dot,sfdp])|=
-%     The algorithm used by GraphViz for positioning the tree nodes.
-%     Either =dot= (default) or =sfdp=.
-%   * =|to_file_type(+FileType:oneof([dot,jpeg,pdf,svg,xdot])|=
-%     The file type of the generated GraphViz file.
-%     Default: `pdf`.
-
-gif_to_gv_file(Gif, ToFile, Options):-
-  once(phrase(gv_graph(Gif), Codes)),
-  to_gv_file(Codes, ToFile, Options).
-
-
-%! gif_to_svg_dom(
-%!   +GraphInterchangeFormat:compound,
-%!   -SvgDom:list(compound),
+%! codes_to_gv_file(
+%!   +Codes:list(code),
+%!   +ToFile:atom,
 %!   +Options:list(nvpair)
 %! ) is det.
 
-gif_to_svg_dom(Gif, SvgDom, Options1):-
-  % Make sure the file type of the output file is SvgDom.
-  merge_options([to_file_type=svg], Options1, Options2),
-  gif_to_gv_file(Gif, ToFile, Options2),
-  file_to_svg(ToFile, SvgDom),
-  safe_delete_file(ToFile).
-
-
-%! open_dot(+File:atom) is det.
-% Opens the given DOT file.
-%
-% @tbd Test support on Windows.
-% @tbd Test support on OS-X.
-
-open_dot(File):-
-  once(find_program_by_file_type(dot, Program)),
-  run_program(Program, [File]).
+codes_to_gv_file(Codes, ToFile, Options):-
+  access_file(ToFile, write),
+  setup_call_cleanup(
+    new_memory_file(MemFile),
+    (
+      setup_call_cleanup(
+        open(MemFile, write, Write, [encoding(utf8),type(test)]),
+        put_codes(Write, Codes),
+        close(Write)
+      ),
+      file_to_gv(MemFile, ToFile, Options)
+    ),
+    free_memory_file(MemFile)
+  ).
 
 
+%! file_to_gv(+FromFile:atom, +Options:list(nvpair)) is det.
 
-% SUPPORT PREDICATES %
+file_to_gv(FromFile, Options):-
+  file_to_gv(FromFile, _, Options).
 
-%! convert_gv(+FromFile:atom, ?ToFile:atom, +Options:list(nvpair)) is det.
+%! file_to_gv(+FromFile:atom, ?ToFile:atom, +Options:list(nvpair)) is det.
 % Converts a GraphViz DOT file to an image file, using a specific
 % visualization method.
 
-convert_gv(FromFile, ToFile, Options):-
+file_to_gv(FromFile, ToFile, Options):-
   option(to_file_type(dot), Options), !,
   rename_file(FromFile, ToFile).
-convert_gv(FromFile, ToFile, Options):-
+file_to_gv(FromFile, ToFile, Options):-
   % The input file must be readable.
   access_file(FromFile, read),
 
@@ -186,26 +178,43 @@ convert_gv(FromFile, ToFile, Options):-
   exit_code_handler('GraphViz', ShellStatus).
 
 
-%! to_gv_file(+Codes:list(code), ?ToFile:atom, +Options:list(nvpair)) is det.
+%! gif_to_gv_file(+Gif:compound, +ToFile:atom, +Options:list(nvpair)) is det.
+% Returns a file containing a GraphViz visualization of the given graph.
+%
+% The following options are supported:
+%   * =|method(+Method:oneof([dot,sfdp])|=
+%     The algorithm used by GraphViz for positioning the tree nodes.
+%     Either =dot= (default) or =sfdp=.
+%   * =|to_file_type(+FileType:oneof([dot,jpeg,pdf,svg,xdot])|=
+%     The file type of the generated GraphViz file.
+%     Default: `pdf`.
+
+gif_to_gv_file(Gif, ToFile, Options):-
+  once(phrase(gv_graph(Gif), Codes)),
+  codes_to_gv_file(Codes, ToFile, Options).
+
+
+%! gif_to_svg_dom(
+%!   +GraphInterchangeFormat:compound,
+%!   -SvgDom:list(compound),
+%!   +Options:list(nvpair)
+%! ) is det.
+
+gif_to_svg_dom(Gif, SvgDom, Options1):-
+  % Make sure the file type of the output file is SvgDom.
+  merge_options([to_file_type=svg], Options1, Options2),
+  gif_to_gv_file(Gif, ToFile, Options2),
+  file_to_svg(ToFile, SvgDom),
+  safe_delete_file(ToFile).
 
-to_gv_file(Codes, ToFile, Options):-
-  absolute_file_name(
-    data(tmp),
-    FromFile,
-    [access(write),file_type(graphviz)]
-  ),
-  setup_call_cleanup(
-    open(FromFile, write, Out, [encoding(utf8),type(test)]),
-    put_codes(Out, Codes),
-    close(Out)
-  ),
-  convert_gv(FromFile, ToFile, Options),
 
-  %%%%% DEB: Store DOT file.
-  %%%%ignore((
-  %%%%  file_type_alternative(ToFile, graphviz, DOT_File),
-  %%%%  safe_copy_file(FromFile, DOT_File)
-  %%%%)),
+%! open_dot(+File:atom) is det.
+% Opens the given DOT file.
+%
+% @tbd Test support on Windows.
+% @tbd Test support on OS-X.
 
-  safe_delete_file(FromFile).
+open_dot(File):-
+  once(find_program_by_file_type(dot, Program)),
+  run_program(Program, [File]).
 
 
 
diff --git a/gv_gif.pl b/gv_gif.pl
index becf7e2..908b9bc 100644
--- a/gv_gif.pl
+++ b/gv_gif.pl
@@ -16,16 +16,35 @@
 Support for creating GIF representations.
 
 @author Wouter Beek
-@version 2014/06
+@version 2014/06-2014/07
 */
 
 :- use_module(library(apply)).
+:- use_module(library(lambda)).
+:- use_module(library(option)).
 :- use_module(library(ordsets)).
+:- use_module(library(predicate_options)). % Declarations.
 
 :- use_module(generics(list_ext)).
+:- use_module(generics(option_ext)).
+:- use_module(graph_theory(graph_generic)).
 
 :- use_module(plRdf(rdf_name)). % Meta-DCG.
 
+:- predicate_options(create_gif/3, 3, [
+     pass_to(create_gif/4, 4)
+   ]).
+:- predicate_options(create_gif/4, 4, [
+     pass_to(vertex_term/3, 3),
+     pass_to(edge_term/3, 3),
+     graph_label(+atom)
+   ]).
+:- predicate_options(edge_term/3, 3, [
+   ]).
+:- predicate_options(vertex_term/3, 3, [
+     vertex_label(+atom)
+   ]).
+
 
 
 %! create_gif(+Edges:ordset, -Gif:compound, +Options:list(nvpair)) is det.
@@ -40,17 +59,28 @@ create_gif(Es, Gif, Options):-
 %!   -Gif:compound,
 %!   +Options:list(nvpair)
 %! ) is det.
-
-create_gif(Vs, Es, graph(V_Terms,E_Terms,G_Attrs), Options):-
-  maplist(vertex_term0(Vs, Options), Vs, V_Terms),
-  maplist(edge_term0(Vs, Options), Es, E_Terms),
-  G_Attrs = [directed=true].
-
-edge_term0(Vs, Options, E, E_Term):-
-  edge_term(Vs, E, E_Term, Options).
-
-vertex_term0(Vs, Options, V, V_Term):-
-  vertex_term(Vs, V, V_Term, Options).
+% The following options are supported:
+%   * =|graph_label(+LabelFunction)|=
+%     The functions that assigns names to graphs.
+%     No default.
+
+create_gif(Vs, Es, graph(VTerms,ETerms,GAttrs), Options):-
+  % Vertex terms.
+  maplist(\V^VTerm^vertex_term(Vs, V, VTerm, Options), Vs, VTerms),
+  
+  % Edge terms.
+  maplist(\E^ETerm^edge_term(Vs, E, ETerm, Options), Es, ETerms),
+  
+  % Graph attributes.
+  (
+    option(graph_label(LabelFunction), Options)
+  ->
+    call(LabelFunction, Graph, GraphLabel)
+  ;
+    true
+  ),
+  
+  merge_options([directed=true,label=GraphLabel], GAttrs).
 
 
 %! edge_term(
@@ -60,11 +90,13 @@ vertex_term0(Vs, Options, V, V_Term):-
 %!   +Options:list(nvpair)
 %! ) is det.
 
-edge_term(Vs, E, edge(FromId,ToId,E_Attrs), _):-
-  edge_components(E, FromV, _, ToV),
+edge_term(Vs, E, edge(FromId,ToId,EAttrs), _):-
+  edge_components(E, FromV, ToV),
+  
   nth0chk(FromId, Vs, FromV),
   nth0chk(ToId, Vs, ToV),
-  E_Attrs = [].
+  
+  EAttrs = [].
 
 
 %! vertex_term(
@@ -73,37 +105,16 @@ edge_term(Vs, E, edge(FromId,ToId,E_Attrs), _):-
 %!   -VertexTerm:compound,
 %!   +Options:list(nvpair)
 %! ) is det.
+% The following options are supported:
+%   * =|vertex_label(+LabelFunction)|=
+%     A function that assigns labels to vertices.
 
-vertex_term(Vs, V, vertex(Id,V,V_Attrs), Options):-
+vertex_term(Vs, V, vertex(Id,V,VAttrs), Options):-
   nth0chk(Id, Vs, V),
   
   % Label.
-  option(vertex_label(VertexLabel), Options, =),
-  call(VertexLabel, V, V_Label),
+  option(vertex_label(LabelFunction), Options, =),
+  call(LabelFunction, V, VLabel),
   
-  V_Attrs = [label=V_Label].
-
-vertex_label(V, V_Label):-
-  dcg_with_output_to(atom(V_Label), rdf_term_name([literal_ellipsis(50)], V)).
-
-
-
-% Helpers
-
-%! edge_components(+Edge:compound, -FromVertex, -EdgeType, -ToVertex) is det.
-%! edge_components(-Edge:compound, +FromVertex, ?EdgeType, +ToVertex) is det.
-
-edge_components(FromV-EdgeType-ToV, FromV, EdgeType, ToV):-
-  nonvar(EdgeType).
-edge_components(FromV-ToV, FromV, EdgeType, ToV):-
-  var(EdgeType).
-
-
-%! edges_to_vertices(+Edges:ordset, -Vertices:ordset) is det.
-
-edges_to_vertices([], []):- !.
-edges_to_vertices([S-_-O|T], S3):-
-  edges_to_vertices(T, S1),
-  ord_add_element(S1, S, S2),
-  ord_add_element(S2, O, S3).
+  VAttrs = [label=VLabel].
 
 
 
diff --git a/gv_dot.pl b/gv_dot.pl
index 9888973..68443ba 100644
--- a/gv_dot.pl
+++ b/gv_dot.pl
@@ -76,13 +76,13 @@ gv_compass_pt(sw) --> `sw`.
 gv_compass_pt(w) --> `w`.
 
 
-%! gv_edge_operator(+Directed:boolean)// .
+%! gv_edge_operator(+Directedness:boolean)// .
 % The binary edge operator between two vertices.
 % The operator that is used depends on whether the graph is directed or
 % undirected.
 %
-% @arg Directed Whether an edge is directed (operator `->`) or
-%               undirected (operator `--`).
+% @arg Directedness Whether an edge is directed (operator `->`) or
+%                   undirected (operator `--`).
 
 gv_edge_operator(false) --> !, `--`.
 gv_edge_operator(true) --> arrow(right, 2).
@@ -90,14 +90,14 @@ gv_edge_operator(true) --> arrow(right, 2).
 
 %! gv_edge_statement(
 %!   +Indent:nonneg,
-%!   +Directed:boolean,
+%!   +Directedness:boolean,
 %!   +GraphAttributes:list(nvpair),
 %!   +EdgeTerm:compound
 %! )// is det.
 % A GraphViz statement describing an edge.
 %
 % @arg Indent The indentation level at which the edge statement is written.
-% @arg Directed Whether the graph is directed or not.
+% @arg Directedness Whether the graph is directed or not.
 % @arg GraphAttributes The attributes of the graph. Some of these attributes
 %      may be used in the edge statement (e.g., the colorscheme).
 % @arg EdgeTerm A compound term in the GIFormat, representing an edge.
@@ -106,11 +106,11 @@ gv_edge_operator(true) --> arrow(right, 2).
 %      at the from and/or to location.
 % @tbd Add support for multiple, consecutive occurrences of gv_edge_rhs//2.
 
-gv_edge_statement(I, Directed, GAttrs, edge(FromId,ToId,EAttrs)) -->
+gv_edge_statement(I, Directedness, GAttrs, edge(FromId,ToId,EAttrs)) -->
   indent(I),
   gv_node_id(FromId), ` `,
 
-  gv_edge_operator(Directed), ` `,
+  gv_edge_operator(Directedness), ` `,
 
   gv_node_id(ToId), ` `,
 
@@ -148,11 +148,11 @@ gv_generic_attributes_statement(Kind, I, GraphAttrs, KindAttrs) -->
 %! gv_graph(+GraphTerm:compound)//
 % The follow graph attributes are supported,
 % beyond the GraphViz attributes for graphs:
-%   1. `directonality(+Directed:oneof([directed,undirected]))`
-%      A directed graph uses the keyword `digraph`.
-%      An undirected graph uses the keyword `graph`.
-%   2. `name(+GraphName:atom)`
-%   3. `strict(+StrictGraph:boolean)`
+%   * `directedness(+boolean)`
+%      Whether the graph is directed (`true`) or undirected (`false`).
+%      Default: `false`.
+%   * `name(+GraphName:atom)`
+%   * `strict(+StrictGraph:boolean)`
 %      This forbids the creation of self-arcs and multi-edges;
 %      they are ignored in the input file.
 %      Only in combinattion with directionality `directed`.
@@ -185,7 +185,7 @@ gv_graph(graph(VTerms, RankedVTerms, ETerms, GAttrs1)) -->
     shared_attributes(VTerms, SharedVAttrs, NewVTerms),
     shared_attributes(ETerms, SharedEAttrs, NewETerms),
     select_nvpair(strict=Strict, GAttrs1, GAttrs2, false),
-    select_nvpair(directed=Directed, GAttrs2, GAttrs3, true),
+    select_nvpair(directedness=Directedness, GAttrs2, GAttrs3, true),
     select_nvpair(name=GName, GAttrs3, GAttrs4, noname),
     add_default_nvpair(GAttrs4, overlap, false, GAttrs5),
     I = 0
@@ -195,24 +195,24 @@ gv_graph(graph(VTerms, RankedVTerms, ETerms, GAttrs1)) -->
   % States that this file represents a graph according to the GraphViz format.
   indent(I),
   gv_strict(Strict),
-  gv_graph_type(Directed), ` `,
+  gv_graph_type(Directedness), ` `,
   gv_id(GName), ` `,
   bracketed(
     curly,
-    gv_graph1(
+    gv_graph0(
       I,
       NewVTerms, SharedVAttrs, RankedVTerms,
       NewETerms, SharedEAttrs,
-      Directed, GAttrs5
+      Directedness, GAttrs5
     )
-  ),
+  ),                                                      
   newline.
 
-gv_graph1(
+gv_graph0(
   I,
   NewVTerms, SharedVAttrs, RankedVTerms,
   NewETerms, SharedEAttrs,
-  Directed, GAttrs
+  Directedness, GAttrs
 ) -->
   newline,
 
@@ -254,10 +254,10 @@ gv_graph1(
   },
 
   % The rank edges.
-  '*'(gv_edge_statement(NewI, Directed, GAttrs), RankEdges),
+  '*'(gv_edge_statement(NewI, Directedness, GAttrs), RankEdges),
 
   % The non-rank edges.
-  '*'(gv_edge_statement(NewI, Directed, GAttrs), NewETerms),
+  '*'(gv_edge_statement(NewI, Directedness, GAttrs), NewETerms),
 
   % Note that we do not include a newline here.
 
@@ -265,7 +265,7 @@ gv_graph1(
   indent(I).
 
 
-%! gv_graph_type(+Directed:boolean)// .
+%! gv_graph_type(+Directedness:boolean)// .
 % The type of graph that is represented.
 
 gv_graph_type(false) --> `graph`.
diff --git a/gv_gif.pl b/gv_gif.pl
index 908b9bc..4a3e75f 100644
--- a/gv_gif.pl
+++ b/gv_gif.pl
@@ -35,14 +35,25 @@ Support for creating GIF representations.
      pass_to(create_gif/4, 4)
    ]).
 :- predicate_options(create_gif/4, 4, [
-     pass_to(vertex_term/3, 3),
      pass_to(edge_term/3, 3),
-     graph_label(+atom)
+     pass_to(graph_attributes/2, 2),
+     pass_to(vertex_term/3, 3)
    ]).
 :- predicate_options(edge_term/3, 3, [
    ]).
+:- predicate_options(graph_attributes/2, 2, [
+     directedness(+boolean),
+     graph_colorscheme(+oneof([none,svg,x11]),
+     graph_label(+atom)
+   ]).
+
 :- predicate_options(vertex_term/3, 3, [
-     vertex_label(+atom)
+     vertex_color(+atom),
+     vertex_coordinates(+atom),
+     vertex_image(+atom),
+     vertex_label(+atom),
+     vertex_peripheries(+atom),
+     vertex_shape(+atom)
    ]).
 
 
@@ -60,6 +71,8 @@ create_gif(Es, Gif, Options):-
 %!   +Options:list(nvpair)
 %! ) is det.
 % The following options are supported:
+%   * =|graph_colorscheme(+oneof([none,svg,x11]))
+%     No default.
 %   * =|graph_label(+LabelFunction)|=
 %     The functions that assigns names to graphs.
 %     No default.
@@ -72,15 +85,7 @@ create_gif(Vs, Es, graph(VTerms,ETerms,GAttrs), Options):-
   maplist(\E^ETerm^edge_term(Vs, E, ETerm, Options), Es, ETerms),
   
   % Graph attributes.
-  (
-    option(graph_label(LabelFunction), Options)
-  ->
-    call(LabelFunction, Graph, GraphLabel)
-  ;
-    true
-  ),
-  
-  merge_options([directed=true,label=GraphLabel], GAttrs).
+  graph_attributes(GAttrs, Options).
 
 
 %! edge_term(
@@ -89,14 +94,69 @@ create_gif(Vs, Es, graph(VTerms,ETerms,GAttrs), Options):-
 %!   -EdgeTerm:compound,
 %!   +Options:list(nvpair)
 %! ) is det.
+% The following options are supported:
+%   * =|edge_arrowhead(+atom)|=
+%   * =|edge_color(+atom)|=
+%   * =|edge_label(+atom)|=
+%   * =|edge_style(+atom)|=
 
 edge_term(Vs, E, edge(FromId,ToId,EAttrs), _):-
   edge_components(E, FromV, ToV),
-  
   nth0chk(FromId, Vs, FromV),
   nth0chk(ToId, Vs, ToV),
   
-  EAttrs = [].
+  % Arrowhead
+  if_option(edge_arrowhead(ArrowheadFunction), Options,
+    call(ArrowheadFunction, E, EArrowhead)
+  ),
+  
+  % Color.
+  if_option(edge_color(ColorFunction), Options,
+    call(ColorFunction, E, EColor)
+  ),
+  
+  % Label.
+  if_option(edge_label(LabelFunction), Options,
+    call(LabelFunction, E, ELabel)
+  ),
+  
+  % Style.
+  if_option(edge_style(StyleFunction), Options,
+    call(StyleFunction, E, EStyle)
+  ),
+  
+  EAttrs = [arrowHead=EArrowhead,color=EColor,label=ELabel,style=EStyle].
+
+
+%! graph_attributes(
+%!   -GraphAttributes:list(nvpair),
+%!   +Options:list(nvpair)
+%! ) is det.
+% The following options are supported:
+%   * =|directedness(+boolean)|=
+%     Whether the graph is directed (`true`) or undirected (`false`).
+%     Default: `false`.
+%   * =|graph_colorscheme(+oneof([none,svg,x11]))|=
+%     The colorscheme from which the color in this graph are taken.
+%     Default: `svg`.
+%   * =|graph_label(+atom)|=
+%     The graph label.
+%     No default.
+
+graph_attributes(GAttrs, Options):-
+  % Directedness.
+  option(directedness(Directedness), Options, false),
+  
+  % Colorscheme.
+  if_option(graph_colorscheme(Colorscheme), Options, true),
+  
+  % Label.
+  if_option(graph_label(GLabel), Options, true),
+  
+  merge_options(
+    [colorscheme=Colorscheme,directedness=Directedness,label=GLabel],
+    GAttrs
+  ).
 
 
 %! vertex_term(
@@ -106,15 +166,66 @@ edge_term(Vs, E, edge(FromId,ToId,EAttrs), _):-
 %!   +Options:list(nvpair)
 %! ) is det.
 % The following options are supported:
+%   * =|vertex_color(+ColorFunction)|=
+%     A function that assigns colors to vertices.
+%     No default.
+%   * =|vertex_coordinate(+CoordinateFunction)|=
+%     A function that assigns coordinates to vertices.
+%     No default.
+%   * =|vertex_image(+ImageFunction)|=
+%     A function that assinges images to vertices.
+%     No default.
 %   * =|vertex_label(+LabelFunction)|=
 %     A function that assigns labels to vertices.
+%     No default.
+%   * =|vertex_peripheries(+PeripheriesFunction)|=
+%     A function that assinges peripheries to vertices.
+%     No default.
+%   * =|vertex_shape(+ShapeFunction)|=
+%     A function that assinges shapes to vertices.
+%     No default.
 
 vertex_term(Vs, V, vertex(Id,V,VAttrs), Options):-
   nth0chk(Id, Vs, V),
   
+  % Color.
+  if_option(vertex_color(ColorFunction), Options,
+    call(ColorFunction, V, VColor)
+  ),
+  
+  % Coordinates.
+  %%%%if_option(vertex_coordinate(CoordinateFunction), Options,
+  %%%%  call(CoordinateFunction, V, VCoordinates)
+  %%%%),
+  
+  % Image.
+  if_option(image(ImageFunction), Options,
+    call(ImageFunction, V, VImage)
+  ),
+  
   % Label.
-  option(vertex_label(LabelFunction), Options, =),
-  call(LabelFunction, V, VLabel),
+  if_option(vertex_label(LabelFunction), Options,
+    call(LabelFunction, V, VLabel)
+  ),
+  
+  % Peripheries.
+  if_option(vertex_peripheries(PeripheriesFunction), Options,
+    call(PeripheriesFunction, V, VPeripheries)
+  ),
+  
+  % Shape.
+  if_option(vertex_shape(ShapeFunction), Options,
+    call(ShapeFunction, V, VShape)
+  ),
   
-  VAttrs = [label=VLabel].
+  merge_options(
+    [
+      color=VColor,
+      image=VImage,
+      label=VLabel,
+      peripheries=VPeripheries,
+      shape=VShape
+    ],
+    VAttrs
+  ).
 
 
 
diff --git a/gv_gif.pl b/gv_gif.pl
index 4a3e75f..ef2f7f2 100644
--- a/gv_gif.pl
+++ b/gv_gif.pl
@@ -43,7 +43,7 @@ Support for creating GIF representations.
    ]).
 :- predicate_options(graph_attributes/2, 2, [
      directedness(+boolean),
-     graph_colorscheme(+oneof([none,svg,x11]),
+     graph_colorscheme(+oneof([none,svg,x11])),
      graph_label(+atom)
    ]).
 
@@ -109,23 +109,23 @@ edge_term(Vs, E, edge(FromId,ToId,EAttrs), _):-
   if_option(edge_arrowhead(ArrowheadFunction), Options,
     call(ArrowheadFunction, E, EArrowhead)
   ),
-  
   % Color.
   if_option(edge_color(ColorFunction), Options,
     call(ColorFunction, E, EColor)
   ),
-  
   % Label.
   if_option(edge_label(LabelFunction), Options,
     call(LabelFunction, E, ELabel)
   ),
-  
   % Style.
   if_option(edge_style(StyleFunction), Options,
     call(StyleFunction, E, EStyle)
   ),
   
-  EAttrs = [arrowHead=EArrowhead,color=EColor,label=ELabel,style=EStyle].
+  merge_options(
+    [arrowHead=EArrowhead,color=EColor,label=ELabel,style=EStyle],
+    EAttrs
+  ).
 
 
 %! graph_attributes(
@@ -146,10 +146,8 @@ edge_term(Vs, E, edge(FromId,ToId,EAttrs), _):-
 graph_attributes(GAttrs, Options):-
   % Directedness.
   option(directedness(Directedness), Options, false),
-  
   % Colorscheme.
   if_option(graph_colorscheme(Colorscheme), Options, true),
-  
   % Label.
   if_option(graph_label(GLabel), Options, true),
   
@@ -169,9 +167,6 @@ graph_attributes(GAttrs, Options):-
 %   * =|vertex_color(+ColorFunction)|=
 %     A function that assigns colors to vertices.
 %     No default.
-%   * =|vertex_coordinate(+CoordinateFunction)|=
-%     A function that assigns coordinates to vertices.
-%     No default.
 %   * =|vertex_image(+ImageFunction)|=
 %     A function that assinges images to vertices.
 %     No default.
@@ -192,27 +187,18 @@ vertex_term(Vs, V, vertex(Id,V,VAttrs), Options):-
   if_option(vertex_color(ColorFunction), Options,
     call(ColorFunction, V, VColor)
   ),
-  
-  % Coordinates.
-  %%%%if_option(vertex_coordinate(CoordinateFunction), Options,
-  %%%%  call(CoordinateFunction, V, VCoordinates)
-  %%%%),
-  
   % Image.
   if_option(image(ImageFunction), Options,
     call(ImageFunction, V, VImage)
   ),
-  
   % Label.
   if_option(vertex_label(LabelFunction), Options,
     call(LabelFunction, V, VLabel)
   ),
-  
   % Peripheries.
   if_option(vertex_peripheries(PeripheriesFunction), Options,
     call(PeripheriesFunction, V, VPeripheries)
   ),
-  
   % Shape.
   if_option(vertex_shape(ShapeFunction), Options,
     call(ShapeFunction, V, VShape)
 
 
diff --git a/gv_gif.pl b/gv_gif.pl
index ef2f7f2..6d78ac3 100644
--- a/gv_gif.pl
+++ b/gv_gif.pl
@@ -40,6 +40,10 @@ Support for creating GIF representations.
      pass_to(vertex_term/3, 3)
    ]).
 :- predicate_options(edge_term/3, 3, [
+     edge_arrowhead(+atom),
+     edge_color(+atom),
+     edge_label(+atom),
+     edge_style(+atom)
    ]).
 :- predicate_options(graph_attributes/2, 2, [
      directedness(+boolean),
 
 
diff --git a/gv_file.pl b/gv_file.pl
index 98cb0a2..25b0cd3 100644
--- a/gv_file.pl
+++ b/gv_file.pl
@@ -110,18 +110,14 @@ and GraphViz output files or SVG DOM structures.
 
 codes_to_gv_file(Codes, ToFile, Options):-
   access_file(ToFile, write),
+  absolute_file_name(data(tmp), TmpFile, [access(write),file_type(dot)]),
   setup_call_cleanup(
-    new_memory_file(MemFile),
-    (
-      setup_call_cleanup(
-        open(MemFile, write, Write, [encoding(utf8),type(test)]),
-        put_codes(Write, Codes),
-        close(Write)
-      ),
-      file_to_gv(MemFile, ToFile, Options)
-    ),
-    free_memory_file(MemFile)
-  ).
+    open(TmpFile, write, Write, [encoding(utf8)]),
+    put_codes(Write, Codes),
+    close(Write)
+  ),
+  file_to_gv(TmpFile, ToFile, Options),
+  delete_file(TmpFile).
 
 
 %! file_to_gv(+FromFile:atom, +Options:list(nvpair)) is det.
@@ -137,9 +133,6 @@ file_to_gv(FromFile, ToFile, Options):-
   option(to_file_type(dot), Options), !,
   rename_file(FromFile, ToFile).
 file_to_gv(FromFile, ToFile, Options):-
-  % The input file must be readable.
-  access_file(FromFile, read),
-
   % The method option.
   option(method(Method), Options, dot),
   must_be(oneof([dot,sfdp]), Method),
diff --git a/gv_gif.pl b/gv_gif.pl
index 6d78ac3..e3a6933 100644
--- a/gv_gif.pl
+++ b/gv_gif.pl
@@ -84,10 +84,10 @@ create_gif(Es, Gif, Options):-
 create_gif(Vs, Es, graph(VTerms,ETerms,GAttrs), Options):-
   % Vertex terms.
   maplist(\V^VTerm^vertex_term(Vs, V, VTerm, Options), Vs, VTerms),
-  
+
   % Edge terms.
   maplist(\E^ETerm^edge_term(Vs, E, ETerm, Options), Es, ETerms),
-  
+
   % Graph attributes.
   graph_attributes(GAttrs, Options).
 
@@ -104,11 +104,11 @@ create_gif(Vs, Es, graph(VTerms,ETerms,GAttrs), Options):-
 %   * =|edge_label(+atom)|=
 %   * =|edge_style(+atom)|=
 
-edge_term(Vs, E, edge(FromId,ToId,EAttrs), _):-
+edge_term(Vs, E, edge(FromId,ToId,EAttrs), Options):-
   edge_components(E, FromV, ToV),
   nth0chk(FromId, Vs, FromV),
   nth0chk(ToId, Vs, ToV),
-  
+
   % Arrowhead
   if_option(edge_arrowhead(ArrowheadFunction), Options,
     call(ArrowheadFunction, E, EArrowhead)
@@ -125,9 +125,9 @@ edge_term(Vs, E, edge(FromId,ToId,EAttrs), _):-
   if_option(edge_style(StyleFunction), Options,
     call(StyleFunction, E, EStyle)
   ),
-  
+
   merge_options(
-    [arrowHead=EArrowhead,color=EColor,label=ELabel,style=EStyle],
+    [arrowhead=EArrowhead,color=EColor,label=ELabel,style=EStyle],
     EAttrs
   ).
 
@@ -154,7 +154,7 @@ graph_attributes(GAttrs, Options):-
   if_option(graph_colorscheme(Colorscheme), Options, true),
   % Label.
   if_option(graph_label(GLabel), Options, true),
-  
+
   merge_options(
     [colorscheme=Colorscheme,directedness=Directedness,label=GLabel],
     GAttrs
@@ -186,7 +186,7 @@ graph_attributes(GAttrs, Options):-
 
 vertex_term(Vs, V, vertex(Id,V,VAttrs), Options):-
   nth0chk(Id, Vs, V),
-  
+
   % Color.
   if_option(vertex_color(ColorFunction), Options,
     call(ColorFunction, V, VColor)
@@ -207,7 +207,7 @@ vertex_term(Vs, V, vertex(Id,V,VAttrs), Options):-
   if_option(vertex_shape(ShapeFunction), Options,
     call(ShapeFunction, V, VShape)
   ),
-  
+
   merge_options(
     [
       color=VColor,
 
 
diff --git a/gv_file.pl b/gv_file.pl
index 25b0cd3..2d2ed12 100644
--- a/gv_file.pl
+++ b/gv_file.pl
@@ -146,11 +146,8 @@ file_to_gv(FromFile, ToFile, Options):-
   (
     var(ToFile)
   ->
-    absolute_file_name(
-      data(export),
-      ToFile,
-      [access(write),file_type(ToFileType)]
-    )
+    user:prolog_file_type(ToExtension, ToFileType),
+    file_alternative(FromFile, _, _, ToExtension, ToFile)
   ;
     is_absolute_file_name(ToFile),
     % The given output file must match a certain file extension.
 
 
diff --git a/gv_file.pl b/gv_file.pl
index 2d2ed12..9eaf66b 100644
--- a/gv_file.pl
+++ b/gv_file.pl
@@ -28,20 +28,16 @@ and GraphViz output files or SVG DOM structures.
 @version 2011-2013/09, 2013/11-2014/01, 2014/05, 2014/07
 */
 
-:- use_module(library(memfile)).
 :- use_module(library(option)).
 :- use_module(library(process)).
 :- use_module(library(predicate_options)). % Declarations.
 
 :- use_module(generics(codes_ext)).
 :- use_module(generics(db_ext)).
-:- use_module(generics(error_ext)).
-:- use_module(generics(trees)).
 :- use_module(os(file_ext)).
 :- use_module(os(run_ext)).
 :- use_module(os(safe_file)).
 :- use_module(svg(svg_file)).
-:- use_module(ugraph(ugraph_export)).
 
 :- use_module(plGraphViz(gv_dot)).
 
diff --git a/gv_gif.pl b/gv_gif.pl
index e3a6933..48ecd2f 100644
--- a/gv_gif.pl
+++ b/gv_gif.pl
@@ -27,7 +27,7 @@ Support for creating GIF representations.
 
 :- use_module(generics(list_ext)).
 :- use_module(generics(option_ext)).
-:- use_module(graph_theory(graph_generic)).
+:- use_module(graph_theory(graph_theory)).
 
 :- use_module(plRdf(rdf_name)). % Meta-DCG.
 
 
 
diff --git a/gv_gif.pl b/gv_gif.pl
index 48ecd2f..2fad4c4 100644
--- a/gv_gif.pl
+++ b/gv_gif.pl
@@ -105,7 +105,7 @@ create_gif(Vs, Es, graph(VTerms,ETerms,GAttrs), Options):-
 %   * =|edge_style(+atom)|=
 
 edge_term(Vs, E, edge(FromId,ToId,EAttrs), Options):-
-  edge_components(E, FromV, ToV),
+  edge(E, FromV, ToV),
   nth0chk(FromId, Vs, FromV),
   nth0chk(ToId, Vs, ToV),
 
 
 
diff --git a/gv_gif.pl b/gv_gif.pl
index 2fad4c4..48ecd2f 100644
--- a/gv_gif.pl
+++ b/gv_gif.pl
@@ -105,7 +105,7 @@ create_gif(Vs, Es, graph(VTerms,ETerms,GAttrs), Options):-
 %   * =|edge_style(+atom)|=
 
 edge_term(Vs, E, edge(FromId,ToId,EAttrs), Options):-
-  edge(E, FromV, ToV),
+  edge_components(E, FromV, ToV),
   nth0chk(FromId, Vs, FromV),
   nth0chk(ToId, Vs, ToV),
 
 
 
diff --git a/gv_gif.pl b/gv_gif.pl
index 48ecd2f..d59e5f8 100644
--- a/gv_gif.pl
+++ b/gv_gif.pl
@@ -65,7 +65,7 @@ Support for creating GIF representations.
 %! create_gif(+Edges:ordset, -Gif:compound, +Options:list(nvpair)) is det.
 
 create_gif(Es, Gif, Options):-
-  edges_to_vertices(Es, Vs),
+  graph_theory:edges_to_vertices(Es, Vs),
   create_gif(Vs, Es, Gif, Options).
 
 %! create_gif(
@@ -105,7 +105,7 @@ create_gif(Vs, Es, graph(VTerms,ETerms,GAttrs), Options):-
 %   * =|edge_style(+atom)|=
 
 edge_term(Vs, E, edge(FromId,ToId,EAttrs), Options):-
-  edge_components(E, FromV, ToV),
+  graph_theory:edge_components(E, FromV, ToV),
   nth0chk(FromId, Vs, FromV),
   nth0chk(ToId, Vs, ToV),
 
 
 
diff --git a/.gitignore b/.gitignore
index b25c15b..bd24270 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,3 @@
 *~
+*#
+*.db
diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index a63cbff..0000000
--- a/.gitmodules
+++ /dev/null
@@ -1,6 +0,0 @@
-[submodule "Prolog-Library-Collection"]
-	path = Prolog-Library-Collection
-	url = https://github.com/wouterbeek/Prolog-Library-Collection.git
-[submodule "plHtml"]
-	path = plHtml
-	url = https://github.com/wouterbeek/plHtml.git
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..c7b7798
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Wouter Beek
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/Prolog-Library-Collection b/Prolog-Library-Collection
deleted file mode 160000
index 5b34b01..0000000
--- a/Prolog-Library-Collection
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 5b34b01ab0cc82a56e6fd90fc26e75da22f5a0fe
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..23a45db
--- /dev/null
+++ b/README.md
@@ -0,0 +1,151 @@
+plGraphViz
+==========
+
+This library allows you to easily export graphs represented as Prolog terms
+using [GraphViz](http://www.graphviz.org/), an advanced graph drawing library.
+The Prolog terms have the following form:
+
+~~~prolog
+graph(Vertices, Edges, GraphAttrs)
+~~~
+
+`Vertices` and `Edges` are lists of compound terms of the following form:
+
+~~~prolog
+vertex(Id, VertexAttrs)
+edge(FromId, ToId, EdgeAttrs)
+~~~
+
+`Id` identifies a vertex and may or may not occur in any of the edges
+(i.e., unconnected vertices are allowed).
+`FromId` and `ToId` may occur in the list of vertices,
+in order to draw an edge between vertices with set attributes.
+
+Attributes have the form `Name=Value`.
+`GraphAttrs` are attributes of the graph.
+`VertexAttrs` are attributes of the vertex.
+`EdgeAttrs` are attributes of the edge.
+
+Attribute values are given as Prolog terms as well,
+and are type-checked before exporting.
+Many of the GraphViz attributes are supported.
+New ones are added on an as-needed bases
+(open an issue on Github if you want to see a specific feature added!).
+HTML-like labels are supported, allowing complex tables to be shown
+inside vertices.
+
+---
+
+### Installation
+
+~~~shell
+$ git clone https://github.com/wouterbeek/plGraphViz.git
+$ cd plGraphViz
+$ git submodule update --init
+~~~
+
+### Example
+
+~~~prolog
+$ swipl run.pl
+?- export_graph_to_gv_file(
+     graph([vertex(1,[]),vertex(2,[])],[edge(1,2,[])],[]),
+     File,
+     [method(sfdp),output(png)]
+   ).
+   File = 'PATH/plGraphViz/data/tmp.png'
+~~~
+
+![](https://raw.githubusercontent.com/wouterbeek/plGraphViz/master/example1.png "Example graph.")
+
+The graphic can be saved to a different file by instantiating
+the `File` argument.
+
+---
+
+### 'Method' option
+
+Option `method(+atom)` sets the drawing method that is used by GraphViz
+ to place the vertices and edges.
+The following values are supported.
+
+| **Value**       | **Description**         |
+| `circo`         | Circular layout.        |
+| `dot` (default) | Directed graph.         |
+| `fdp`           | Undirected graph.       |
+| `neato`         | Undirected graph.       |
+| `osage`         | Tree map.               |
+| `sfdp`          | Large undirected graph. |
+| `twopi`         | Radical layouts.        |
+
+---
+
+### 'Output' option
+
+Option `output(+atom)` sets the type of file the graph is written to.
+The following values are supported.
+
+| **Value**             | **Description**                       |
+|:---------------------:|:--------------------------------------|
+| `bmp`                 | Windows Bitmap Format                 |
+| `canon`               |                                       |
+| `dot`                 |                                       |
+| `gv`,  `xdot`, `xdot1.2`, `xdot1.4` | DOT                     |
+| `cgimage`             | CGImage bitmap format                 |
+| `cmap`                | Client-side imagemap (deprecated)     |
+| `eps`                 | Encapsulated PostScript               |
+| `exr`                 | OpenEXR                               |
+| `fig`                 |                                       |
+| `gd`, `gd2`           | GD/GD2 formats                        |
+| `gif`                 |                                       |
+| `gtk`                 | GTK canvas                            |
+| `ico`                 | Icon Image File Format                |
+| `imap`                |                                       |
+| `cmapx`               | Server-side and client-side imagemaps |
+| `imap_np`, `cmapx_np` | Server-side and client-side imagemaps |
+| `ismap`               | Server-side imagemap (deprecated)     |
+| `jp2`                 | JPEG 2000                             |
+| `jpg`, `jpeg`, `jpe`  | JPEG                                  |
+| `pct`, `pict`         | PICT                                  |
+| `pdf` (default)       | Portable Document Format (PDF)        |
+| `pic`                 | Kernighan's PIC graphics language     |
+| `plain`, `plain-ext`  | Simple text format                    |
+| `png`                 | Portable Network Graphics format      |
+| `pov`                 | POV-Ray markup language (prototype)   |
+| `ps`                  | PostScript                            |
+| `ps2`                 | PostScript for PDF                    |
+| `psd`                 | PSD                                   |
+| `sgi`                 | SGI                                   |
+| `svg`, `svgz`         | Scalable Vector Graphics              |
+| `tga`                 | Truevision TGA                        |
+| `tif`, `tiff`         | TIFF (Tag Image File Format)          |
+| `tk`                  | TK graphics                           |
+| `vml`, `vmlz`         | Vector Markup Language (VML)          |
+| `vrml`                | VRML                                  |
+| `wbmp`                | Wireless BitMap format                |
+| `webp`                | Image format for the Web              |
+| `xlib`, `x11`         | Xlib canvas                           |
+
+---
+
+### HTML-like labels
+
+Example of using HTML-like labels:
+
+~~~prolog
+export_graph_to_gv_file(
+  graph(
+    [vertex(1,[]),vertex(2,[label=html(table([tr([td(a),td(b)]),tr([td(c),td(d)])]))])],
+    [edge(1,2,[label='From 1 to 2.'])],
+    []
+  ),
+  File,
+  []
+).
+~~~
+
+![](https://raw.githubusercontent.com/wouterbeek/plGraphViz/master/example2.png "Example graph with HTML-like labels.")
+
+---
+
+Developed during 2013-2014 by [Wouter Beek](http://www.wouterbeek.com).
diff --git a/data/gv_attrs_scrape.pl b/data/gv_attrs_scrape.pl
new file mode 100644
index 0000000..bcc7654
--- /dev/null
+++ b/data/gv_attrs_scrape.pl
@@ -0,0 +1,119 @@
+:- module(
+  gv_attrs_scrape,
+  [
+    gv_attrs_scrape/1 % +File
+  ]
+).
+
+/** <module> GraphViz: Scrape attributes
+
+Writes compound terms of the following form to file:
+
+```prolog
+gv_attr(
+  ?Name,
+  ?UsedBy:list(oneof([cluster,edge,graph,node,subgraph])),
+  ?Types:list(atom),
+  ?Default,
+  ?Minimum,
+  ?Notes
+) is nondet.
+```
+
+@author Wouter Beek
+@version 2015/10, 2016/07
+*/
+
+:- use_module(library(apply)).
+:- use_module(library(dcg/dcg_ext)).
+:- use_module(library(debug)).
+:- use_module(library(gv/gv_attr_type)).
+:- use_module(library(http/http_download)).
+:- use_module(library(lists)).
+:- use_module(library(os/io)).
+:- use_module(library(pl_term)).
+:- use_module(library(print_ext)).
+:- use_module(library(xpath)).
+:- use_module(library(xpath/xpath_table)).
+:- use_module(library(yall)).
+
+
+
+
+
+%! gv_attrs_scrape(+File) is det.
+
+gv_attrs_scrape(File):-
+  debug(gv, "Updating the GraphViz attributes table.", []),
+  call_to_stream(File, [In,Meta,Meta]>>gv_attrs_download(In)).
+
+
+gv_attrs_download(Write):-
+  gv_attrs_iri(Iri),
+  html_download(Iri, Dom),
+  xpath_chk(Dom, //table(@align=lower_case(center)), TableDom),
+  xpath_table(TableDom, _, Rows),
+  maplist(write_attr_row(Write), Rows).
+
+
+write_attr_row(Write, [Name,UsedBy1,Types1,Default1,Minimum,Notes]):-
+  atom_phrase(translate_usedby(UsedBy2), UsedBy1),
+  once(atom_phrase(translate_type(Types2), Types1)),
+  sort(UsedBy2, UsedBy3),
+  translate_default(Default1, Default2),
+  with_output_to(
+    Write,
+    write_fact(gv_attr(Name, UsedBy3, Types2, Default2, Minimum, Notes))
+  ).
+
+gv_attrs_iri('http://www.graphviz.org/doc/info/attrs.html').
+
+
+
+
+
+% HELPERS %
+
+%! translate_default(+Default1, -Default2) is det.
+
+% The empty string is represented by the empty atom.
+translate_default('""', ''):- !.
+% The absence of a default value is represented by an uninstantiated variable.
+translate_default('<none>', _):- !.
+translate_default(Default, Default).
+
+
+
+%! translate_type(-Types:list(atom))// is det.
+
+translate_type([H|T]) -->
+  gv_attr_type(H),
+  whites,
+  translate_type(T).
+translate_type([H]) -->
+  gv_attr_type(H).
+translate_type([]) --> "".
+
+
+
+%! translated_usedby(
+%!   -UsedBy:list(oneof([cluster,edge,graph,node,subgraph]))
+%! )// is det.
+
+translate_usedby([cluster|T]) -->
+  "C", !,
+  translate_usedby(T).
+translate_usedby([edge|T]) -->
+  "E", !,
+  translate_usedby(T).
+translate_usedby([graph|T]) -->
+  "G", !,
+  translate_usedby(T).
+translate_usedby([node|T]) -->
+  "N", !,
+  translate_usedby(T).
+translate_usedby([subgraph|T]) -->
+  "S", !,
+  translate_usedby(T).
+translate_usedby([]) -->
+  "".
diff --git a/data/gv_color_scrape.pl b/data/gv_color_scrape.pl
new file mode 100644
index 0000000..102a02a
--- /dev/null
+++ b/data/gv_color_scrape.pl
@@ -0,0 +1,52 @@
+:- module(
+  gv_color_scrape,
+  [
+    gv_color_scrape/1 % +File
+  ]
+).
+
+/** <module> GraphViz: Scrape colors
+
+@author Wouter Beek
+@version 2015/10, 2016/07
+*/
+
+:- use_module(library(apply)).
+:- use_module(library(debug)).
+:- use_module(library(http/http_download)).
+:- use_module(library(lists)).
+:- use_module(library(os/io)).
+:- use_module(library(pl_term)).
+:- use_module(library(print_ext)).
+:- use_module(library(xpath)).
+:- use_module(library(xpath/xpath_table)).
+:- use_module(library(yall)).
+
+
+
+
+
+%! gv_color_scrape(+File) is det.
+
+gv_color_scrape(File):-
+  debug(io, "Updating the GraphViz color table.", []),
+  call_to_stream(File, [In,Meta,Meta]>>gv_color_download(In)).
+
+
+gv_color_download(Write):-
+  gv_color_iri(Iri),
+  html_download(Iri, Dom),
+  xpath_chk(Dom, //table(1), TableDom1),
+  xpath_chk(Dom, //table(2), TableDom2),
+  maplist(write_color_table(Write), [x11,svg], [TableDom1,TableDom2]).
+
+
+write_color_table(Write, Colorscheme, TableDom):-
+  xpath_table(TableDom, _, Rows),
+  append(Rows, Cells),
+  forall(
+    member(Cell, Cells),
+    with_output_to(Write, write_fact(gv_color(Colorscheme, Cell)))
+  ).
+
+gv_color_iri('http://www.graphviz.org/doc/info/colors.html').
diff --git a/example1.png b/example1.png
new file mode 100644
index 0000000..31e7ba6
Binary files /dev/null and b/example1.png differ
diff --git a/example2.png b/example2.png
new file mode 100644
index 0000000..6e44b16
Binary files /dev/null and b/example2.png differ
diff --git a/gv_attr_type.pl b/gv_attr_type.pl
deleted file mode 100644
index 7a64c82..0000000
--- a/gv_attr_type.pl
+++ /dev/null
@@ -1,417 +0,0 @@
-:- module(
-  gv_attr_type,
-  [
-    gv_attr_type/1, % ?Type:atom
-    addDouble//1, % +Double:float
-    addPoint//1, % +Point:compound
-    arrowType//1, % +ArrowType:atom
-    bool//1, % +Boolean:boolean
-    clusterMode//1, % +ClusterMode:atom
-    dirType//1, % +DirectionType:oneof([back,both,forward,none])
-    double//1, % +Double:float
-    doubleList//1, % +Doubles:list(float)
-    escString//1,
-    %layerList//1,
-    %layerRange//1,
-    lblString//1,
-    int//1, % +Integer:integer
-    outputMode//1, % +OutputMode:atom
-    %packMode//1,
-    pagedir//1, % +Pagedir:atom
-    point//1, % +Point:compound
-    pointList//1, % +Points:list(compound)
-    %portPos//1,
-    quadType//1, % +QuadType:atom
-    rankType//1, % +RankType:atom
-    rankdir//1, % +RankDirection:atom
-    rect//1, % +Rectangle:compound
-    shape//1,
-    smoothType//1, % +SmoothType:atom
-    %splineType//1,
-    %startType//1,
-    string//1, % ?Content:atom
-    style//2 % +Context:oneof([cluster,edge,node])
-             % +Style:atom
-    %viewPort//1
-  ]
-).
-:- reexport(
-  plGraphViz(gv_color),
-  [
-    color//1, % +Color:compound
-    colorList//1 % +ColorList:list(compound)
-  ]
-).
-
-/** <module> GraphViz attribute types
-
-@author Wouter Beek
-@version 2014/06
-*/
-
-:- use_module(dcg(dcg_abnf)).
-:- use_module(dcg(dcg_cardinal)).
-:- use_module(dcg(dcg_content)).
-
-:- use_module(plGraphViz(gv_html)).
-
-
-
-%! gv_attr_type(?Type:atom) is nondet.
-
-gv_attr_type(addDouble).
-gv_attr_type(addPoint).
-gv_attr_type(arrowType).
-gv_attr_type(bool).
-gv_attr_type(color).
-gv_attr_type(colorList).
-gv_attr_type(clusterMode).
-gv_attr_type(dirType).
-gv_attr_type(double).
-gv_attr_type(doubleList).
-gv_attr_type(escString).
-gv_attr_type(layerList).
-gv_attr_type(layerRange).
-gv_attr_type(lblString).
-gv_attr_type(int).
-gv_attr_type(outputMode).
-gv_attr_type(packMode).
-gv_attr_type(pagedir).
-gv_attr_type(point).
-gv_attr_type(pointList).
-gv_attr_type(portPos).
-gv_attr_type(quadType).
-gv_attr_type(rankType).
-gv_attr_type(rankdir).
-gv_attr_type(rect).
-gv_attr_type(shape).
-gv_attr_type(smoothType).
-gv_attr_type(splineType).
-gv_attr_type(startType).
-gv_attr_type(string).
-gv_attr_type(style).
-gv_attr_type(viewPort).
-
-
-%! addDouble(+Float:float)// .
-% An *addDouble* is represented by a Prolog float.
-
-addDouble(Float) -->
-  '?'(`+`),
-  double(Float).
-
-
-%! addPoint(+Point:compound)// .
-% An *addPoint* is represented by a compound of the following form:
-% `point(X:float,Y:float,InputOnly:boolean)`.
-
-addPoint(Point) -->
-  '?'(`+`),
-  point(Point).
-
-
-%! arrowType(+ArrowType:atom)// .
-
-arrowType(V) -->
-  {arrowType(V)},
-  atom(V).
-
-arrowType(V):-
-  primitive_shape(V).
-arrowType(V):-
-  derived(V).
-arrowType(V):-
-  backwards_compatible(V).
-
-primitive_shape(box).
-primitive_shape(crow).
-primitive_shape(circle).
-primitive_shape(diamond).
-primitive_shape(dot).
-primitive_shape(inv).
-primitive_shape(none).
-primitive_shape(normal).
-primitive_shape(tee).
-primitive_shape(vee).
-
-derived(odot).
-derived(invdot).
-derived(invodot).
-derived(obox).
-derived(odiamond).
-
-backwards_compatible(ediamond).
-backwards_compatible(empty).
-backwards_compatible(halfopen).
-backwards_compatible(invempty).
-backwards_compatible(open).
-
-
-bool(false) --> `false`.
-bool(false) --> `no`.
-bool(true) --> `true`.
-bool(true) --> `yes`.
-
-
-%! clusterMode(+ClusterMode:atom)// .
-
-clusterMode(V) -->
-  {clusterMode(V)},
-  atom(V).
-
-clusterMode(global).
-clusterMode(local).
-clusterMode(none).
-
-
-%! dirType(+DirectionType:oneof([back,both,forward,none]))// .
-
-dirType(DirType) -->
-  {dirType(DirType)},
-  atom(DirType).
-
-dirType(back).
-dirType(both).
-dirType(forward).
-dirType(none).
-
-
-double(Double1) -->
-  % float//1 will check for float type.
-  {Double2 is Double1 * 1.0},
-  float(Double2).
-
-
-doubleList([H|T]) -->
-  double(H),
-  '*'(doubleList1, T).
-
-doubleList1(Float) -->
-  `:`,
-  double(Float).
-
-
-%! escString(+String:atom)// .
-% @tbd Support for context-dependent replacements.
-
-escString(String) -->
-  atom(String).
-
-
-% @tbd layerList
-
-
-% @tbd layerRange
-
-
-lblString(V) -->
-  escString(V).
-lblString(V) -->
-  gv_html_like_label(V).
-
-
-int(V) -->
-  integer(V).
-
-
-outputMode(V) -->
-  {outputMode(V)},
-  atom(V).
-
-outputMode(breadthfirst).
-outputMode(edgesfirst).
-outputMode(nodesfirst).
-
-
-% @tbd packMode
-
-
-pagedir(V) -->
-  {pagedir(V)},
-  atom(V).
-
-pagedir('BL').
-pagedir('BR').
-pagedir('LB').
-pagedir('LT').
-pagedir('RB').
-pagedir('RT').
-pagedir('TL').
-pagedir('TR').
-
-
-%! point(+Point:compound)// .
-% A *point* is represented by a compound of the following form:
-% `point(X:float,Y:float,InputOnly:boolean)`.
-
-point(point(X,Y,InputOnly)) -->
-  float(X),
-  `,`,
-  float(Y),
-  input_only(InputOnly).
-
-input_only(false) --> [].
-input_only(true) --> `!`.
-
-
-pointList(Points) -->
-  '*'(point, Points).
-
-
-% @tbd portPos
-
-
-quadType(V) -->
-  {quadType(V)},
-  atom(V).
-
-quadType(fast).
-quadType(none).
-quadType(normal).
-
-
-rankType(V) -->
-  {rankType(V)},
-  atom(V).
-
-rankType(max).
-rankType(min).
-rankType(same).
-rankType(sink).
-rankType(source).
-
-
-rankdir(V) -->
-  {rankdir(V)},
-  atom(V).
-
-rankdir('BT').
-rankdir('LR').
-rankdir('RL').
-rankdir('TB').
-
-
-rect(rect(LowerLeftX,LowerLeftY,UpperRightX,UpperRightY)) -->
-  float(LowerLeftX), `,`,
-  float(LowerLeftY), `,`,
-  float(UpperRightX), `,`,
-  float(UpperRightY).
-
-
-shape(V) -->
-  {polygon_based_shape(V)},
-  atom(V).
-
-polygon_based_shape(assembly).
-polygon_based_shape(box).
-polygon_based_shape(box3d).
-polygon_based_shape(cds).
-polygon_based_shape(circle).
-polygon_based_shape(component).
-polygon_based_shape(diamond).
-polygon_based_shape(doublecircle).
-polygon_based_shape(doubleoctagon).
-polygon_based_shape(egg).
-polygon_based_shape(ellipse).
-polygon_based_shape(fivepoverhang).
-polygon_based_shape(folder).
-polygon_based_shape(hexagon).
-polygon_based_shape(house).
-polygon_based_shape(insulator).
-polygon_based_shape(invhouse).
-polygon_based_shape(invtrapezium).
-polygon_based_shape(invtriangle).
-polygon_based_shape(larrow).
-polygon_based_shape(lpromoter).
-polygon_based_shape('Mcircle').
-polygon_based_shape('Mdiamond').
-polygon_based_shape('Msquare').
-polygon_based_shape(none).
-polygon_based_shape(note).
-polygon_based_shape(noverhang).
-polygon_based_shape(octagon).
-polygon_based_shape(oval).
-polygon_based_shape(parallelogram).
-polygon_based_shape(pentagon).
-polygon_based_shape(plaintext).
-polygon_based_shape(point).
-polygon_based_shape(polygon).
-polygon_based_shape(primersite).
-polygon_based_shape(promoter).
-polygon_based_shape(proteasesite).
-polygon_based_shape(proteinstab).
-polygon_based_shape(rarrow).
-polygon_based_shape(rect).
-polygon_based_shape(rectangle).
-polygon_based_shape(restrictionsite).
-polygon_based_shape(ribosite).
-polygon_based_shape(rnastab).
-polygon_based_shape(rpromoter).
-polygon_based_shape(septagon).
-polygon_based_shape(signature).
-polygon_based_shape(square).
-polygon_based_shape(tab).
-polygon_based_shape(terminator).
-polygon_based_shape(threepoverhang).
-polygon_based_shape(trapezium).
-polygon_based_shape(triangle).
-polygon_based_shape(tripleoctagon).
-polygon_based_shape(utr).
-
-
-smoothType(V) -->
-  {smoothType(V)},
-  atom(V).
-
-smoothType(avg_dist).
-smoothType(graph_dist).
-smoothType(none).
-smoothType(power_dist).
-smoothType(rng).
-smoothType(spring).
-smoothType(triangle).
-
-
-% @tbd splineType
-
-
-% @tbd startType
-
-
-%! string(?Content:atom)// .
-% A GraphViz string.
-
-string(Content) -->
-  atom(Content).
-
-
-%! style(?Context:oneof([cluster,edge,node]), ?Style:atom) is nondet.
-
-style(Context, Style) -->
-  {style(Context, Style)},
-  atom(Style).
-
-style(cluster, bold).
-style(cluster, dashed).
-style(cluster, dotted).
-style(cluster, filled).
-style(cluster, rounded).
-style(cluster, solid).
-style(cluster, striped).
-style(edge, bold).
-style(edge, dashed).
-style(edge, dotted).
-style(edge, solid).
-style(node, bold).
-style(node, dashed).
-style(node, diagonals).
-style(node, dotted).
-style(node, filled).
-style(node, rounded).
-style(node, solid).
-style(node, striped).
-style(node, wedged).
-
-
-% @tbd viewPort
-
diff --git a/gv_attrs.pl b/gv_attrs.pl
deleted file mode 100644
index dd0145a..0000000
--- a/gv_attrs.pl
+++ /dev/null
@@ -1,184 +0,0 @@
-:- module(
-  gv_attrs,
-  [
-    gv_attr/3 % +Context:oneof([cluster,edge,graph,node,subgraph])
-              % +Attr1:nvpair
-              % +Attr2:nvpair
-  ]
-).
-
-/** <module> GraphViz attributes v2
-
-@author Wouter Beek
-@version 2014/06
-*/
-
-:- use_module(library(apply)).
-:- use_module(library(persistency)).
-:- use_module(library(xpath)).
-
-:- use_module(dcg(dcg_content)).
-:- use_module(dcg(dcg_generic)).
-:- use_module(generics(db_ext)).
-:- use_module(os(file_ext)).
-
-:- use_module(plHtml(html)).
-:- use_module(plHtml(html_table)).
-
-:- use_module(plGraphViz(gv_attr_type)). % DCGs implementing attribute types.
-
-:- db_add_novel(user:prolog_file_type(log, logging)).
-
-%! gv_attr(
-%!   ?Name:atom,
-%!   ?UsedBy:list(oneof([cluster,edge,graph,node,subgraph])),
-%!   ?Types:list(atom),
-%!   ?Default,
-%!   ?Minimum,
-%!   ?Notes:atom
-%! ) is nondet.
-
-:- persistent(
-  gv_attr(
-    name:atom,
-    used_by:list(oneof([cluster,edge,graph,node,subgraph])),
-    types:list(atom),
-    default,
-    minimum,
-    notes:atom
-  )
-).
-
-:- initialization(gv_attrs_init).
-
-
-
-gv_attr(Context, N=V, N=V):-
-  var(V), !,
-  gv_attr(N, UsedBy, _, V, _, _),
-  memberchk(Context, UsedBy).
-gv_attr(Context, N=V1, N=V2):-
-  gv_attr(N, UsedBy, Types, _, Minimum, _),
-  memberchk(Context, UsedBy),
-  member(Type, Types),
-  (
-    Type == style
-  ->
-    Dcg =.. [Type,Context,V1]
-  ;
-    Dcg =.. [Type,V1]
-  ),
-  once(dcg_phrase(Dcg, V2)),
-  check_minimum(V1, Minimum).
-
-check_minimum(_, ''):- !.
-check_minimum(V, Min1):-
-  atom_number(Min1, Min2),
-  Min2 =< V.
-
-
-
-% INITIALIZATION
-
-%! assert_gv_attr_row(+Row:list(atom)) is det.
-
-assert_gv_attr_row([Name,UsedBy1,Types1,Default1,Minimum,Notes]):-
-  dcg_phrase(translate_usedby(UsedBy2), UsedBy1),
-  once(dcg_phrase(translate_type(Types2), Types1)),
-  sort(UsedBy2, UsedBy3),
-  translate_default(Default1, Default2),
-  assert_gv_attr(Name, UsedBy3, Types2, Default2, Minimum, Notes).
-
-
-%! gv_attrs_downloads is det.
-% Downloads the table describing GraphViz attributes from `graphviz.org`.
-
-gv_attrs_download:-
-  gv_attrs_url(Url),
-  download_html(Url, Dom, [html_dialect(html4),verbose(silent)]),
-
-  xpath(Dom, //table(@align=center), TableDom),
-  % @tbd This does not work, since in `record_name(Element, Name)`,
-  %      `Element` is a signleton list whereas a compound term is expected.
-  %%%%xpath(Dom, /html/body/table, TableDom),
-
-  html_to_table(TableDom, _, Rows),
-  maplist(assert_gv_attr_row, Rows).
-
-
-%! gv_attrs_file(-File:atom) is det.
-
-gv_attrs_file(File):-
-  absolute_file_name(
-    data(gv_attrs),
-    File,
-    [access(write),file_type(logging)]
-  ).
-
-
-%! gv_attrs_init is det.
-
-gv_attrs_init:-
-  gv_attrs_file(File),
-  safe_db_attach(File),
-  file_age(File, Age),
-  gv_attrs_update(Age).
-
-
-%! gv_attrs_update(+Age:float) is det.
-
-% The persistent store is still fresh.
-gv_attrs_update(Age):-
-  once(gv_attr(_, _, _, _, _, _)),
-  Age < 8640000, !.
-% The persistent store has become stale, so refresh it.
-gv_attrs_update(_):-
-  retractall_gv_attr(_, _, _, _, _, _),
-  gv_attrs_download.
-
-
-%! gv_attrs_url(-Url:url) is det.
-
-gv_attrs_url('http://www.graphviz.org/doc/info/attrs.html').
-
-
-%! safe_db_attach(+File:atom) is det.
-
-safe_db_attach(File):-
-  exists_file(File), !,
-  db_attach(File, []).
-safe_db_attach(File):-
-  touch_file(File),
-  safe_db_attach(File).
-
-
-%! translate_default(+Default1:atom, -Default2:atom) is det.
-
-% The empty string is represented by the empty atom.
-translate_default('""', ''):- !.
-% The absence of a default value is represented by an uninstantiated variable.
-translate_default('<none>', _):- !.
-translate_default(Default, Default).
-
-
-%! translate_type(-Types:list(atom))// is det.
-
-translate_type([H|T]) -->
-  {gv_attr_type(H)},
-  atom(H),
-  whites,
-  translate_type(T).
-translate_type([]) --> !, [].
-
-
-%! translated_usedby(
-%!   -UsedBy:list(oneof([cluster,edge,graph,node,subgraph]))
-%! )// is det.
-
-translate_usedby([cluster|T]) --> `C`, !, translate_usedby(T).
-translate_usedby([edge|T]) --> `E`, !, translate_usedby(T).
-translate_usedby([graph|T]) --> `G`, !, translate_usedby(T).
-translate_usedby([node|T]) --> `N`, !, translate_usedby(T).
-translate_usedby([subgraph|T]) --> `S`, !, translate_usedby(T).
-translate_usedby([]) --> [].
-
diff --git a/gv_color.pl b/gv_color.pl
deleted file mode 100644
index 19d6e05..0000000
--- a/gv_color.pl
+++ /dev/null
@@ -1,151 +0,0 @@
-:- module(
-  gv_color,
-  [
-    gv_color/2, % ?Colorscheme:oneof([svg,x11])
-                % ?Color:atom
-    color//1, % +Color:compound
-    colorList//1 % +Pairs:list(pair(compound,float))
-  ]
-).
-
-/** <module> GraphViz color
-
-@author Wouter Beek
-@tbd Color value `transparent` is only available in the output formats
-     ps, svg, fig, vmrl, and the bitmap formats.
-@version 2014/06
-*/
-
-:- use_module(library(apply)).
-:- use_module(library(lists)).
-:- use_module(library(persistency)).
-:- use_module(library(xpath)).
-
-:- use_module(dcg(dcg_abnf)).
-:- use_module(dcg(dcg_cardinal)).
-:- use_module(dcg(dcg_content)).
-:- use_module(generics(db_ext)).
-:- use_module(os(file_ext)).
-:- use_module(sparql(sparql_char)).
-
-:- use_module(plHtml(html)).
-:- use_module(plHtml(html_table)).
-
-:- db_add_novel(user:prolog_file_type(log, logging)).
-
-%! gv_color(?Colorscheme:oneof([svg,x11]), ?Color:atom) is nondet.
-
-:- persistent(gv_color(colorscheme:oneof([svg,x11]),color:atom)).
-
-:- initialization(gv_color_init).
-
-
-
-% color(+Color:compound)// .
-% A *color* is represented by a compound term of one of the following forms:
-%   1. `rgb(Red:nonneg,Green:nonneg,Blue:nonneg)`
-%   2. `rgba(Red:nonneg,Green:nonneg,Blue:nonneg,Alpha:nonneg)`
-%   3. `hsv(Hue:between(0.0,1.0),Saturation:between(0.0,1.0),Value:between(0.0,1.0))`
-
-color(rgb(Red,Green,Blue)) --> !,
-  `#`,
-  '#'(3, hex_color, [Red,Green,Blue]).
-color(rgbs(Red,Green,Blue,Alpha)) --> !,
-  `#`,
-  '#'(4, hex_color, [Red,Green,Blue,Alpha]).
-color(hsv(Hue,Saturation,Value)) --> !,
-  '#'(3, hsv_color, [Hue,Saturation,Value]).
-color(Name) -->
-  {gv_color(_, Name)},
-  atom(Name).
-
-hex_color(I) -->
-  {W1 is I / 16},
-  'HEX'(W1),
-  {W2 is I mod 16},
-  'HEX'(W2).
-
-hsv_color(D, Head, Tail):-
-  format(codes(Head,Tail), '~2f', [D]).
-
-
-%! colorList(+Pairs:list(pair(compound,float)))// .
-
-colorList(Pairs) -->
-  '+'(wc, Pairs).
-
-wc(Color-Float) -->
-  color(Color),
-  '?'(wc_weight(Float)).
-
-wc_weight(Float) -->
-  `;`,
-  float(Float).
-
-
-
-% INITIALIZATION
-
-%! gv_color_download is det.
-
-gv_color_download:-
-  gv_color_url(Url),
-  download_html(Url, Dom, [html_dialect(html4),verbose(silent)]),
-  xpath(Dom, //table(1), TableDom1),
-  xpath(Dom, //table(2), TableDom2),
-  maplist(assert_color_table, [x11,svg], [TableDom1,TableDom2]).
-
-assert_color_table(Colorscheme, TableDom):-
-  html_to_table(TableDom, _, Rows),
-  append(Rows, Cells),
-  forall(
-    member(Cell, Cells),
-    assert_gv_color(Colorscheme, Cell)
-  ).
-
-
-%! gv_color_file(-File:atom) is det.
-
-gv_color_file(File):-
-  absolute_file_name(
-    data(gv_color),
-    File,
-    [access(write),file_type(logging)]
-  ).
-
-
-%! gv_color_init is det.
-
-gv_color_init:-
-  gv_color_file(File),
-  safe_db_attach(File),
-  file_age(File, Age),
-  gv_color_update(Age).
-
-
-%! gv_color_update(+Age:float) is det.
-
-% The persistent store is still fresh.
-gv_color_update(Age):-
-  once(gv_color(_, _)),
-  Age < 8640000, !.
-% The persistent store has become stale, so refresh it.
-gv_color_update(_):-
-  retractall_gv_color(_, _),
-  gv_color_download.
-
-
-%! gv_color_url(-Url:url) is det.
-
-gv_color_url('http://www.graphviz.org/doc/info/colors.html').
-
-
-%! safe_db_attach(+File:atom) is det.
-
-safe_db_attach(File):-
-  exists_file(File), !,
-  db_attach(File, []).
-safe_db_attach(File):-
-  touch_file(File),
-  safe_db_attach(File).
-
diff --git a/gv_dot.pl b/gv_dot.pl
deleted file mode 100644
index 68443ba..0000000
--- a/gv_dot.pl
+++ /dev/null
@@ -1,496 +0,0 @@
-:- module(
-  gv_dot,
-  [
-    gv_graph//1 % +GraphTerm:compound
-  ]
-).
-
-/** <module> GraphViz DOT generator
-
-DCG rules for GraphViz DOT file generation.
-
-Methods for writing to the GraphViz DOT format.
-
-In GraphViz vertices are called 'nodes'.
-
-@author Wouter Beek
-@see http://www.graphviz.org/content/dot-language
-@version 2013/07, 2013/09, 2014/03-2014/06
-*/
-
-:- use_module(library(apply)).
-:- use_module(library(lists)).
-:- use_module(library(ordsets)).
-
-:- use_module(dcg(dcg_abnf)).
-:- use_module(dcg(dcg_ascii)).
-:- use_module(dcg(dcg_content)).
-:- use_module(dcg(dcg_generic)).
-:- use_module(dcg(dcg_meta)).
-:- use_module(dcg(dcg_os)).
-
-:- use_module(plGraphViz(gv_attrs)).
-:- use_module(plGraphViz(gv_html)).
-:- use_module(plGraphViz(gv_numeral)).
-
-
-
-%! gv_attribute(+Attribute:nvpair)// is det.
-% A single GraphViz attribute.
-% We assume that the attribute has already been validated.
-
-gv_attribute(Name=Val) -->
-  gv_id(Name), `=`, gv_id(Val), `;`.
-
-
-%! gv_attribute_list(
-%!   +Context:oneof([cluster,edge,graph,node,subgraph]),
-%!   +GraphAttributes:list(nvpair),
-%!   +Attributes:list(nvpair)
-%! )// .
-% ~~~{.abnf}
-% attr_list = "[" [a_list] "]" [attr_list]
-% a_list = ID "=" ID [","] [a_list]
-% ~~~
-
-% Attributes occur between square brackets.
-gv_attribute_list(Context, _, Attrs1) -->
-  {maplist(gv_attr(Context), Attrs1, Attrs2)},
-  bracketed(square, '*'(gv_attribute, Attrs2)).
-
-
-%! gv_compass_pt(+Direction:oneof(['_',c,e,n,ne,nw,s,se,sw,w]))// .
-% ~~~
-% compass_pt : (n | ne | e | se | s | sw | w | nw | c | _)
-% ~~~
-
-gv_compass_pt('_') --> `_`.
-gv_compass_pt(c) --> `c`.
-gv_compass_pt(e) --> `e`.
-gv_compass_pt(n) --> `n`.
-gv_compass_pt(ne) --> `ne`.
-gv_compass_pt(nw) --> `nw`.
-gv_compass_pt(s) --> `s`.
-gv_compass_pt(se) --> `se`.
-gv_compass_pt(sw) --> `sw`.
-gv_compass_pt(w) --> `w`.
-
-
-%! gv_edge_operator(+Directedness:boolean)// .
-% The binary edge operator between two vertices.
-% The operator that is used depends on whether the graph is directed or
-% undirected.
-%
-% @arg Directedness Whether an edge is directed (operator `->`) or
-%                   undirected (operator `--`).
-
-gv_edge_operator(false) --> !, `--`.
-gv_edge_operator(true) --> arrow(right, 2).
-
-
-%! gv_edge_statement(
-%!   +Indent:nonneg,
-%!   +Directedness:boolean,
-%!   +GraphAttributes:list(nvpair),
-%!   +EdgeTerm:compound
-%! )// is det.
-% A GraphViz statement describing an edge.
-%
-% @arg Indent The indentation level at which the edge statement is written.
-% @arg Directedness Whether the graph is directed or not.
-% @arg GraphAttributes The attributes of the graph. Some of these attributes
-%      may be used in the edge statement (e.g., the colorscheme).
-% @arg EdgeTerm A compound term in the GIFormat, representing an edge.
-%
-% @tbd Instead of gv_node_id//1 we could have a gv_subgraph//1
-%      at the from and/or to location.
-% @tbd Add support for multiple, consecutive occurrences of gv_edge_rhs//2.
-
-gv_edge_statement(I, Directedness, GAttrs, edge(FromId,ToId,EAttrs)) -->
-  indent(I),
-  gv_node_id(FromId), ` `,
-
-  gv_edge_operator(Directedness), ` `,
-
-  gv_node_id(ToId), ` `,
-
-  % We want `colorscheme/1` from the edges and
-  % `directionality/1` from the graph.
-  gv_attribute_list(edge, GAttrs, EAttrs),
-  newline.
-
-
-%! gv_generic_attributes_statement(
-%!   +Kind:oneof([edge,graph,node]),
-%!   +Indent:integer,
-%!   +GraphAttributes:list(nvpair),
-%!   +CategoryAttributes:list(nvpair)
-%! )//
-% A GraphViz statement describing generic attributes for a category of items.
-%
-% @arg Kind The category of items for to the attributes apply.
-%      Possible values: `edge`, `graph`, and `node`.
-% @arg Indent An integer indicating the number of tabs.
-% @arg GraphAttributes A list of name-value pairs.
-% @arg CategoryAttributes A list of name-value pairs.
-%
-% ~~~
-% attr_stmt = (graph / node / edge) attr_list
-% ~~~
-
-gv_generic_attributes_statement(_, _, _, []) --> [], !.
-gv_generic_attributes_statement(Kind, I, GraphAttrs, KindAttrs) -->
-  indent(I),
-  gv_kind(Kind), ` `,
-  gv_attribute_list(Kind, GraphAttrs, KindAttrs), newline.
-
-
-%! gv_graph(+GraphTerm:compound)//
-% The follow graph attributes are supported,
-% beyond the GraphViz attributes for graphs:
-%   * `directedness(+boolean)`
-%      Whether the graph is directed (`true`) or undirected (`false`).
-%      Default: `false`.
-%   * `name(+GraphName:atom)`
-%   * `strict(+StrictGraph:boolean)`
-%      This forbids the creation of self-arcs and multi-edges;
-%      they are ignored in the input file.
-%      Only in combinattion with directionality `directed`.
-%
-% ~~~{.abnf}
-% graph = ["strict"] ("graph" / "digraph") [ID] "{" stmt_list "}"
-% ~~~
-%
-% `GraphTerm` is a compound term of the following form:
-% ~~~{.pl}
-% graph(VertexTerms,RankedVertexTerms,EdgeTerms,GraphAttributes)
-% ~~~
-%
-% `RankedVertexTerms` is a list of compound terms of the following form:
-% ~~~{.pl}
-% rank(RankNode,ContentNodes)
-% ~~~
-%
-% @tbd Add support for subgraphs (arbitrary nesting).
-% @tbd Add support for escape strings:
-%      http://www.graphviz.org/doc/info/attrs.html#k:escString
-% @tbd Assert attributes that are generic with respect to a subgraph.
-% @tbd Not all vertex and edge properties can be shared it seems (e.g., label).
-
-gv_graph(graph(VTerms, ETerms, GAttrs)) -->
-  gv_graph(graph(VTerms, [], ETerms, GAttrs)).
-
-gv_graph(graph(VTerms, RankedVTerms, ETerms, GAttrs1)) -->
-  {
-    shared_attributes(VTerms, SharedVAttrs, NewVTerms),
-    shared_attributes(ETerms, SharedEAttrs, NewETerms),
-    select_nvpair(strict=Strict, GAttrs1, GAttrs2, false),
-    select_nvpair(directedness=Directedness, GAttrs2, GAttrs3, true),
-    select_nvpair(name=GName, GAttrs3, GAttrs4, noname),
-    add_default_nvpair(GAttrs4, overlap, false, GAttrs5),
-    I = 0
-  },
-
-  % The first statement in the GraphViz output.
-  % States that this file represents a graph according to the GraphViz format.
-  indent(I),
-  gv_strict(Strict),
-  gv_graph_type(Directedness), ` `,
-  gv_id(GName), ` `,
-  bracketed(
-    curly,
-    gv_graph0(
-      I,
-      NewVTerms, SharedVAttrs, RankedVTerms,
-      NewETerms, SharedEAttrs,
-      Directedness, GAttrs5
-    )
-  ),                                                      
-  newline.
-
-gv_graph0(
-  I,
-  NewVTerms, SharedVAttrs, RankedVTerms,
-  NewETerms, SharedEAttrs,
-  Directedness, GAttrs
-) -->
-  newline,
-
-  % The following lines are indented.
-  {NewI is I + 1},
-
-  % Attributes that apply to the graph as a whole.
-  gv_generic_attributes_statement(graph, NewI, GAttrs, GAttrs),
-
-  % Attributes that are the same for all nodes.
-  gv_generic_attributes_statement(node, NewI, GAttrs, SharedVAttrs),
-
-  % Attributes that are the same for all edges.
-  gv_generic_attributes_statement(edge, NewI, GAttrs, SharedEAttrs),
-
-  % Only add a newline if some content was written in the previous three
-  % lines.
-  ({(GAttrs == [], SharedVAttrs == [], SharedEAttrs == [])} -> `` ; newline),
-
-  % The list of GraphViz nodes.
-  '*'(gv_node_statement(NewI, GAttrs), NewVTerms),
-  ({NewVTerms == []} -> `` ; newline),
-
-  % The ranked GraphViz nodes (displayed at the same height).
-  '*'(gv_ranked_node_collection(NewI, GAttrs), RankedVTerms),
-  ({RankedVTerms == []} -> `` ; newline),
-
-  {
-    findall(
-      edge(FromId,ToId,[]),
-      (
-        nth0(Index1, RankedVTerms, rank(vertex(FromId,_,_),_)),
-        nth0(Index2, RankedVTerms, rank(vertex(ToId,_,_),_)),
-        % We assume that the rank vertices are nicely ordered.
-        succ(Index1, Index2)
-      ),
-      RankEdges
-    )
-  },
-
-  % The rank edges.
-  '*'(gv_edge_statement(NewI, Directedness, GAttrs), RankEdges),
-
-  % The non-rank edges.
-  '*'(gv_edge_statement(NewI, Directedness, GAttrs), NewETerms),
-
-  % Note that we do not include a newline here.
-
-  % We want to indent the closing curly brace.
-  indent(I).
-
-
-%! gv_graph_type(+Directedness:boolean)// .
-% The type of graph that is represented.
-
-gv_graph_type(false) --> `graph`.
-gv_graph_type(true) --> `digraph`.
-
-
-%! gv_id(?Atom:atom)// is det.
-% Parse a GraphViz identifier.
-% There are 4 variants:
-%   1. Any string of alphabetic (`[a-zA-Z'200-'377]`) characters,
-%      underscores (`_`) or digits (`[0-9]`), not beginning with a digit.
-%   2. A numeral `[-]?(.[0-9]+ | [0-9]+(.[0-9]*)? )`.
-%   3. Any double-quoted string (`"..."`) possibly containing
-%      escaped quotes (`\"`).
-%      In quoted strings in DOT, the only escaped character is
-%      double-quote (`"`). That is, in quoted strings, the dyad `\"`
-%      is converted to `"`. All other characters are left unchanged.
-%      In particular, `\\` remains `\\`.
-%      Layout engines may apply additional escape sequences.
-%   4. An HTML string (`<...>`).
-%
-% @tbd Add support for HTML-like labels:
-%      http://www.graphviz.org/doc/info/shapes.html#html
-%      This requires an XML grammar!
-
-% HTML strings (variant 4).
-gv_id(Atom) -->
-  dcg_atom_codes(gv_html_like_label, Atom), !.
-% Alpha-numeric strings (variant 1).
-gv_id(Atom) -->
-  {atom_codes(Atom, [H|T])},
-  gv_id_first(H),
-  gv_id_rest(T), !,
-  % Variant 1 identifiers should not be (case-variants of) a
-  % GraphViz keyword.
-  {\+ gv_keyword([H|T])}.
-% Numerals (variant 2)
-gv_id(N) -->
-  {number(N)}, !,
-  gv_numeral(N).
-% Double-quoted strings (variant 3).
-% The quotes are already part of the given atom.
-gv_id(Atom) -->
-  {
-    atom_codes(Atom, [H|T]),
-    append(S, [H], T)
-  },
-  dcg_between(double_quote(H), gv_quoted_string(S)), !.
-% Double-quoted strings (variant 3).
-% The quotes are not in the given atom. They are written anyway.
-gv_id(Atom) -->
-  quoted(double_quote, dcg_atom_codes(gv_quoted_string, Atom)), !.
-
-gv_id_first(X) --> ascii_letter(X).
-gv_id_first(X) --> underscore(X).
-
-gv_id_rest([]) --> [].
-gv_id_rest([H|T]) -->
-  (ascii_alpha_numeric(H) ; underscore(H)),
-  gv_id_rest(T).
-
-
-%! gv_keyword(+Codes:list(code)) is semidet.
-% Succeeds if the given codes for a GraphViz reserved keyword.
-
-gv_keyword(Codes):-
-  % Obviously, the keywords do not occur on the difference list input.
-  % So we must use phrase/[2,3].
-  phrase(gv_keyword, Codes).
-
-%! gv_keyword// .
-% GraphViz has reserved keywords that cannot be used as identifiers.
-% GraphViz keywords are case-insensitive.
-
-gv_keyword --> `digraph`.
-gv_keyword --> `edge`.
-gv_keyword --> `graph`.
-gv_keyword --> `node`.
-gv_keyword --> `strict`.
-gv_keyword --> `subgraph`.
-
-
-%! gv_kind(+Kind:oneof([edge,graph,node]))// .
-
-gv_kind(edge) --> `edge`.
-gv_kind(graph) --> `graph`.
-gv_kind(node) --> `node`.
-
-
-%! gv_node_id(+NodeId:atom)// .
-% GraphViz node identifiers can be of the following two types:
-%   1. A GraphViz identifier, see gv_id//1.
-%   2. A GraphViz identifier plus a GraphViz port indicator, see gv_port//0.
-%
-% @tbd Add support for GraphViz port indicators
-%      inside GraphViz node identifiers.
-
-gv_node_id(Id) -->
-  gv_id(Id).
-%gv_node_id(_) -->
-%  gv_id(_),
-%  gv_port.
-
-
-%! gv_node_statement(
-%!   +Indent:integer,
-%!   +GraphAttributes,
-%!   +VertexTerm:compound
-%! )// .
-% A GraphViz statement describing a vertex (GraphViz calls vertices 'nodes').
-
-gv_node_statement(I, GraphAttrs, vertex(Id,_,VAttrs)) -->
-  indent(I),
-  gv_node_id(Id), ` `,
-  gv_attribute_list(node, GraphAttrs, VAttrs), newline.
-
-
-gv_port -->
-  gv_port_location,
-  '?'(gv_port_angle).
-gv_port -->
-  gv_port_angle,
-  '?'(gv_port_location).
-gv_port -->
-  `:`,
-  gv_compass_pt(_).
-
-gv_port_angle -->
-  `@`,
-  gv_id(_).
-
-gv_port_location -->
-  `:`,
-  gv_id(_).
-gv_port_location -->
-  `:`,
-  bracketed(
-    round,
-    (
-      gv_id(_),
-      `,`,
-      gv_id(_)
-    )
-  ).
-
-
-gv_quoted_string([]) --> [].
-% Just to be sure, we do not allow the double quote
-% that closes the string to be escaped.
-gv_quoted_string([X]) -->
-  {X \== 92}, !,
-  [X].
-% A double quote is only allowed if it is escaped by a backslash.
-gv_quoted_string([92,34|T]) --> !,
-  gv_quoted_string(T).
-% Add the backslash escape character.
-gv_quoted_string([34|T]) --> !,
-  `\\\"`,
-  gv_quoted_string(T).
-% All other characters are allowed without escaping.
-gv_quoted_string([H|T]) -->
-  [H],
-  gv_quoted_string(T).
-
-
-gv_ranked_node_collection(
-  I,
-  GraphAttrs,
-  rank(Rank_V_Term,Content_V_Terms)
-) -->
-  indent(I),
-  bracketed(curly, (
-    newline,
-
-    % The rank attribute.
-    {NewI is I + 1},
-    indent(NewI), gv_attribute(rank=same), `;`, newline,
-
-    '*'(gv_node_statement(NewI, GraphAttrs), [Rank_V_Term|Content_V_Terms]),
-
-    % We want to indent the closing curly brace.
-    indent(I)
-  )),
-  newline.
-
-
-%! gv_strict(+Strict:boolean)// is det.
-% The keyword denoting that the graph is strict, i.e., has no self-arcs and
-% no multi-edges.
-% This only applies to directed graphs.
-
-gv_strict(false) --> [].
-gv_strict(true) --> `strict `.
-
-
-
-% Helpers
-
-add_default_nvpair(Attrs1, N, Default, Attrs2):-
-  add_default_nvpair(Attrs1, N, Default, _, Attrs2).
-
-add_default_nvpair(Attrs, N, _, V, Attrs):-
-  memberchk(N=V, Attrs), !.
-add_default_nvpair(Attrs1, N, Default, Default, Attrs2):-
-  ord_add_element(Attrs1, N=Default, Attrs2).
-
-select_nvpair(N=V, Attrs1, Attrs2, _):-
-  memberchk(N=V, Attrs1), !,
-  select(N=V, Attrs1, Attrs2).
-select_nvpair(_=Default, Attrs, Attrs, Default).
-
-
-extract_shared([], []):- !.
-extract_shared(Argss, Shared):-
-  ord_intersection(Argss, Shared).
-
-remove_shared_attributes(Shared, Args1, Args2):-
-  ord_subtract(Args1, Shared, Args2).
-
-shared_attributes(Terms1, Shared, Terms2):-
-  maplist(term_components(Func), Terms1, Args1, Args2, Args3a),
-  extract_shared(Args3a, Shared),
-  maplist(remove_shared_attributes(Shared), Args3a, Args3b),
-  maplist(term_components(Func), Terms2, Args1, Args2, Args3b).
-
-term_components(Func, Term, Arg1, Arg2, Arg3):-
-  Term =.. [Func,Arg1,Arg2,Arg3].
-
diff --git a/gv_file.pl b/gv_file.pl
deleted file mode 100644
index 9eaf66b..0000000
--- a/gv_file.pl
+++ /dev/null
@@ -1,206 +0,0 @@
-:- module(
-  gv_file,
-  [
-    file_to_gv/2, % +File:atom
-                  % +Options:list(nvpair)
-    file_to_gv/3, % +FromFile:atom
-                  % ?ToFile:atom
-                  % +Options:list(nvpair)
-    gif_to_gv_file/3, % +GraphInterchangeFormat:compound
-                      % +ToFile:atom
-                      % +Options:list(nvpair)
-    gif_to_svg_dom/3, % +GraphInterchangeFormat:compound
-                      % -SvgDom:list(compound)
-                      % +Options:list(nvpair)
-    open_dot/1 % +File:file
-  ]
-).
-
-/** <module> GraphViz file
-
-Predicates for converting GIF-formatted terms
-into GraphViz output files or SVG DOM structures.
-
-Also converts between GraphViz DOT formatted files
-and GraphViz output files or SVG DOM structures.
-
-@author Wouter Beek
-@version 2011-2013/09, 2013/11-2014/01, 2014/05, 2014/07
-*/
-
-:- use_module(library(option)).
-:- use_module(library(process)).
-:- use_module(library(predicate_options)). % Declarations.
-
-:- use_module(generics(codes_ext)).
-:- use_module(generics(db_ext)).
-:- use_module(os(file_ext)).
-:- use_module(os(run_ext)).
-:- use_module(os(safe_file)).
-:- use_module(svg(svg_file)).
-
-:- use_module(plGraphViz(gv_dot)).
-
-:- dynamic(user:file_type_program/2).
-:- multifile(user:file_type_program/2).
-
-:- dynamic(user:module_uses/2).
-:- multifile(user:module_uses/2).
-
-:- dynamic(user:prolog_file_type/2).
-:- multifile(user:prolog_file_type/2).
-
-% Register DOT.
-:- db_add_novel(user:prolog_file_type(dot, dot)).
-:- db_add_novel(user:prolog_file_type(dot, graphviz)).
-:- db_add_novel(user:file_type_program(dot, dotty)).
-:- db_add_novel(user:file_type_program(dot, dotx)).
-:- db_add_novel(user:module_uses(gv_file, file_type(dot))).
-
-% Register JPG/JPEG.
-:- db_add_novel(user:prolog_file_type(jpeg, jpeg)).
-:- db_add_novel(user:prolog_file_type(jpeg, graphviz_output)).
-:- db_add_novel(user:prolog_file_type(jpg, jpeg)).
-:- db_add_novel(user:prolog_file_type(jpg, graphviz_output)).
-
-% Register PDF.
-:- db_add_novel(user:prolog_file_type(pdf, pdf)).
-:- db_add_novel(user:prolog_file_type(pdf, graphviz_output)).
-
-% Register PNG.
-:- db_add_novel(user:prolog_file_type(png, png)).
-:- db_add_novel(user:prolog_file_type(png, graphviz_output)).
-
-% Register SVG.
-:- db_add_novel(user:prolog_file_type(svg, graphviz_output)).
-:- db_add_novel(user:prolog_file_type(svg, svg)).
-
-% Register XDOT.
-:- db_add_novel(user:prolog_file_type(xdot, graphviz_output)).
-:- db_add_novel(user:prolog_file_type(xdot, xdot)).
-
-:- predicate_options(codes_to_gv_file/3, 3, [
-     pass_to(file_to_gv/3, 3)
-   ]).
-:- predicate_options(file_to_gv/2, 2, [
-     pass_to(file_to_gv/3, 3)
-   ]).
-:- predicate_options(file_to_gv/3, 3, [
-     method(+oneof([dot,sfdp])),
-     to_file_type(+oneof([dot,jpeg,pdf,svg,xdot]))
-   ]).
-:- predicate_options(gif_to_svg_dom/3, 3, [
-     pass_to(gif_to_gv_file/3, 3)
-   ]).
-:- predicate_options(gif_to_gv_file/3, 3, [
-     pass_to(codes_to_gv_file/3, 3)
-   ]).
-
-
-
-%! codes_to_gv_file(
-%!   +Codes:list(code),
-%!   +ToFile:atom,
-%!   +Options:list(nvpair)
-%! ) is det.
-
-codes_to_gv_file(Codes, ToFile, Options):-
-  access_file(ToFile, write),
-  absolute_file_name(data(tmp), TmpFile, [access(write),file_type(dot)]),
-  setup_call_cleanup(
-    open(TmpFile, write, Write, [encoding(utf8)]),
-    put_codes(Write, Codes),
-    close(Write)
-  ),
-  file_to_gv(TmpFile, ToFile, Options),
-  delete_file(TmpFile).
-
-
-%! file_to_gv(+FromFile:atom, +Options:list(nvpair)) is det.
-
-file_to_gv(FromFile, Options):-
-  file_to_gv(FromFile, _, Options).
-
-%! file_to_gv(+FromFile:atom, ?ToFile:atom, +Options:list(nvpair)) is det.
-% Converts a GraphViz DOT file to an image file, using a specific
-% visualization method.
-
-file_to_gv(FromFile, ToFile, Options):-
-  option(to_file_type(dot), Options), !,
-  rename_file(FromFile, ToFile).
-file_to_gv(FromFile, ToFile, Options):-
-  % The method option.
-  option(method(Method), Options, dot),
-  must_be(oneof([dot,sfdp]), Method),
-
-  % The file type option.
-  option(to_file_type(ToFileType), Options, pdf),
-  prolog_file_type(ToExtension, ToFileType),
-  prolog_file_type(ToExtension, graphviz_output), !,
-
-  % The output file is either given or created.
-  (
-    var(ToFile)
-  ->
-    user:prolog_file_type(ToExtension, ToFileType),
-    file_alternative(FromFile, _, _, ToExtension, ToFile)
-  ;
-    is_absolute_file_name(ToFile),
-    % The given output file must match a certain file extension.
-    file_name_extension(_, ToExtension, ToFile)
-  ),
-  % Now that we have the output file we can prevent the
-  % file type / file extension translation predicates from bakctracking.
-  !,
-
-  % Run the GraphViz conversion command in the shell.
-  format(atom(OutputType), '-T~w', [ToExtension]),
-  process_create(
-    path(Method),
-    [OutputType,FromFile,'-o',ToFile],
-    [process(PID)]
-  ),
-  process_wait(PID, exit(ShellStatus)),
-  exit_code_handler('GraphViz', ShellStatus).
-
-
-%! gif_to_gv_file(+Gif:compound, +ToFile:atom, +Options:list(nvpair)) is det.
-% Returns a file containing a GraphViz visualization of the given graph.
-%
-% The following options are supported:
-%   * =|method(+Method:oneof([dot,sfdp])|=
-%     The algorithm used by GraphViz for positioning the tree nodes.
-%     Either =dot= (default) or =sfdp=.
-%   * =|to_file_type(+FileType:oneof([dot,jpeg,pdf,svg,xdot])|=
-%     The file type of the generated GraphViz file.
-%     Default: `pdf`.
-
-gif_to_gv_file(Gif, ToFile, Options):-
-  once(phrase(gv_graph(Gif), Codes)),
-  codes_to_gv_file(Codes, ToFile, Options).
-
-
-%! gif_to_svg_dom(
-%!   +GraphInterchangeFormat:compound,
-%!   -SvgDom:list(compound),
-%!   +Options:list(nvpair)
-%! ) is det.
-
-gif_to_svg_dom(Gif, SvgDom, Options1):-
-  % Make sure the file type of the output file is SvgDom.
-  merge_options([to_file_type=svg], Options1, Options2),
-  gif_to_gv_file(Gif, ToFile, Options2),
-  file_to_svg(ToFile, SvgDom),
-  safe_delete_file(ToFile).
-
-
-%! open_dot(+File:atom) is det.
-% Opens the given DOT file.
-%
-% @tbd Test support on Windows.
-% @tbd Test support on OS-X.
-
-open_dot(File):-
-  once(find_program_by_file_type(dot, Program)),
-  run_program(Program, [File]).
-
diff --git a/gv_gif.pl b/gv_gif.pl
deleted file mode 100644
index d59e5f8..0000000
--- a/gv_gif.pl
+++ /dev/null
@@ -1,221 +0,0 @@
-:- module(
-  gv_gif,
-  [
-    create_gif/3, % +Edges:ordset
-                  % -Gif:compound
-                  % +Options:list(nvpair)
-    create_gif/4 % +Vertices:ordset
-                 % +Edges:ordset
-                 % -Gif:compound
-                 % +Options:list(nvpair)
-  ]
-).
-
-/** <module> GraphViz Graph Interchange Format (GIF)
-
-Support for creating GIF representations.
-
-@author Wouter Beek
-@version 2014/06-2014/07
-*/
-
-:- use_module(library(apply)).
-:- use_module(library(lambda)).
-:- use_module(library(option)).
-:- use_module(library(ordsets)).
-:- use_module(library(predicate_options)). % Declarations.
-
-:- use_module(generics(list_ext)).
-:- use_module(generics(option_ext)).
-:- use_module(graph_theory(graph_theory)).
-
-:- use_module(plRdf(rdf_name)). % Meta-DCG.
-
-:- predicate_options(create_gif/3, 3, [
-     pass_to(create_gif/4, 4)
-   ]).
-:- predicate_options(create_gif/4, 4, [
-     pass_to(edge_term/3, 3),
-     pass_to(graph_attributes/2, 2),
-     pass_to(vertex_term/3, 3)
-   ]).
-:- predicate_options(edge_term/3, 3, [
-     edge_arrowhead(+atom),
-     edge_color(+atom),
-     edge_label(+atom),
-     edge_style(+atom)
-   ]).
-:- predicate_options(graph_attributes/2, 2, [
-     directedness(+boolean),
-     graph_colorscheme(+oneof([none,svg,x11])),
-     graph_label(+atom)
-   ]).
-
-:- predicate_options(vertex_term/3, 3, [
-     vertex_color(+atom),
-     vertex_coordinates(+atom),
-     vertex_image(+atom),
-     vertex_label(+atom),
-     vertex_peripheries(+atom),
-     vertex_shape(+atom)
-   ]).
-
-
-
-%! create_gif(+Edges:ordset, -Gif:compound, +Options:list(nvpair)) is det.
-
-create_gif(Es, Gif, Options):-
-  graph_theory:edges_to_vertices(Es, Vs),
-  create_gif(Vs, Es, Gif, Options).
-
-%! create_gif(
-%!   +Vertices:ordset,
-%!   +Edges:ordset,
-%!   -Gif:compound,
-%!   +Options:list(nvpair)
-%! ) is det.
-% The following options are supported:
-%   * =|graph_colorscheme(+oneof([none,svg,x11]))
-%     No default.
-%   * =|graph_label(+LabelFunction)|=
-%     The functions that assigns names to graphs.
-%     No default.
-
-create_gif(Vs, Es, graph(VTerms,ETerms,GAttrs), Options):-
-  % Vertex terms.
-  maplist(\V^VTerm^vertex_term(Vs, V, VTerm, Options), Vs, VTerms),
-
-  % Edge terms.
-  maplist(\E^ETerm^edge_term(Vs, E, ETerm, Options), Es, ETerms),
-
-  % Graph attributes.
-  graph_attributes(GAttrs, Options).
-
-
-%! edge_term(
-%!   +Vertices:ordset,
-%!   +Edge:pair,
-%!   -EdgeTerm:compound,
-%!   +Options:list(nvpair)
-%! ) is det.
-% The following options are supported:
-%   * =|edge_arrowhead(+atom)|=
-%   * =|edge_color(+atom)|=
-%   * =|edge_label(+atom)|=
-%   * =|edge_style(+atom)|=
-
-edge_term(Vs, E, edge(FromId,ToId,EAttrs), Options):-
-  graph_theory:edge_components(E, FromV, ToV),
-  nth0chk(FromId, Vs, FromV),
-  nth0chk(ToId, Vs, ToV),
-
-  % Arrowhead
-  if_option(edge_arrowhead(ArrowheadFunction), Options,
-    call(ArrowheadFunction, E, EArrowhead)
-  ),
-  % Color.
-  if_option(edge_color(ColorFunction), Options,
-    call(ColorFunction, E, EColor)
-  ),
-  % Label.
-  if_option(edge_label(LabelFunction), Options,
-    call(LabelFunction, E, ELabel)
-  ),
-  % Style.
-  if_option(edge_style(StyleFunction), Options,
-    call(StyleFunction, E, EStyle)
-  ),
-
-  merge_options(
-    [arrowhead=EArrowhead,color=EColor,label=ELabel,style=EStyle],
-    EAttrs
-  ).
-
-
-%! graph_attributes(
-%!   -GraphAttributes:list(nvpair),
-%!   +Options:list(nvpair)
-%! ) is det.
-% The following options are supported:
-%   * =|directedness(+boolean)|=
-%     Whether the graph is directed (`true`) or undirected (`false`).
-%     Default: `false`.
-%   * =|graph_colorscheme(+oneof([none,svg,x11]))|=
-%     The colorscheme from which the color in this graph are taken.
-%     Default: `svg`.
-%   * =|graph_label(+atom)|=
-%     The graph label.
-%     No default.
-
-graph_attributes(GAttrs, Options):-
-  % Directedness.
-  option(directedness(Directedness), Options, false),
-  % Colorscheme.
-  if_option(graph_colorscheme(Colorscheme), Options, true),
-  % Label.
-  if_option(graph_label(GLabel), Options, true),
-
-  merge_options(
-    [colorscheme=Colorscheme,directedness=Directedness,label=GLabel],
-    GAttrs
-  ).
-
-
-%! vertex_term(
-%!   +Vertices:ordset,
-%!   +Vertex,
-%!   -VertexTerm:compound,
-%!   +Options:list(nvpair)
-%! ) is det.
-% The following options are supported:
-%   * =|vertex_color(+ColorFunction)|=
-%     A function that assigns colors to vertices.
-%     No default.
-%   * =|vertex_image(+ImageFunction)|=
-%     A function that assinges images to vertices.
-%     No default.
-%   * =|vertex_label(+LabelFunction)|=
-%     A function that assigns labels to vertices.
-%     No default.
-%   * =|vertex_peripheries(+PeripheriesFunction)|=
-%     A function that assinges peripheries to vertices.
-%     No default.
-%   * =|vertex_shape(+ShapeFunction)|=
-%     A function that assinges shapes to vertices.
-%     No default.
-
-vertex_term(Vs, V, vertex(Id,V,VAttrs), Options):-
-  nth0chk(Id, Vs, V),
-
-  % Color.
-  if_option(vertex_color(ColorFunction), Options,
-    call(ColorFunction, V, VColor)
-  ),
-  % Image.
-  if_option(image(ImageFunction), Options,
-    call(ImageFunction, V, VImage)
-  ),
-  % Label.
-  if_option(vertex_label(LabelFunction), Options,
-    call(LabelFunction, V, VLabel)
-  ),
-  % Peripheries.
-  if_option(vertex_peripheries(PeripheriesFunction), Options,
-    call(PeripheriesFunction, V, VPeripheries)
-  ),
-  % Shape.
-  if_option(vertex_shape(ShapeFunction), Options,
-    call(ShapeFunction, V, VShape)
-  ),
-
-  merge_options(
-    [
-      color=VColor,
-      image=VImage,
-      label=VLabel,
-      peripheries=VPeripheries,
-      shape=VShape
-    ],
-    VAttrs
-  ).
-
diff --git a/gv_html.pl b/gv_html.pl
deleted file mode 100644
index b8d3190..0000000
--- a/gv_html.pl
+++ /dev/null
@@ -1,60 +0,0 @@
-:- module(
-  gv_html,
-  [
-    gv_html_like_label//1 % +Codes:list(code)
-  ]
-).
-
-/** <module> GraphViz HTML
-
-@author Wouter Beek
-@version 2013/07, 2013/09, 2014/03-2014/06
-*/
-
-:- use_module(dcg(dcg_content)).
-
-:- use_module(plHtml(html_dcg)).
-
-
-
-%! gv_html_label(+Codes:list(code))// .
-%
-% @see http://www.graphviz.org/doc/info/shapes.html#html
-
-gv_html_label --> gv_html_text, !.
-gv_html_label --> gv_html_table, !.
-gv_html_label --> [].
-
-gv_html_like_label --> bracketed(angular, gv_html_label).
-
-gv_html_like_label(Content) --> bracketed(angular, html_dcg(Content)).
-
-gv_html_table --> html_element(table, _, gv_html_rows).
-gv_html_table --> html_element(font, _, html_element(table, _, gv_html_rows)).
-
-gv_html_rows --> gv_html_row, gv_html_rows.
-gv_html_rows --> gv_html_row, html_element(hr, _), gv_html_rows.
-gv_html_rows --> gv_html_row.
-
-gv_html_row --> html_element(tr, _, gv_html_cells).
-
-gv_html_cell --> html_element(td, _, gv_html_label).
-gv_html_cell --> html_element(td, _, html_element(img, _)).
-
-gv_html_cells --> gv_html_cell, gv_html_cells.
-gv_html_cells --> gv_html_cell.
-gv_html_cells --> gv_html_cell, html_element(vr, _), gv_html_cells.
-
-gv_html_text --> gv_html_textitem, gv_html_text.
-gv_html_text --> gv_html_textitem.
-
-gv_html_textitem --> html_string, !.
-gv_html_textitem --> html_entity, !.
-gv_html_textitem --> html_element(br, _), !.
-gv_html_textitem --> html_element(font, _, gv_html_text), !.
-gv_html_textitem --> html_element(i, _, gv_html_text), !.
-gv_html_textitem --> html_element(b, _, gv_html_text), !.
-gv_html_textitem --> html_element(u, _, gv_html_text), !.
-gv_html_textitem --> html_element(sub, _, gv_html_text), !.
-gv_html_textitem --> html_element(sup, _, gv_html_text), !.
-
diff --git a/gv_numeral.pl b/gv_numeral.pl
deleted file mode 100644
index 9b82fc0..0000000
--- a/gv_numeral.pl
+++ /dev/null
@@ -1,71 +0,0 @@
-:- module(
-  gv_numeral,
-  [
-    gv_numeral//1 % ?Value:number
-  ]
-).
-
-/** <module> GraphViz numeral
-
-@author Wouter Beek
-@version 2014/05-2014/06
-*/
-
-:- use_module(dcg(dcg_abnf)).
-:- use_module(dcg(dcg_cardinal)).
-:- use_module(math(math_ext)).
-
-
-
-%! gv_numeral(?Value:number)// .
-% ~~~{.bnf}
-% ('-')? ( '.' [0-9]+ | [0-9]+ ( '.' [0-9]* )? )
-% ~~~
-
-gv_numeral(N) -->
-  {nonvar(N)},
-  {number_sign_parts(N, Sign, Abs)},
-  ({Sign =:= -1} -> `-` ; ``),
-  gv_numeral_abs(Abs).
-gv_numeral(N) -->
-  {var(N)},
-  'sign?'(Sign),
-  gv_numeral_abs(Abs),
-  {number_sign_parts(N, Sign, Abs)}.
-
-
-gv_numeral_abs(N) -->
-  {nonvar(N)},
-  {number_integer_parts(N, N1, N2)},
-  (
-    {N2 =:= 0}
-  ->
-    integer(N1)
-  ;
-    {N1 =:= 0}
-  ->
-    `.`,
-    integer(N2)
-  ;
-    integer(N1),
-    '?'((`.`, 'integer?'(N2)))
-  ).
-gv_numeral_abs(N) -->
-  {var(N)},
-  (
-    `.`,
-    integer(N2)
-  ->
-    {N1 = 0}
-  ;
-    integer(N1),
-    (
-      `.`
-    ->
-      'integer?'(N2)
-    ;
-      {N2 = 0}
-    )
-  ),
-  {number_integer_parts(N, N1, N2)}.
-
diff --git a/gv_tree.pl b/gv_tree.pl
deleted file mode 100644
index afeb8f6..0000000
--- a/gv_tree.pl
+++ /dev/null
@@ -1,43 +0,0 @@
-:- module(
-  gv_tree,
-  [
-    tree_to_gv_file/3 % +Tree:compound
-                      % ?ToFile:atom
-                      % +Options:list(nvpair)
-  ]
-).
-
-/** <module> GraphViz tree
-
-Export trees to GraphViz.
-
-@author Wouter Beek
-@version 2014/06-2014/07
-*/
-
-:- use_module(library(aggregate)).
-
-:- use_module(generics(trees)).
-
-:- use_module(plGraphViz(gv_file)).
-:- use_module(plGraphViz(gv_gif)).
-
-
-%! tree_to_gv_file(
-%!   +Tree:compound,
-%!   ?ToFile:atom,
-%!   +Options:list(nvpair)
-%! ) is det.
-% Stores the given tree term into a GraphViz file.
-%
-% Options are passed on to create_gif/3 and gif_to_gv_file/3.
-
-tree_to_gv_file(Tree, ToFile, Options):-
-  tree_to_gif(Tree, Gif, Options),
-  gif_to_gv_file(Gif, ToFile, Options).
-
-
-tree_to_gif(H-T, Gif, Options):-
-  tree_to_vertices_edges(Tree, Vs, Es),
-  create_gif(Vs, Es, Gif, Options).
-
diff --git a/index.pl b/index.pl
deleted file mode 100644
index 7461d13..0000000
--- a/index.pl
+++ /dev/null
@@ -1,2 +0,0 @@
-% Index of project plGraphViz (empty).
-
diff --git a/load.pl b/load.pl
deleted file mode 100644
index 4b117d1..0000000
--- a/load.pl
+++ /dev/null
@@ -1,12 +0,0 @@
-% Load file for plGraphViz.
-
-:- dynamic(user:prolog/3).
-:- multifile(user:prolog/3).
-   user:project(plGraphViz, 'GraphViz support for SWI-Prolog.', plGraphViz).
-
-:- use_module(load_project).
-:- load_project(plGraphViz, [
-    plc-'Prolog-Library-Collection',
-    plHtml
-]).
-
diff --git a/load_project.pl b/load_project.pl
deleted file mode 100644
index 5599b5e..0000000
--- a/load_project.pl
+++ /dev/null
@@ -1,120 +0,0 @@
-:- module(
-  load_project,
-  [
-    load_project/2, % +Parent:atom
-                    % +ChildProjects:list(or([atom,pair(atom)]))
-    load_subproject/2, % +ParentFileSearchPath:atom
-                       % +Child:or([atom,pair(atom)])
-    set_data_subdirectory/1 % +ParentDirectory:atom
-  ]
-).
-
-/** <module> Load project
-
-Generic code for loading a project:
-  * Create a subdirectory for data.
-  * Load the root of subprojects onto the file search path.
-  * Load the index of subprojects onto the file search path.
-
-@author Wouter Beek
-@version 2014/06/14
-*/
-
-:- use_module(library(ansi_term)). % Colorized terminal messages.
-:- use_module(library(apply)).
-
-:- dynamic(user:project/2).
-:- multifile(user:project/2).
-:- dynamic(user:project/3).
-:- multifile(user:project/3).
-
-
-
-load_project(Parent, ChildProjects):-
-  parent_alias(Parent, ParentFsp),
-
-  % Entry point.
-  source_file(load_project(_,_), ThisFile),
-  file_directory_name(ThisFile, ThisDir),
-  assert(user:file_search_path(ParentFsp, ThisDir)),
-  assert(user:file_search_path(project, ThisDir)),
-
-  % Set the data subdirectory.
-  set_data_subdirectory(ThisDir),
-
-  % Load the root of submodules onto the file search path.
-  maplist(load_subproject(ParentFsp), ChildProjects),
-
-  % Load the index into the file search path.
-  load_project_index(ParentFsp).
-
-
-
-%! load_subproject(
-%!   +ParentFileSearchPath:atom,
-%!   +Child:or([atom,pair(atom)])
-%! ) is det.
-
-load_subproject(ParentFsp, ChildFsp-ChildDir):- !,
-  load_subproject_file_search_path(ParentFsp, ChildFsp, ChildDir),
-  load_project_index(ChildFsp).
-load_subproject(ParentFsp, Child):-
-  load_subproject(ParentFsp, Child-Child).
-
-
-%! load_subproject_file_search_path(
-%!   +ParentFileSearchPath:atom,
-%!   +ChildFileSearchPath:atom,
-%!   +ChildDirectory:atom
-%! ) is det.
-
-% The file search path for the subproject has already been set.
-load_subproject_file_search_path(_, ChildFsp, _):-
-  user:file_search_path(ChildFsp, _).
-load_subproject_file_search_path(ParentFsp, ChildFsp, ChildDir):-
-  Spec =.. [ParentFsp,ChildDir],
-  absolute_file_name(Spec, _, [access(read),file_type(directory)]), !,
-  assert(user:file_search_path(ChildFsp, Spec)).
-load_subproject_file_search_path(_, ChildFsp, ChildDir):-
-  print_message(warning, missing_subproject_directory(ChildFsp,ChildDir)).
-
-
-%! load_project_index(+FileSearchPath:atom) is det.
-
-load_project_index(Fsp):-
-  Spec =.. [Fsp,index],
-  absolute_file_name(
-    Spec,
-    File,
-    [access(read),file_errors(fail),file_type(prolog)]
-  ), !,
-  ensure_loaded(File).
-load_project_index(_).
-
-
-%! parent_alias(+Parent:atom, -ParentFsp:atom) is det.
-
-parent_alias(Parent, ParentFsp):-
-  user:project(Parent, _, ParentFsp), !.
-parent_alias(Parent, Parent).
-
-
-%! set_data_subdirectory(+ParentDirectory:atom) is det.
-
-set_data_subdirectory(ParentDir):-
-  directory_file_path(ParentDir, data, DataDir),
-  make_directory_path(DataDir),
-  assert(user:file_search_path(data, DataDir)).
-
-
-
-:- multifile(prolog:message//1).
-
-prolog:message(missing_subproject_directory(ChildFsp,ChildDir)) -->
-  [
-    'The ~a submodule is not present.'-[ChildFsp], nl,
-    'Check whether subdirectory ~a is present in your project directory:'-[ChildDir], nl,
-    '    git submodule init', nl,
-    '    git submodule update'
-  ].
-
diff --git a/pack.pl b/pack.pl
new file mode 100644
index 0000000..989108a
--- /dev/null
+++ b/pack.pl
@@ -0,0 +1,9 @@
+author('Wouter Beek', 'me@wouterbeek.com').
+download('https://github.com/wouterbeek/plGraphViz/release/*.zip').
+home('https://github.com/wouterbeek/plGraphViz').
+maintainer('Wouter Beek', 'me@wouterbeek.com').
+name(plGraphViz).
+packager('Wouter Beek', 'me@wouterbeek.com').
+requires('Prolog-Library-Collection').
+title(plGraphViz).
+version('0.0.1').
diff --git a/plHtml b/plHtml
deleted file mode 160000
index 08480c4..0000000
--- a/plHtml
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 08480c40acaee5f70bfb21e3c641bef1e7d258bd
diff --git a/prolog/fca/fca_viz.pl b/prolog/fca/fca_viz.pl
new file mode 100644
index 0000000..083dbb2
--- /dev/null
+++ b/prolog/fca/fca_viz.pl
@@ -0,0 +1,101 @@
+:- module(
+  fca_viz,
+  [
+    fca_export_graph/2, % +Context, -ExportGraph
+    fca_export_graph/3, % +Context, -ExportGraph, :Opts
+    fca_viz/2,          % +Context, ?File
+    fca_viz/3           % +Context, ?File, :Opts
+  ]
+).
+
+/** <module> FCA visualization
+
+@author Wouter Beek
+@version 2015/11-2016/01
+*/
+
+:- use_module(library(aggregate)).
+:- use_module(library(dcg/dcg_ext)).
+:- use_module(library(dcg/dcg_pl)).
+:- use_module(library(fca/fca)).
+:- use_module(library(graph/build_export_graph)).
+:- use_module(library(graph/s/s_graph)).
+:- use_module(library(gv/gv_file)).
+:- use_module(library(option)).
+:- use_module(library(ordsets)).
+
+:- meta_predicate(fca_export_graph(+,?,:)).
+:- meta_predicate(fca_viz(+,?,:)).
+
+:- predicate_options(fca_export_graph/3, 3, [
+     concept_label(+callable)
+   ]).
+:- predicate_options(fca_viz/3, 3, [
+     pass_to(fca_export_graph/3, 3),
+     pass_to(graph_viz/3, 3)
+   ]).
+
+is_meta(concept_label).
+
+
+
+
+
+%! fca_export_graph(+Context:compound, -ExportGraph:compound) is det.
+% Wrapper around fca_export_graph/3 with default options.
+
+fca_export_graph(Context, ExportG):-
+  fca_export_graph(Context, ExportG, []).
+
+
+%! fca_export_graph(
+%!   +Context:compound,
+%!   -ExportGraph:compound,
+%!   :Options:list(compound)
+%! ) is det.
+% The following optios are supported:
+%   * concept_label(+callable)
+%     DCG writing the labels for individual concepts.
+
+fca_export_graph(Context, ExportG, Opts1):-
+  fca_hasse(Context, Hasse),
+  meta_options(is_meta, Opts1, Opts2),
+  option(concept_label(Label_3), Opts2, concept_label),
+  merge_options(
+    [
+      vertex_label(Label_3),
+      vertex_rank(fca:concept_cardinality)
+    ],
+    Opts2,
+    Opts3
+  ),
+  build_export_graph(Hasse, ExportG, Opts3).
+
+
+
+%! fca_viz(+Context:compound, ?File:atom) is det.
+% Wrapper around fca_viz/3 with default options.
+
+fca_viz(Context, File):-
+  fca_viz(Context, File, []).
+
+
+%! fca_viz(+Context:compound, ?File:atom, :Options:list(compound)) is det.
+
+fca_viz(Context, File, Opts1):-
+  meta_options(is_meta, Opts1, Opts2),
+  statistics(process_cputime, Time1),
+  fca_export_graph(Context, ExportG, Opts2),
+  ExportG = graph(_,_,Es,_),
+  aggregate_all(max(N), (member(edge(V1,V2,_), Es), (N = V1 ; N = V2)), N),
+  ignore(option(number_of_vertices(N), Opts2)),
+  statistics(process_cputime, Time2),
+  Time is Time2 - Time1,
+  ignore(option(process_cputime(Time), Opts2)),
+  graph_viz(ExportG, File, Opts2).
+
+
+
+%! concept_label(+Concept:compound)// is det.
+
+concept_label(concept(Os,As)) --> set(Os), " / ", set(As).
diff --git a/prolog/graph/build_export_graph.pl b/prolog/graph/build_export_graph.pl
new file mode 100644
index 0000000..5134069
--- /dev/null
+++ b/prolog/graph/build_export_graph.pl
@@ -0,0 +1,369 @@
+:- module(
+  build_export_graph,
+  [
+    build_export_graph/2, % +Graph, -ExportGraph
+    build_export_graph/3 % +Graph
+                         % -ExportGraph:compound
+                         % +Options:list(compound)
+  ]
+).
+
+/** <module> Build graph representation for exporting
+
+Support for building GIF representations.
+
+# Graph Intermediate Format (GIF)
+
+## Graph
+
+```prolog
+graph(Vs:ordset(compound),Ranks,Es:compound,Attributes:list(compound))
+```
+
+### Edge
+
+```prolog
+edge(FromVertexId,ToVertexId,Attributes:list(compound))
+```
+
+### Rank
+
+```prolog
+RankVertex:compound-ContentVertices:ordset(compound)
+```
+
+### Vertex
+
+```prolog
+vertex(Id,Attributes:list(compound))
+```
+
+# Property functions
+
+Edge label:
+  1. [[graph_edge]] edge_label/2
+
+Vertex coordinates:
+  1. [[circle_coords]] circular_coord/4
+  2. [[random_coords]] random_coord/4
+
+---
+
+@author Wouter Beek
+@version 2015/07, 2015/09-2016/01
+*/
+
+:- use_module(library(apply)).
+:- use_module(library(dcg/dcg_ext)).
+:- use_module(library(graph/s/s_graph)).
+:- use_module(library(list_ext)).
+:- use_module(library(option_ext)).
+:- use_module(library(ordsets)).
+:- use_module(library(pairs)).
+
+:- predicate_options(build_export_graph/4, 4, [
+     pass_to(edge_term/3, 3),
+     pass_to(graph_attributes/2, 2),
+     pass_to(vertex_term/3, 3)
+   ]).
+:- predicate_options(edge_term/3, 3, [
+     edge_arrowhead(+callable),
+     edge_color(+callable),
+     edge_id(+callable),
+     edge_label(+callable),
+     edge_penwidth(+callable),
+     edge_style(+callable)
+   ]).
+:- predicate_options(graph_attributes/2, 2, [
+     graph_charset(+oneof(['iso-8859-1','Latin1','UTF-8'])),
+     graph_colorscheme(+oneof([none,svg,x11])),
+     graph_directed(+boolean),
+     graph_fontsize(+float),
+     graph_label(+atom),
+     graph_overlap(+boolean)
+   ]).
+:- predicate_options(vertex_term/3, 3, [
+     vertex_color(+callable),
+     vertex_id(+callable),
+     vertex_image(+callable),
+     vertex_label(+callable),
+     vertex_peripheries(+callable),
+     vertex_position(+callable),
+     vertex_rank(+callable),
+     vertex_shape(+callable),
+     vertex_uri(+callable)
+   ]).
+
+:- meta_predicate(build_export_graph(+,-,:)).
+
+is_meta(edge_arrowhead).
+is_meta(edge_color).
+is_meta(edge_id).
+is_meta(edge_label).
+is_meta(edge_penwidth).
+is_meta(edge_style).
+is_meta(vertex_color).
+is_meta(vertex_id).
+is_meta(vertex_image).
+is_meta(vertex_label).
+is_meta(vertex_peripheries).
+is_meta(vertex_position).
+is_meta(vertex_rank).
+is_meta(vertex_shape).
+is_meta(vertex_uri).
+
+
+
+
+
+%! build_export_graph(+Graph, -ExportGraph:compound) is det.
+% Wrapper around build_export_graph/3 with default options.
+
+build_export_graph(G, ExportG):-
+  build_export_graph(G, ExportG, []).
+
+
+%! build_export_graph(
+%!   +Graph,
+%!   -ExportGraph:compound,
+%!   +Options:list(compound)
+%! ) is det.
+% Graph is either:
+%   * a coumpound term `graph(Vs,Es)`, or
+%   * an unlabeled graph as defined by `library(ugraph)`.
+%
+% The following options are supported:
+%   * `vertex_rank(:RankFunction)`
+%     Assigns a non-negative integer to each vertex.
+%     No default.
+
+build_export_graph(G, graph(VTerms2,VRanks,ETerms,GAttrs), Opts1):-
+  graph_components(G, Vs, Es),
+  meta_options(is_meta, Opts1, Opts2),
+  maplist(vertex_term0(Vs, Opts2), Vs, VTerms1),
+  build_export_ranks(Vs, VTerms1, VRanks, VTerms2, Opts2),
+  maplist(edge_term0(Vs, Opts2), Es, ETerms),
+  graph_attributes(GAttrs, Opts2).
+
+vertex_term0(Vs, Opts, V, VTerm) :- vertex_term(Vs, V, VTerm, Opts).
+
+edge_term0(Vs, Opts, E, ETerm) :- edge_term(Vs, E, ETerm, Opts).
+
+graph_components(graph(Vs,Es), Vs, Es):- !.
+graph_components(G, Vs, Es):-
+  s_graph_components(G, Vs, Es).
+
+build_export_ranks(Vs, VTerms, VRanks, [], Opts):-
+  option(vertex_rank(VRank_2), Opts), !,
+  maplist(VRank_2, Vs, Ranks),
+  pairs_keys_values(Pairs, Ranks, VTerms),
+  group_pairs_by_key(Pairs, GroupedPairs),
+  build_export_rank_terms(GroupedPairs, VRanks).
+build_export_ranks(_, VTerms, [], VTerms, _).
+
+build_export_rank_terms([N-VTerms|T1], [RankTerm-VTerms|T2]):- !,
+  build_export_rank_term(N, RankTerm),
+  build_export_rank_terms(T1, T2).
+build_export_rank_terms([], []).
+
+build_export_rank_term(N, vertex(Id,[label(""),shape(none)])):-
+  format(atom(Id), "r~d", [N]).
+
+
+
+%! edge_term(
+%!   +Vertices:ordset(compound),
+%!   +Edge:compound,
+%!   -EdgeTerm:compound,
+%!   +Options:list(compound)
+%! ) is det.
+% The following options are supported:
+%   * `edge_arrowhead(+callable)`
+%     No default.
+%   * `edge_color(+callable)`
+%     No default.
+%   * `edge_id(+callable)`
+%     Function that assignes the unique identifiers for an edge's
+%     incident vertices.
+%   * `edge_label(+callable)`
+%     No default.
+%   * `edge_penwidth(+callable)`
+%     No default.
+%   * `edge_style(+callable)`
+%     No default.
+
+edge_term(Vs, E, edge(FromId,ToId,EAttrs), Opts):-
+  % Arrowhead
+  if_option(edge_arrowhead(Arrowhead_2), Opts,
+    call(Arrowhead_2, E, EArrowhead)
+  ),
+  
+  % Color.
+  if_option(edge_color(ColorFunction), Opts, call(ColorFunction, E, EColor)),
+  
+  % Id.
+  (   option(edge_id(Id_2), Opts)
+  ->  call(Id_2, E, FromId, ToId)
+  ;   edge_components(E, FromV, ToV),
+      nth0chk(FromId, Vs, FromV),
+      nth0chk(ToId, Vs, ToV)
+  ),
+  
+  % Label.
+  if_option(edge_label(ELabel_3), Opts, string_phrase(dcg_call(ELabel_3, E), ELabel)),
+  
+  % Penwidth.
+  if_option(edge_penwidth(Penwidth_2), Opts, call(Penwidth_2, E, EPenwidth)),
+  
+  % Style.
+  if_option(edge_style(Style_2), Opts, call(Style_2, E, EStyle)),
+  
+  exclude(
+    option_has_var_value,
+    [
+      arrowhead(EArrowhead),
+      color(EColor),
+      label(ELabel),
+      penwidth(EPenwidth),
+      style(EStyle)
+    ],
+    EAttrs
+  ).
+
+
+
+%! graph_attributes(
+%!   -GraphAttributes:list(compound),
+%!   +Options:list(compound)
+%! ) is det.
+% The following options are supported:
+%   * `graph_charset(+oneof(['iso-8859-1','Latin1','UTF-8']))`
+%     The name of the character set that is used to encode text in the graph.
+%     Default: `UTF-8`.
+%   * `graph_colorscheme(+oneof([none,svg,x11]))`
+%     The colorscheme from which the color in this graph are taken.
+%     Default: `x11`.
+%   * `graph_directed(+boolean)`
+%     Whether the graph is directed (`true`) or undirected (`false`).
+%     Default: `false`.
+%   * `graph_fontsize(+float)`
+%     The font size of text in the graph.
+%     Default: `11.0`.
+%   * `graph_label(+atom)`
+%     The graph label.
+%     No default.
+%   * `graph_overlap(+boolean)`
+%     Whether the vertices are allowed to overlap.
+%     Default: `false`.
+
+graph_attributes(GAttrs, Opts):-
+  % Characer set.
+  option(graph_charset(Charset), Opts, 'UTF-8'),
+  % Colorscheme.
+  option(graph_colorscheme(Colorscheme), Opts, x11),
+  % Directed.
+  option(graph_directed(Directed), Opts, false),
+  % Fontsize.
+  option(graph_fontsize(Fontsize), Opts, 11.0),
+  % Label.
+  % Defaults to the empty string.
+  option(graph_label(GLabel), Opts, '""'),
+  % Overlap.
+  option(graph_overlap(Overlap), Opts, false),
+  exclude(
+    option_has_var_value,
+    [
+      charset(Charset),
+      colorscheme(Colorscheme),
+      directed(Directed),
+      fontsize(Fontsize),
+      label(GLabel),
+      overlap(Overlap)
+    ],
+    GAttrs
+  ).
+
+
+
+%! vertex_term(
+%!   +Vertices:ordset(compound),
+%!   +Vertex:compound,
+%!   -VertexTerm:compound,
+%!   +Options:list(compound)
+%! ) is det.
+% The following options are supported:
+%   * `vertex_color(:ColorFunction)`
+%     A function that assigns colors to vertices.
+%     No default.
+%   * `vertex_id(:ColorFunction)`
+%     A functions that assigns unique identifiers to vertices.
+%   * `vertex_image(:ImageFunction)`
+%     A function that assigns images to vertices.
+%     No default.
+%   * `vertex_label(:LabelFunction)`
+%     A function that assigns labels to vertices.
+%     No default.
+%   * `vertex_peripheries(:PeripheriesFunction)`
+%     A function that assinges peripheries to vertices.
+%     No default.
+%   * `vertex_position(:PositionFunction)`
+%     No default.
+%   * `vertex_shape(:ShapeFunction)`
+%     A function that assinges shapes to vertices.
+%     No default.
+%   * `vertex_uri(:UriFunction)`
+
+vertex_term(Vs, V, vertex(VId,VAttrs), Opts):-
+  % Color.
+  if_option(vertex_color(Color_2), Opts, call(Color_2, V, VColor)),
+  
+  % Id.
+  (option(vertex_id(Id_2), Opts) -> call(Id_2, V, VId) ; nth0chk(VId, Vs, V)),
+  
+  % Image.
+  ignore(if_option(vertex_image(Image_2), Opts, call(Image_2, V, VImage))),
+
+  % Label.
+  if_option(vertex_label(VLabel_2), Opts, string_phrase(dcg_call(VLabel_2, V), VLabel)),
+
+  % Peripheries.
+  if_option(vertex_peripheries(Peripheries_2), Opts,
+    call(Peripheries_2, V, VPeripheries)
+  ),
+
+  % Position.
+  if_option(vertex_position(Position_4), Opts,
+    call(Position_4, Vs, Opts, V, VPosition)
+  ),
+
+  % Shape.
+  if_option(vertex_shape(Shape_2), Opts, call(Shape_2, V, VShape)),
+  
+  % URI
+  if_option(vertex_uri(Uri_2), Opts, call(Uri_2, V, VUri)),
+
+  exclude(
+    option_has_var_value,
+    [
+      color(VColor),
+      image(VImage),
+      label(VLabel),
+      peripheries(VPeripheries),
+      pos(VPosition),
+      shape(VShape),
+      'URL'(VUri)
+    ],
+    VAttrs
+  ).
+
+
+
+
+
+% HELPERS %
+
+%! edge_components(+Edge:compound, -FromV, -ToV) is det.
+
+edge_components(edge(FromV,_,ToV), FromV, ToV):- !.
+edge_components(edge(FromV,ToV), FromV, ToV):- !.
+edge_components(FromV-ToV, FromV, ToV):- !.
diff --git a/prolog/gv/gv_attr_type.pl b/prolog/gv/gv_attr_type.pl
new file mode 100644
index 0000000..f55fce5
--- /dev/null
+++ b/prolog/gv/gv_attr_type.pl
@@ -0,0 +1,421 @@
+:- module(
+  gv_attr_type,
+  [
+    gv_attr_type//1, % ?Type:atom
+    addDouble//1, % +Double:float
+    addPoint//1, % +Point:compound
+    arrowType//1, % +ArrowType:atom
+    bool//1, % +Boolean:boolean
+    clusterMode//1, % +ClusterMode:atom
+    dirType//1, % +DirectionType:oneof([back,both,forward,none])
+    double//1, % +Double:float
+    doubleList//1, % +Doubles:list(float)
+    escString//1,
+    %layerList//1,
+    %layerRange//1,
+    lblString//1,
+    int//1, % +Integer:integer
+    outputMode//1, % +OutputMode:atom
+    %packMode//1,
+    pagedir//1, % +Pagedir:atom
+    point//1, % +Point:compound
+    pointList//1, % +Points:list(compound)
+    %portPos//1,
+    quadType//1, % +QuadType:atom
+    rankType//1, % +RankType:atom
+    rankdir//1, % +RankDirection:atom
+    rect//1, % +Rectangle:compound
+    shape//1,
+    smoothType//1, % +SmoothType:atom
+    %splineType//1,
+    %startType//1,
+    string//1, % ?Content:atom
+    style//2 % +Context:oneof([cluster,edge,node])
+             % +Style:atom
+    %viewPort//1
+  ]
+).
+:- reexport(
+  library(gv/gv_color),
+  [
+    color//1, % +Color:compound
+    colorList//1 % +ColorList:list(compound)
+  ]
+).
+
+/** <module> GraphViz attribute types
+
+@author Wouter Beek
+@version 2015/07, 2015/11, 2016/02
+*/
+
+:- use_module(library(dcg/dcg_ext), except([string//1])).
+:- use_module(library(gv/gv_html)).
+
+
+
+
+
+%! gv_attr_type(?Type:atom) is nondet.
+
+gv_attr_type(addDouble)   --> "addDouble".
+gv_attr_type(addPoint)    --> "addPoint".
+gv_attr_type(arrowType)   --> "arrowType".
+gv_attr_type(bool)        --> "bool".
+gv_attr_type(color)       --> "color".
+gv_attr_type(colorList)   --> "colorList".
+gv_attr_type(clusterMode) --> "clusterMode".
+gv_attr_type(dirType)     --> "dirType".
+gv_attr_type(double)      --> "double".
+gv_attr_type(doubleList)  --> "doubleList".
+gv_attr_type(escString)   --> "escString".
+gv_attr_type(layerList)   --> "layerList".
+gv_attr_type(layerRange)  --> "layerRange".
+gv_attr_type(lblString)   --> "lblString".
+gv_attr_type(int)         --> "int".
+gv_attr_type(outputMode)  --> "outputMode".
+gv_attr_type(packMode)    --> "packMode".
+gv_attr_type(pagedir)     --> "pagedir".
+gv_attr_type(point)       --> "point".
+gv_attr_type(pointList)   --> "pointList".
+gv_attr_type(portPos)     --> "portPos".
+gv_attr_type(quadType)    --> "quadType".
+gv_attr_type(rankType)    --> "rankType".
+gv_attr_type(rankdir)     --> "rankdir".
+gv_attr_type(rect)        --> "rect".
+gv_attr_type(shape)       --> "shape".
+gv_attr_type(smoothType)  --> "smoothType".
+gv_attr_type(splineType)  --> "splineType".
+gv_attr_type(startType)   --> "startType".
+gv_attr_type(string)      --> "string".
+gv_attr_type(style)       --> "style".
+gv_attr_type(viewPort)    --> "viewPort".
+
+
+
+%! addDouble(+Float:float)// .
+% An *addDouble* is represented by a Prolog float.
+
+addDouble(N) --> ("+", ! ; ""), float(N).
+
+
+
+%! addPoint(+Point:compound)// .
+% An *addPoint* is represented by a compound of the following form:
+% `point(X:float,Y:float,InputOnly:boolean)`.
+
+addPoint(Point) --> ("+", ! ; ""), point(Point).
+
+
+
+%! arrowType(+ArrowType:atom)// .
+
+arrowType(V) --> primitive_shape(V).
+arrowType(V) --> derived(V).
+arrowType(V) --> backwards_compatible(V).
+
+primitive_shape(box)     --> "box".
+primitive_shape(crow)    --> "crow".
+primitive_shape(circle)  --> "circle".
+primitive_shape(diamond) --> "diamond".
+primitive_shape(dot)     --> "dot".
+primitive_shape(inv)     --> "inv".
+primitive_shape(none)    --> "none".
+primitive_shape(normal)  --> "normal".
+primitive_shape(tee)     --> "tee".
+primitive_shape(vee)     --> "vee".
+
+derived(odot)     --> "odot".
+derived(invdot)   --> "invdot".
+derived(invodot)  --> "invodot".
+derived(obox)     --> "obox".
+derived(odiamond) --> "odiamond".
+
+backwards_compatible(ediamond) --> "ediamond".
+backwards_compatible(empty)    --> "empty".
+backwards_compatible(halfopen) --> "halfopen".
+backwards_compatible(invempty) --> "invempty".
+backwards_compatible(open)     --> "open".
+
+
+
+%! bool(+Value:boolean)// .
+
+bool(false) --> "false".
+bool(false) --> "no".
+bool(true)  --> "true".
+bool(true)  --> "yes".
+
+
+
+%! clusterMode(+ClusterMode:atom)// .
+
+clusterMode(global) --> "global".
+clusterMode(local) --> "local".
+clusterMode(none) --> "none".
+
+
+
+%! dirType(+DirectionType:oneof([back,both,forward,none]))// .
+
+dirType(back) --> "back".
+dirType(both) --> "both".
+dirType(forward) --> "forward".
+dirType(none) --> "none".
+
+
+
+%! double(+Double:float)// .
+
+double(N) --> float(N).
+
+
+
+%! doubleList(+Doubles:list(float))// .
+
+doubleList([H|T]) --> double(H), (":", !, doubleList(T) ; {T = []}).
+
+
+
+%! escString(+String:or([atom,string]))// .
+% @tbd Support for context-dependent replacements.
+
+escString(S1) -->
+  {(  string(S1)
+  ->  string_phrase(escape_double_quotes, S1, S2)
+  ;   atom_phrase(escape_double_quotes, S1, S2)
+  )},
+  "\"", atom(S2), "\"".
+
+escape_double_quotes, [0'\\,0'"] --> [0'"], !, escape_double_quotes.
+escape_double_quotes, [X]        --> [X],   !, escape_double_quotes.
+escape_double_quotes             --> "".
+
+
+
+% @tbd layerList
+
+
+
+% @tbd layerRange
+
+
+
+%! lblString(+String:compound)// .
+
+lblString(html_like_label(V)) --> gv_html_like_label(V).
+lblString(V) --> escString(V).
+
+
+
+%! int(+Integer:integer)// .
+
+int(V) --> integer(V).
+
+
+
+%! outputMode(+OutputMode:atom)// .
+
+outputMode(breadthfirst) --> "breadthfirst".
+outputMode(edgesfirst)   --> "edgesfirst".
+outputMode(nodesfirst)   --> "nodesfirst".
+
+
+
+% @tbd packMode
+
+
+
+%! pagedir(+PageDirection:atom)// .
+
+pagedir('BL') --> "BL".
+pagedir('BR') --> "BR".
+pagedir('LB') --> "LB".
+pagedir('LT') --> "LT".
+pagedir('RB') --> "RB".
+pagedir('RT') --> "RT".
+pagedir('TL') --> "TL".
+pagedir('TR') --> "TR".
+
+
+
+%! point(+Point:compound)// .
+% A *point* is represented by a compound of the following form:
+% `point(X:float,Y:float,Changeable:boolean)`.
+
+point(point(X,Y,Changeable)) -->
+  float(X), ",", float(Y),
+  input_changeable(Changeable).
+
+input_changeable(false) --> "".
+input_changeable(true) --> "!".
+
+
+
+%! pointList(+Points:list(compound))// .
+
+pointList(Points) -->
+  *(point, Points).
+
+
+
+% @tbd portPos
+
+
+
+%! quadType(+QuadType:atom)// .
+
+quadType(fast) --> "fast".
+quadType(none) --> "none".
+quadType(normal) --> "normal".
+
+
+
+%! rankType(+RankType:atom)// .
+
+rankType(max) --> "max".
+rankType(min) --> "min".
+rankType(same) --> "same".
+rankType(sink) --> "sink".
+rankType(source) --> "source".
+
+
+
+%! rankdir(+RankDirection:oneof(['BT','LR','RL','TB']))// .
+
+rankdir('BT') --> "BT".
+rankdir('LR') --> "LR".
+rankdir('RL') --> "RL".
+rankdir('TB') --> "TB".
+
+
+
+%! rect(+Rectangle:compound)// .
+
+rect(rect(LowerLeftX,LowerLeftY,UpperRightX,UpperRightY)) -->
+  float(LowerLeftX), ",",
+  float(LowerLeftY), ",",
+  float(UpperRightX), ",",
+  float(UpperRightY).
+
+
+
+%! shape(+Shape:atom)// .
+
+shape(V) --> {polygon_based_shape(V)}, atom(V).
+
+polygon_based_shape(assembly).
+polygon_based_shape(box).
+polygon_based_shape(box3d).
+polygon_based_shape(cds).
+polygon_based_shape(circle).
+polygon_based_shape(component).
+polygon_based_shape(diamond).
+polygon_based_shape(doublecircle).
+polygon_based_shape(doubleoctagon).
+polygon_based_shape(egg).
+polygon_based_shape(ellipse).
+polygon_based_shape(fivepoverhang).
+polygon_based_shape(folder).
+polygon_based_shape(hexagon).
+polygon_based_shape(house).
+polygon_based_shape(insulator).
+polygon_based_shape(invhouse).
+polygon_based_shape(invtrapezium).
+polygon_based_shape(invtriangle).
+polygon_based_shape(larrow).
+polygon_based_shape(lpromoter).
+polygon_based_shape('Mcircle').
+polygon_based_shape('Mdiamond').
+polygon_based_shape('Msquare').
+polygon_based_shape(none).
+polygon_based_shape(note).
+polygon_based_shape(noverhang).
+polygon_based_shape(octagon).
+polygon_based_shape(oval).
+polygon_based_shape(parallelogram).
+polygon_based_shape(pentagon).
+polygon_based_shape(plaintext).
+polygon_based_shape(point).
+polygon_based_shape(polygon).
+polygon_based_shape(primersite).
+polygon_based_shape(promoter).
+polygon_based_shape(proteasesite).
+polygon_based_shape(proteinstab).
+polygon_based_shape(rarrow).
+polygon_based_shape(rect).
+polygon_based_shape(rectangle).
+polygon_based_shape(restrictionsite).
+polygon_based_shape(ribosite).
+polygon_based_shape(rnastab).
+polygon_based_shape(rpromoter).
+polygon_based_shape(septagon).
+polygon_based_shape(signature).
+polygon_based_shape(square).
+polygon_based_shape(tab).
+polygon_based_shape(terminator).
+polygon_based_shape(threepoverhang).
+polygon_based_shape(trapezium).
+polygon_based_shape(triangle).
+polygon_based_shape(tripleoctagon).
+polygon_based_shape(utr).
+
+
+
+%! smoothType(+SmoothType:atom)// .
+
+smoothType(V) --> {smoothType(V)}, atom(V).
+
+smoothType(avg_dist).
+smoothType(graph_dist).
+smoothType(none).
+smoothType(power_dist).
+smoothType(rng).
+smoothType(spring).
+smoothType(triangle).
+
+
+
+% @tbd splineType
+
+
+
+% @tbd startType
+
+
+
+%! string(?String:atom)// .
+% A GraphViz string.
+
+string(Content) --> "\"", atom(Content), "\"".
+
+
+
+%! style(?Context:oneof([cluster,edge,node]), ?Style:atom) is nondet.
+
+style(Context, Style) --> {style(Context, Style)}, atom(Style).
+
+style(cluster, bold).
+style(cluster, dashed).
+style(cluster, dotted).
+style(cluster, filled).
+style(cluster, rounded).
+style(cluster, solid).
+style(cluster, striped).
+style(edge, bold).
+style(edge, dashed).
+style(edge, dotted).
+style(edge, solid).
+style(node, bold).
+style(node, dashed).
+style(node, diagonals).
+style(node, dotted).
+style(node, filled).
+style(node, rounded).
+style(node, solid).
+style(node, striped).
+style(node, wedged).
+
+
+
+% @tbd viewPort
diff --git a/prolog/gv/gv_attrs.data b/prolog/gv/gv_attrs.data
new file mode 100644
index 0000000..95fc680
--- /dev/null
+++ b/prolog/gv/gv_attrs.data
@@ -0,0 +1,169 @@
+gv_attr('Damping',[graph],[double],'0.99','0.0','neato only').
+gv_attr('K',[cluster,graph],[double],'0.3','0','sfdp, fdp only').
+gv_attr('URL',[cluster,edge,graph,node],[escString],_G59448,'','svg, postscript, map only').
+gv_attr('_background',[graph],[string],_G59644,'','').
+gv_attr(area,[cluster,node],[double],'1.0','>0','patchwork only').
+gv_attr(arrowhead,[edge],[arrowType],normal,'','').
+gv_attr(arrowsize,[edge],[double],'1.0','0.0','').
+gv_attr(arrowtail,[edge],[arrowType],normal,'','').
+gv_attr(bb,[graph],[rect],'','','write only').
+gv_attr(bgcolor,[cluster,graph],[color,colorList],_G60571,'','').
+gv_attr(center,[graph],[bool],false,'','').
+gv_attr(charset,[graph],[string],'"UTF-8"','','').
+gv_attr(clusterrank,[graph],[clusterMode],local,'','dot only').
+gv_attr(color,[cluster,edge,node],[color,colorList],black,'','').
+gv_attr(colorscheme,[cluster,edge,graph,node],[string],'','','').
+gv_attr(comment,[edge,graph,node],[string],'','','').
+gv_attr(compound,[graph],[bool],false,'','dot only').
+gv_attr(concentrate,[graph],[bool],false,'','').
+gv_attr(constraint,[edge],[bool],true,'','dot only').
+gv_attr(decorate,[edge],[bool],false,'','').
+gv_attr(defaultdist,[graph],[double],'1+(avg. len)*sqrt(|V|)',epsilon,'neato only').
+gv_attr(dim,[graph],[int],'2','2','sfdp, fdp, neato only').
+gv_attr(dimen,[graph],[int],'2','2','sfdp, fdp, neato only').
+gv_attr(dir,[edge],[dirType],'forward(directed)none(undirected)','','').
+gv_attr(diredgeconstraints,[graph],[string,bool],false,'','neato only').
+gv_attr(distortion,[node],[double],'0.0','-100.0','').
+gv_attr(dpi,[graph],[double],'96.00.0','','svg, bitmap output only').
+gv_attr(edgeURL,[edge],[escString],'','','svg, map only').
+gv_attr(edgehref,[edge],[escString],'','','svg, map only').
+gv_attr(edgetarget,[edge],[escString],_G63711,'','svg, map only').
+gv_attr(edgetooltip,[edge],[escString],'','','svg, cmap only').
+gv_attr(epsilon,[graph],[double],'.0001 * # nodes(mode == KK).0001(mode == major)','','neato only').
+gv_attr(esep,[graph],[addDouble,addPoint],'+3','','not dot').
+gv_attr(fillcolor,[cluster,edge,node],[color,colorList],'lightgrey(nodes)black(clusters)','','').
+gv_attr(fixedsize,[node],[bool,string],false,'','').
+gv_attr(fontcolor,[cluster,edge,graph,node],[color],black,'','').
+gv_attr(fontname,[cluster,edge,graph,node],[string],'"Times-Roman"','','').
+gv_attr(fontnames,[graph],[string],'','','svg only').
+gv_attr(fontpath,[graph],[string],'system-dependent','','').
+gv_attr(fontsize,[cluster,edge,graph,node],[double],'14.0','1.0','').
+gv_attr(forcelabels,[graph],[bool],true,'','').
+gv_attr(gradientangle,[cluster,graph,node],[int],'','','').
+gv_attr(group,[node],[string],'','','dot only').
+gv_attr(headURL,[edge],[escString],'','','svg, map only').
+gv_attr(head_lp,[edge],[point],'','','write only').
+gv_attr(headclip,[edge],[bool],true,'','').
+gv_attr(headhref,[edge],[escString],'','','svg, map only').
+gv_attr(headlabel,[edge],[lblString],'','','').
+gv_attr(headport,[edge],[portPos],center,'','').
+gv_attr(headtarget,[edge],[escString],_G4892,'','svg, map only').
+gv_attr(headtooltip,[edge],[escString],'','','svg, cmap only').
+gv_attr(height,[node],[double],'0.5','0.02','').
+gv_attr(href,[cluster,edge,graph,node],[escString],'','','svg, postscript, map only').
+gv_attr(id,[cluster,edge,graph,node],[escString],'','','svg, postscript, map only').
+gv_attr(image,[node],[string],'','','').
+gv_attr(imagepath,[graph],[string],'','','').
+gv_attr(imagescale,[node],[bool,string],false,'','').
+gv_attr(inputscale,[graph],[double],_G6250,'','fdp, neato only').
+gv_attr(label,[cluster,edge,graph,node],[lblString],'"\\N" (nodes)"" (otherwise)','','').
+gv_attr(labelURL,[edge],[escString],'','','svg, map only').
+gv_attr(label_scheme,[graph],[int],'0','0','sfdp only').
+gv_attr(labelangle,[edge],[double],'-25.0','-180.0','').
+gv_attr(labeldistance,[edge],[double],'1.0','0.0','').
+gv_attr(labelfloat,[edge],[bool],false,'','').
+gv_attr(labelfontcolor,[edge],[color],black,'','').
+gv_attr(labelfontname,[edge],[string],'"Times-Roman"','','').
+gv_attr(labelfontsize,[edge],[double],'14.0','1.0','').
+gv_attr(labelhref,[edge],[escString],'','','svg, map only').
+gv_attr(labeljust,[cluster,graph],[string],'"c"','','').
+gv_attr(labelloc,[cluster,graph,node],[string],'"t"(clusters)"b"(root graphs)"c"(nodes)','','').
+gv_attr(labeltarget,[edge],[escString],_G8276,'','svg, map only').
+gv_attr(labeltooltip,[edge],[escString],'','','svg, cmap only').
+gv_attr(landscape,[graph],[bool],false,'','').
+gv_attr(layer,[cluster,edge,node],[layerRange],'','','').
+gv_attr(layerlistsep,[graph],[string],'","','','').
+gv_attr(layers,[graph],[layerList],'','','').
+gv_attr(layerselect,[graph],[layerRange],'','','').
+gv_attr(layersep,[graph],[string],'" :\\t"','','').
+gv_attr(layout,[graph],[string],'','','').
+gv_attr(len,[edge],[double],'1.0(neato)0.3(fdp)','','fdp, neato only').
+gv_attr(levels,[graph],[int],'MAXINT','0.0','sfdp only').
+gv_attr(levelsgap,[graph],[double],'0.0','','neato only').
+gv_attr(lhead,[edge],[string],'','','dot only').
+gv_attr(lheight,[cluster,graph],[double],'','','write only').
+gv_attr(lp,[cluster,edge,graph],[point],'','','write only').
+gv_attr(ltail,[edge],[string],'','','dot only').
+gv_attr(lwidth,[cluster,graph],[double],'','','write only').
+gv_attr(margin,[cluster,graph,node],[double,point],'<device-dependent>','','').
+gv_attr(maxiter,[graph],[int],'100 * # nodes(mode == KK)200(mode == major)600(fdp)','','fdp, neato only').
+gv_attr(mclimit,[graph],[double],'1.0','','dot only').
+gv_attr(mindist,[graph],[double],'1.0','0.0','circo only').
+gv_attr(minlen,[edge],[int],'1','0','dot only').
+gv_attr(mode,[graph],[string],major,'','neato only').
+gv_attr(model,[graph],[string],shortpath,'','neato only').
+gv_attr(mosek,[graph],[bool],false,'','neato only').
+gv_attr(nodesep,[graph],[double],'0.25','0.02','').
+gv_attr(nojustify,[cluster,edge,graph,node],[bool],false,'','').
+gv_attr(normalize,[graph],[double,bool],false,'','not dot').
+gv_attr(notranslate,[graph],[bool],false,'','neato only').
+gv_attr(nslimitnslimit1,[graph],[double],'','','dot only').
+gv_attr(ordering,[graph,node],[string],'','','dot only').
+gv_attr(orientation,[node],[double],'0.0','360.0','').
+gv_attr(orientation,[graph],[string],'','','').
+gv_attr(outputorder,[graph],[outputMode],breadthfirst,'','').
+gv_attr(overlap,[graph],[string,bool],true,'','not dot').
+gv_attr(overlap_scaling,[graph],[double],'-4','-1.0e10','prism only').
+gv_attr(overlap_shrink,[graph],[bool],true,'','prism only').
+gv_attr(pack,[graph],[bool,int],false,'','').
+gv_attr(packmode,[graph],[packMode],node,'','').
+gv_attr(pad,[graph],[double,point],'0.0555 (4 points)','','').
+gv_attr(page,[graph],[double,point],'','','').
+gv_attr(pagedir,[graph],[pagedir],'BL','','').
+gv_attr(pencolor,[cluster],[color],black,'','').
+gv_attr(penwidth,[cluster,edge,node],[double],'1.0','0.0','').
+gv_attr(peripheries,[cluster,node],[int],'shape default(nodes)1(clusters)','0','').
+gv_attr(pin,[node],[bool],false,'','fdp, neato only').
+gv_attr(pos,[edge,node],[point,splineType],'','','').
+gv_attr(quadtree,[graph],[quadType,bool],normal,'','sfdp only').
+gv_attr(quantum,[graph],[double],'0.0','0.0','').
+gv_attr(rank,[subgraph],[rankType],'','','dot only').
+gv_attr(rankdir,[graph],[rankdir],'TB','','dot only').
+gv_attr(ranksep,[graph],[double,doubleList],'0.5(dot)1.0(twopi)','0.02','twopi, dot only').
+gv_attr(ratio,[graph],[double,string],'','','').
+gv_attr(rects,[node],[rect],'','','write only').
+gv_attr(regular,[node],[bool],false,'','').
+gv_attr(remincross,[graph],[bool],true,'','dot only').
+gv_attr(repulsiveforce,[graph],[double],'1.0','0.0','sfdp only').
+gv_attr(resolution,[graph],[double],'96.00.0','','svg, bitmap output only').
+gv_attr(root,[graph,node],[string,bool],'<none>(graphs)false(nodes)','','circo, twopi only').
+gv_attr(rotate,[graph],[int],'0','','').
+gv_attr(rotation,[graph],[double],'0','','sfdp only').
+gv_attr(samehead,[edge],[string],'','','dot only').
+gv_attr(sametail,[edge],[string],'','','dot only').
+gv_attr(samplepoints,[node],[int],'8(output)20(overlap and image maps)','','').
+gv_attr(scale,[graph],[double,point],'','','not dot').
+gv_attr(searchsize,[graph],[int],'30','','dot only').
+gv_attr(sep,[graph],[addDouble,addPoint],'+4','','not dot').
+gv_attr(shape,[node],[shape],ellipse,'','').
+gv_attr(shapefile,[node],[string],'','','').
+gv_attr(showboxes,[edge,graph,node],[int],'0','0','dot only').
+gv_attr(sides,[node],[int],'4','0','').
+gv_attr(size,[graph],[double,point],'','','').
+gv_attr(skew,[node],[double],'0.0','-100.0','').
+gv_attr(smoothing,[graph],[smoothType],'"none"','','sfdp only').
+gv_attr(sortv,[cluster,graph,node],[int],'0','0','').
+gv_attr(splines,[graph],[bool,string],'','','').
+gv_attr(start,[graph],[startType],'','','fdp, neato only').
+gv_attr(style,[cluster,edge,graph,node],[style],'','','').
+gv_attr(stylesheet,[graph],[string],'','','svg only').
+gv_attr(tailURL,[edge],[escString],'','','svg, map only').
+gv_attr(tail_lp,[edge],[point],'','','write only').
+gv_attr(tailclip,[edge],[bool],true,'','').
+gv_attr(tailhref,[edge],[escString],'','','svg, map only').
+gv_attr(taillabel,[edge],[lblString],'','','').
+gv_attr(tailport,[edge],[portPos],center,'','').
+gv_attr(tailtarget,[edge],[escString],_G21528,'','svg, map only').
+gv_attr(tailtooltip,[edge],[escString],'','','svg, cmap only').
+gv_attr(target,[cluster,edge,graph,node],[escString,string],_G21878,'','svg, map only').
+gv_attr(tooltip,[cluster,edge,node],[escString],'','','svg, cmap only').
+gv_attr(truecolor,[graph],[bool],'','','bitmap output only').
+gv_attr(vertices,[node],[pointList],'','','write only').
+gv_attr(viewport,[graph],[viewPort],'','','').
+gv_attr(voro_margin,[graph],[double],'0.05','0.0','not dot').
+gv_attr(weight,[edge],[int,double],'1','0(dot,twopi)1(neato,fdp)','').
+gv_attr(width,[node],[double],'0.75','0.01','').
+gv_attr(xdotversion,[graph],[string],'','','xdot only').
+gv_attr(xlabel,[edge,node],[lblString],'','','').
+gv_attr(xlp,[edge,node],[point],'','','write only').
+gv_attr(z,[node],[double],'0.0','-MAXFLOAT-1000','').
diff --git a/prolog/gv/gv_attrs.pl b/prolog/gv/gv_attrs.pl
new file mode 100644
index 0000000..4a92d5b
--- /dev/null
+++ b/prolog/gv/gv_attrs.pl
@@ -0,0 +1,76 @@
+:- module(
+  gv_attrs,
+  [
+    gv_attr_value//2 % +Context:oneof([cluster,edge,graph,node,subgraph])
+                     % +Attr
+  ]
+).
+:- ensure_loaded(library('gv/gv_attrs.data')).
+
+/** <module> GraphViz attributes
+
+@author Wouter Beek
+@version 2015/07-2015/08, 2015/10, 2016/03
+*/
+
+:- use_module(library(apply)).
+:- use_module(library(dcg/dcg_ext)).
+:- use_module(library(error)).
+:- use_module(library(gv/gv_attr_type), [gv_attr_type//1]).
+:- use_module(library(lists)).
+:- use_module(library(os/file_ext)).
+
+
+
+
+
+%! gv_attr_value(+Context, +Attr)// is det.
+% Uses the default value in case Value is uninstantiated.
+% Otherwise, performs a typecheck and converts the given value.
+%
+% Context can be either `cluster`, `edge`, `graph`, `node` or `subgraph`.
+
+% Use the default if no value is given.
+gv_attr_value(Context, Name=Value) -->
+  {
+    var(Value),
+    gv_attr(Name, UsedBy, _, DefaultValue, _, _),
+    % Check validity of context.
+    memberchk(Context, UsedBy)
+  }, !,
+  gv_attr_value(Context, Name=DefaultValue).
+gv_attr_value(Context, Name=Value) -->
+  {
+    % Check the validity of the context argument.
+    gv_attr(Name, UsedBy, Types, _, Minimum, _),
+    memberchk(Context, UsedBy),
+
+    %  Pick a value type non-deterministically.
+    member(Type, Types),
+
+    % The `style` type is the only one that requires the context argument.
+    (   Type == style
+    ->  Dcg =.. [Type,Context]
+    ;   Dcg =.. [Type]
+    ),
+
+    % Check validity of Value w.r.t. minimum value -- if available.
+    check_minimum(Value, Minimum)
+  }, !,
+  dcg_call(gv_attr_type:Dcg, Value).
+gv_attr_value(_, Name=_) -->
+  {existence_error(gv_attr, Name)}.
+
+
+
+
+
+% HELPERS %
+
+%! check_minimum(+Value:atom, +Minimum:number) is semidet.
+% Trivially succeeds if no minimum value is available for a given attribute.
+
+check_minimum(_, ''):- !.
+check_minimum(V, Min1):-
+  atom_number(Min1, Min2),
+  Min2 =< V.
diff --git a/prolog/gv/gv_color.data b/prolog/gv/gv_color.data
new file mode 100644
index 0000000..166b13c
--- /dev/null
+++ b/prolog/gv/gv_color.data
@@ -0,0 +1,802 @@
+gv_color(x11,aliceblue).
+gv_color(x11,antiquewhite).
+gv_color(x11,antiquewhite1).
+gv_color(x11,antiquewhite2).
+gv_color(x11,antiquewhite3).
+gv_color(x11,antiquewhite4).
+gv_color(x11,aquamarine).
+gv_color(x11,aquamarine1).
+gv_color(x11,aquamarine2).
+gv_color(x11,aquamarine3).
+gv_color(x11,aquamarine4).
+gv_color(x11,azure).
+gv_color(x11,azure1).
+gv_color(x11,azure2).
+gv_color(x11,azure3).
+gv_color(x11,azure4).
+gv_color(x11,beige).
+gv_color(x11,bisque).
+gv_color(x11,bisque1).
+gv_color(x11,bisque2).
+gv_color(x11,bisque3).
+gv_color(x11,bisque4).
+gv_color(x11,black).
+gv_color(x11,blanchedalmond).
+gv_color(x11,blue).
+gv_color(x11,blue1).
+gv_color(x11,blue2).
+gv_color(x11,blue3).
+gv_color(x11,blue4).
+gv_color(x11,blueviolet).
+gv_color(x11,brown).
+gv_color(x11,brown1).
+gv_color(x11,brown2).
+gv_color(x11,brown3).
+gv_color(x11,brown4).
+gv_color(x11,burlywood).
+gv_color(x11,burlywood1).
+gv_color(x11,burlywood2).
+gv_color(x11,burlywood3).
+gv_color(x11,burlywood4).
+gv_color(x11,cadetblue).
+gv_color(x11,cadetblue1).
+gv_color(x11,cadetblue2).
+gv_color(x11,cadetblue3).
+gv_color(x11,cadetblue4).
+gv_color(x11,chartreuse).
+gv_color(x11,chartreuse1).
+gv_color(x11,chartreuse2).
+gv_color(x11,chartreuse3).
+gv_color(x11,chartreuse4).
+gv_color(x11,chocolate).
+gv_color(x11,chocolate1).
+gv_color(x11,chocolate2).
+gv_color(x11,chocolate3).
+gv_color(x11,chocolate4).
+gv_color(x11,coral).
+gv_color(x11,coral1).
+gv_color(x11,coral2).
+gv_color(x11,coral3).
+gv_color(x11,coral4).
+gv_color(x11,cornflowerblue).
+gv_color(x11,cornsilk).
+gv_color(x11,cornsilk1).
+gv_color(x11,cornsilk2).
+gv_color(x11,cornsilk3).
+gv_color(x11,cornsilk4).
+gv_color(x11,crimson).
+gv_color(x11,cyan).
+gv_color(x11,cyan1).
+gv_color(x11,cyan2).
+gv_color(x11,cyan3).
+gv_color(x11,cyan4).
+gv_color(x11,darkgoldenrod).
+gv_color(x11,darkgoldenrod1).
+gv_color(x11,darkgoldenrod2).
+gv_color(x11,darkgoldenrod3).
+gv_color(x11,darkgoldenrod4).
+gv_color(x11,darkgreen).
+gv_color(x11,darkkhaki).
+gv_color(x11,darkolivegreen).
+gv_color(x11,darkolivegreen1).
+gv_color(x11,darkolivegreen2).
+gv_color(x11,darkolivegreen3).
+gv_color(x11,darkolivegreen4).
+gv_color(x11,darkorange).
+gv_color(x11,darkorange1).
+gv_color(x11,darkorange2).
+gv_color(x11,darkorange3).
+gv_color(x11,darkorange4).
+gv_color(x11,darkorchid).
+gv_color(x11,darkorchid1).
+gv_color(x11,darkorchid2).
+gv_color(x11,darkorchid3).
+gv_color(x11,darkorchid4).
+gv_color(x11,darksalmon).
+gv_color(x11,darkseagreen).
+gv_color(x11,darkseagreen1).
+gv_color(x11,darkseagreen2).
+gv_color(x11,darkseagreen3).
+gv_color(x11,darkseagreen4).
+gv_color(x11,darkslateblue).
+gv_color(x11,darkslategray).
+gv_color(x11,darkslategray1).
+gv_color(x11,darkslategray2).
+gv_color(x11,darkslategray3).
+gv_color(x11,darkslategray4).
+gv_color(x11,darkslategrey).
+gv_color(x11,darkturquoise).
+gv_color(x11,darkviolet).
+gv_color(x11,deeppink).
+gv_color(x11,deeppink1).
+gv_color(x11,deeppink2).
+gv_color(x11,deeppink3).
+gv_color(x11,deeppink4).
+gv_color(x11,deepskyblue).
+gv_color(x11,deepskyblue1).
+gv_color(x11,deepskyblue2).
+gv_color(x11,deepskyblue3).
+gv_color(x11,deepskyblue4).
+gv_color(x11,dimgray).
+gv_color(x11,dimgrey).
+gv_color(x11,dodgerblue).
+gv_color(x11,dodgerblue1).
+gv_color(x11,dodgerblue2).
+gv_color(x11,dodgerblue3).
+gv_color(x11,dodgerblue4).
+gv_color(x11,firebrick).
+gv_color(x11,firebrick1).
+gv_color(x11,firebrick2).
+gv_color(x11,firebrick3).
+gv_color(x11,firebrick4).
+gv_color(x11,floralwhite).
+gv_color(x11,forestgreen).
+gv_color(x11,gainsboro).
+gv_color(x11,ghostwhite).
+gv_color(x11,gold).
+gv_color(x11,gold1).
+gv_color(x11,gold2).
+gv_color(x11,gold3).
+gv_color(x11,gold4).
+gv_color(x11,goldenrod).
+gv_color(x11,goldenrod1).
+gv_color(x11,goldenrod2).
+gv_color(x11,goldenrod3).
+gv_color(x11,goldenrod4).
+gv_color(x11,gray).
+gv_color(x11,gray0).
+gv_color(x11,gray1).
+gv_color(x11,gray10).
+gv_color(x11,gray100).
+gv_color(x11,gray11).
+gv_color(x11,gray12).
+gv_color(x11,gray13).
+gv_color(x11,gray14).
+gv_color(x11,gray15).
+gv_color(x11,gray16).
+gv_color(x11,gray17).
+gv_color(x11,gray18).
+gv_color(x11,gray19).
+gv_color(x11,gray2).
+gv_color(x11,gray20).
+gv_color(x11,gray21).
+gv_color(x11,gray22).
+gv_color(x11,gray23).
+gv_color(x11,gray24).
+gv_color(x11,gray25).
+gv_color(x11,gray26).
+gv_color(x11,gray27).
+gv_color(x11,gray28).
+gv_color(x11,gray29).
+gv_color(x11,gray3).
+gv_color(x11,gray30).
+gv_color(x11,gray31).
+gv_color(x11,gray32).
+gv_color(x11,gray33).
+gv_color(x11,gray34).
+gv_color(x11,gray35).
+gv_color(x11,gray36).
+gv_color(x11,gray37).
+gv_color(x11,gray38).
+gv_color(x11,gray39).
+gv_color(x11,gray4).
+gv_color(x11,gray40).
+gv_color(x11,gray41).
+gv_color(x11,gray42).
+gv_color(x11,gray43).
+gv_color(x11,gray44).
+gv_color(x11,gray45).
+gv_color(x11,gray46).
+gv_color(x11,gray47).
+gv_color(x11,gray48).
+gv_color(x11,gray49).
+gv_color(x11,gray5).
+gv_color(x11,gray50).
+gv_color(x11,gray51).
+gv_color(x11,gray52).
+gv_color(x11,gray53).
+gv_color(x11,gray54).
+gv_color(x11,gray55).
+gv_color(x11,gray56).
+gv_color(x11,gray57).
+gv_color(x11,gray58).
+gv_color(x11,gray59).
+gv_color(x11,gray6).
+gv_color(x11,gray60).
+gv_color(x11,gray61).
+gv_color(x11,gray62).
+gv_color(x11,gray63).
+gv_color(x11,gray64).
+gv_color(x11,gray65).
+gv_color(x11,gray66).
+gv_color(x11,gray67).
+gv_color(x11,gray68).
+gv_color(x11,gray69).
+gv_color(x11,gray7).
+gv_color(x11,gray70).
+gv_color(x11,gray71).
+gv_color(x11,gray72).
+gv_color(x11,gray73).
+gv_color(x11,gray74).
+gv_color(x11,gray75).
+gv_color(x11,gray76).
+gv_color(x11,gray77).
+gv_color(x11,gray78).
+gv_color(x11,gray79).
+gv_color(x11,gray8).
+gv_color(x11,gray80).
+gv_color(x11,gray81).
+gv_color(x11,gray82).
+gv_color(x11,gray83).
+gv_color(x11,gray84).
+gv_color(x11,gray85).
+gv_color(x11,gray86).
+gv_color(x11,gray87).
+gv_color(x11,gray88).
+gv_color(x11,gray89).
+gv_color(x11,gray9).
+gv_color(x11,gray90).
+gv_color(x11,gray91).
+gv_color(x11,gray92).
+gv_color(x11,gray93).
+gv_color(x11,gray94).
+gv_color(x11,gray95).
+gv_color(x11,gray96).
+gv_color(x11,gray97).
+gv_color(x11,gray98).
+gv_color(x11,gray99).
+gv_color(x11,green).
+gv_color(x11,green1).
+gv_color(x11,green2).
+gv_color(x11,green3).
+gv_color(x11,green4).
+gv_color(x11,greenyellow).
+gv_color(x11,grey).
+gv_color(x11,grey0).
+gv_color(x11,grey1).
+gv_color(x11,grey10).
+gv_color(x11,grey100).
+gv_color(x11,grey11).
+gv_color(x11,grey12).
+gv_color(x11,grey13).
+gv_color(x11,grey14).
+gv_color(x11,grey15).
+gv_color(x11,grey16).
+gv_color(x11,grey17).
+gv_color(x11,grey18).
+gv_color(x11,grey19).
+gv_color(x11,grey2).
+gv_color(x11,grey20).
+gv_color(x11,grey21).
+gv_color(x11,grey22).
+gv_color(x11,grey23).
+gv_color(x11,grey24).
+gv_color(x11,grey25).
+gv_color(x11,grey26).
+gv_color(x11,grey27).
+gv_color(x11,grey28).
+gv_color(x11,grey29).
+gv_color(x11,grey3).
+gv_color(x11,grey30).
+gv_color(x11,grey31).
+gv_color(x11,grey32).
+gv_color(x11,grey33).
+gv_color(x11,grey34).
+gv_color(x11,grey35).
+gv_color(x11,grey36).
+gv_color(x11,grey37).
+gv_color(x11,grey38).
+gv_color(x11,grey39).
+gv_color(x11,grey4).
+gv_color(x11,grey40).
+gv_color(x11,grey41).
+gv_color(x11,grey42).
+gv_color(x11,grey43).
+gv_color(x11,grey44).
+gv_color(x11,grey45).
+gv_color(x11,grey46).
+gv_color(x11,grey47).
+gv_color(x11,grey48).
+gv_color(x11,grey49).
+gv_color(x11,grey5).
+gv_color(x11,grey50).
+gv_color(x11,grey51).
+gv_color(x11,grey52).
+gv_color(x11,grey53).
+gv_color(x11,grey54).
+gv_color(x11,grey55).
+gv_color(x11,grey56).
+gv_color(x11,grey57).
+gv_color(x11,grey58).
+gv_color(x11,grey59).
+gv_color(x11,grey6).
+gv_color(x11,grey60).
+gv_color(x11,grey61).
+gv_color(x11,grey62).
+gv_color(x11,grey63).
+gv_color(x11,grey64).
+gv_color(x11,grey65).
+gv_color(x11,grey66).
+gv_color(x11,grey67).
+gv_color(x11,grey68).
+gv_color(x11,grey69).
+gv_color(x11,grey7).
+gv_color(x11,grey70).
+gv_color(x11,grey71).
+gv_color(x11,grey72).
+gv_color(x11,grey73).
+gv_color(x11,grey74).
+gv_color(x11,grey75).
+gv_color(x11,grey76).
+gv_color(x11,grey77).
+gv_color(x11,grey78).
+gv_color(x11,grey79).
+gv_color(x11,grey8).
+gv_color(x11,grey80).
+gv_color(x11,grey81).
+gv_color(x11,grey82).
+gv_color(x11,grey83).
+gv_color(x11,grey84).
+gv_color(x11,grey85).
+gv_color(x11,grey86).
+gv_color(x11,grey87).
+gv_color(x11,grey88).
+gv_color(x11,grey89).
+gv_color(x11,grey9).
+gv_color(x11,grey90).
+gv_color(x11,grey91).
+gv_color(x11,grey92).
+gv_color(x11,grey93).
+gv_color(x11,grey94).
+gv_color(x11,grey95).
+gv_color(x11,grey96).
+gv_color(x11,grey97).
+gv_color(x11,grey98).
+gv_color(x11,grey99).
+gv_color(x11,honeydew).
+gv_color(x11,honeydew1).
+gv_color(x11,honeydew2).
+gv_color(x11,honeydew3).
+gv_color(x11,honeydew4).
+gv_color(x11,hotpink).
+gv_color(x11,hotpink1).
+gv_color(x11,hotpink2).
+gv_color(x11,hotpink3).
+gv_color(x11,hotpink4).
+gv_color(x11,indianred).
+gv_color(x11,indianred1).
+gv_color(x11,indianred2).
+gv_color(x11,indianred3).
+gv_color(x11,indianred4).
+gv_color(x11,indigo).
+gv_color(x11,invis).
+gv_color(x11,ivory).
+gv_color(x11,ivory1).
+gv_color(x11,ivory2).
+gv_color(x11,ivory3).
+gv_color(x11,ivory4).
+gv_color(x11,khaki).
+gv_color(x11,khaki1).
+gv_color(x11,khaki2).
+gv_color(x11,khaki3).
+gv_color(x11,khaki4).
+gv_color(x11,lavender).
+gv_color(x11,lavenderblush).
+gv_color(x11,lavenderblush1).
+gv_color(x11,lavenderblush2).
+gv_color(x11,lavenderblush3).
+gv_color(x11,lavenderblush4).
+gv_color(x11,lawngreen).
+gv_color(x11,lemonchiffon).
+gv_color(x11,lemonchiffon1).
+gv_color(x11,lemonchiffon2).
+gv_color(x11,lemonchiffon3).
+gv_color(x11,lemonchiffon4).
+gv_color(x11,lightblue).
+gv_color(x11,lightblue1).
+gv_color(x11,lightblue2).
+gv_color(x11,lightblue3).
+gv_color(x11,lightblue4).
+gv_color(x11,lightcoral).
+gv_color(x11,lightcyan).
+gv_color(x11,lightcyan1).
+gv_color(x11,lightcyan2).
+gv_color(x11,lightcyan3).
+gv_color(x11,lightcyan4).
+gv_color(x11,lightgoldenrod).
+gv_color(x11,lightgoldenrod1).
+gv_color(x11,lightgoldenrod2).
+gv_color(x11,lightgoldenrod3).
+gv_color(x11,lightgoldenrod4).
+gv_color(x11,lightgoldenrodyellow).
+gv_color(x11,lightgray).
+gv_color(x11,lightgrey).
+gv_color(x11,lightpink).
+gv_color(x11,lightpink1).
+gv_color(x11,lightpink2).
+gv_color(x11,lightpink3).
+gv_color(x11,lightpink4).
+gv_color(x11,lightsalmon).
+gv_color(x11,lightsalmon1).
+gv_color(x11,lightsalmon2).
+gv_color(x11,lightsalmon3).
+gv_color(x11,lightsalmon4).
+gv_color(x11,lightseagreen).
+gv_color(x11,lightskyblue).
+gv_color(x11,lightskyblue1).
+gv_color(x11,lightskyblue2).
+gv_color(x11,lightskyblue3).
+gv_color(x11,lightskyblue4).
+gv_color(x11,lightslateblue).
+gv_color(x11,lightslategray).
+gv_color(x11,lightslategrey).
+gv_color(x11,lightsteelblue).
+gv_color(x11,lightsteelblue1).
+gv_color(x11,lightsteelblue2).
+gv_color(x11,lightsteelblue3).
+gv_color(x11,lightsteelblue4).
+gv_color(x11,lightyellow).
+gv_color(x11,lightyellow1).
+gv_color(x11,lightyellow2).
+gv_color(x11,lightyellow3).
+gv_color(x11,lightyellow4).
+gv_color(x11,limegreen).
+gv_color(x11,linen).
+gv_color(x11,magenta).
+gv_color(x11,magenta1).
+gv_color(x11,magenta2).
+gv_color(x11,magenta3).
+gv_color(x11,magenta4).
+gv_color(x11,maroon).
+gv_color(x11,maroon1).
+gv_color(x11,maroon2).
+gv_color(x11,maroon3).
+gv_color(x11,maroon4).
+gv_color(x11,mediumaquamarine).
+gv_color(x11,mediumblue).
+gv_color(x11,mediumorchid).
+gv_color(x11,mediumorchid1).
+gv_color(x11,mediumorchid2).
+gv_color(x11,mediumorchid3).
+gv_color(x11,mediumorchid4).
+gv_color(x11,mediumpurple).
+gv_color(x11,mediumpurple1).
+gv_color(x11,mediumpurple2).
+gv_color(x11,mediumpurple3).
+gv_color(x11,mediumpurple4).
+gv_color(x11,mediumseagreen).
+gv_color(x11,mediumslateblue).
+gv_color(x11,mediumspringgreen).
+gv_color(x11,mediumturquoise).
+gv_color(x11,mediumvioletred).
+gv_color(x11,midnightblue).
+gv_color(x11,mintcream).
+gv_color(x11,mistyrose).
+gv_color(x11,mistyrose1).
+gv_color(x11,mistyrose2).
+gv_color(x11,mistyrose3).
+gv_color(x11,mistyrose4).
+gv_color(x11,moccasin).
+gv_color(x11,navajowhite).
+gv_color(x11,navajowhite1).
+gv_color(x11,navajowhite2).
+gv_color(x11,navajowhite3).
+gv_color(x11,navajowhite4).
+gv_color(x11,navy).
+gv_color(x11,navyblue).
+gv_color(x11,none).
+gv_color(x11,oldlace).
+gv_color(x11,olivedrab).
+gv_color(x11,olivedrab1).
+gv_color(x11,olivedrab2).
+gv_color(x11,olivedrab3).
+gv_color(x11,olivedrab4).
+gv_color(x11,orange).
+gv_color(x11,orange1).
+gv_color(x11,orange2).
+gv_color(x11,orange3).
+gv_color(x11,orange4).
+gv_color(x11,orangered).
+gv_color(x11,orangered1).
+gv_color(x11,orangered2).
+gv_color(x11,orangered3).
+gv_color(x11,orangered4).
+gv_color(x11,orchid).
+gv_color(x11,orchid1).
+gv_color(x11,orchid2).
+gv_color(x11,orchid3).
+gv_color(x11,orchid4).
+gv_color(x11,palegoldenrod).
+gv_color(x11,palegreen).
+gv_color(x11,palegreen1).
+gv_color(x11,palegreen2).
+gv_color(x11,palegreen3).
+gv_color(x11,palegreen4).
+gv_color(x11,paleturquoise).
+gv_color(x11,paleturquoise1).
+gv_color(x11,paleturquoise2).
+gv_color(x11,paleturquoise3).
+gv_color(x11,paleturquoise4).
+gv_color(x11,palevioletred).
+gv_color(x11,palevioletred1).
+gv_color(x11,palevioletred2).
+gv_color(x11,palevioletred3).
+gv_color(x11,palevioletred4).
+gv_color(x11,papayawhip).
+gv_color(x11,peachpuff).
+gv_color(x11,peachpuff1).
+gv_color(x11,peachpuff2).
+gv_color(x11,peachpuff3).
+gv_color(x11,peachpuff4).
+gv_color(x11,peru).
+gv_color(x11,pink).
+gv_color(x11,pink1).
+gv_color(x11,pink2).
+gv_color(x11,pink3).
+gv_color(x11,pink4).
+gv_color(x11,plum).
+gv_color(x11,plum1).
+gv_color(x11,plum2).
+gv_color(x11,plum3).
+gv_color(x11,plum4).
+gv_color(x11,powderblue).
+gv_color(x11,purple).
+gv_color(x11,purple1).
+gv_color(x11,purple2).
+gv_color(x11,purple3).
+gv_color(x11,purple4).
+gv_color(x11,red).
+gv_color(x11,red1).
+gv_color(x11,red2).
+gv_color(x11,red3).
+gv_color(x11,red4).
+gv_color(x11,rosybrown).
+gv_color(x11,rosybrown1).
+gv_color(x11,rosybrown2).
+gv_color(x11,rosybrown3).
+gv_color(x11,rosybrown4).
+gv_color(x11,royalblue).
+gv_color(x11,royalblue1).
+gv_color(x11,royalblue2).
+gv_color(x11,royalblue3).
+gv_color(x11,royalblue4).
+gv_color(x11,saddlebrown).
+gv_color(x11,salmon).
+gv_color(x11,salmon1).
+gv_color(x11,salmon2).
+gv_color(x11,salmon3).
+gv_color(x11,salmon4).
+gv_color(x11,sandybrown).
+gv_color(x11,seagreen).
+gv_color(x11,seagreen1).
+gv_color(x11,seagreen2).
+gv_color(x11,seagreen3).
+gv_color(x11,seagreen4).
+gv_color(x11,seashell).
+gv_color(x11,seashell1).
+gv_color(x11,seashell2).
+gv_color(x11,seashell3).
+gv_color(x11,seashell4).
+gv_color(x11,sienna).
+gv_color(x11,sienna1).
+gv_color(x11,sienna2).
+gv_color(x11,sienna3).
+gv_color(x11,sienna4).
+gv_color(x11,skyblue).
+gv_color(x11,skyblue1).
+gv_color(x11,skyblue2).
+gv_color(x11,skyblue3).
+gv_color(x11,skyblue4).
+gv_color(x11,slateblue).
+gv_color(x11,slateblue1).
+gv_color(x11,slateblue2).
+gv_color(x11,slateblue3).
+gv_color(x11,slateblue4).
+gv_color(x11,slategray).
+gv_color(x11,slategray1).
+gv_color(x11,slategray2).
+gv_color(x11,slategray3).
+gv_color(x11,slategray4).
+gv_color(x11,slategrey).
+gv_color(x11,snow).
+gv_color(x11,snow1).
+gv_color(x11,snow2).
+gv_color(x11,snow3).
+gv_color(x11,snow4).
+gv_color(x11,springgreen).
+gv_color(x11,springgreen1).
+gv_color(x11,springgreen2).
+gv_color(x11,springgreen3).
+gv_color(x11,springgreen4).
+gv_color(x11,steelblue).
+gv_color(x11,steelblue1).
+gv_color(x11,steelblue2).
+gv_color(x11,steelblue3).
+gv_color(x11,steelblue4).
+gv_color(x11,tan).
+gv_color(x11,tan1).
+gv_color(x11,tan2).
+gv_color(x11,tan3).
+gv_color(x11,tan4).
+gv_color(x11,thistle).
+gv_color(x11,thistle1).
+gv_color(x11,thistle2).
+gv_color(x11,thistle3).
+gv_color(x11,thistle4).
+gv_color(x11,tomato).
+gv_color(x11,tomato1).
+gv_color(x11,tomato2).
+gv_color(x11,tomato3).
+gv_color(x11,tomato4).
+gv_color(x11,transparent).
+gv_color(x11,turquoise).
+gv_color(x11,turquoise1).
+gv_color(x11,turquoise2).
+gv_color(x11,turquoise3).
+gv_color(x11,turquoise4).
+gv_color(x11,violet).
+gv_color(x11,violetred).
+gv_color(x11,violetred1).
+gv_color(x11,violetred2).
+gv_color(x11,violetred3).
+gv_color(x11,violetred4).
+gv_color(x11,wheat).
+gv_color(x11,wheat1).
+gv_color(x11,wheat2).
+gv_color(x11,wheat3).
+gv_color(x11,wheat4).
+gv_color(x11,white).
+gv_color(x11,whitesmoke).
+gv_color(x11,yellow).
+gv_color(x11,yellow1).
+gv_color(x11,yellow2).
+gv_color(x11,yellow3).
+gv_color(x11,yellow4).
+gv_color(x11,yellowgreen).
+gv_color(svg,aliceblue).
+gv_color(svg,antiquewhite).
+gv_color(svg,aqua).
+gv_color(svg,aquamarine).
+gv_color(svg,azure).
+gv_color(svg,beige).
+gv_color(svg,bisque).
+gv_color(svg,black).
+gv_color(svg,blanchedalmond).
+gv_color(svg,blue).
+gv_color(svg,blueviolet).
+gv_color(svg,brown).
+gv_color(svg,burlywood).
+gv_color(svg,cadetblue).
+gv_color(svg,chartreuse).
+gv_color(svg,chocolate).
+gv_color(svg,coral).
+gv_color(svg,cornflowerblue).
+gv_color(svg,cornsilk).
+gv_color(svg,crimson).
+gv_color(svg,cyan).
+gv_color(svg,darkblue).
+gv_color(svg,darkcyan).
+gv_color(svg,darkgoldenrod).
+gv_color(svg,darkgray).
+gv_color(svg,darkgreen).
+gv_color(svg,darkgrey).
+gv_color(svg,darkkhaki).
+gv_color(svg,darkmagenta).
+gv_color(svg,darkolivegreen).
+gv_color(svg,darkorange).
+gv_color(svg,darkorchid).
+gv_color(svg,darkred).
+gv_color(svg,darksalmon).
+gv_color(svg,darkseagreen).
+gv_color(svg,darkslateblue).
+gv_color(svg,darkslategray).
+gv_color(svg,darkslategrey).
+gv_color(svg,darkturquoise).
+gv_color(svg,darkviolet).
+gv_color(svg,deeppink).
+gv_color(svg,deepskyblue).
+gv_color(svg,dimgray).
+gv_color(svg,dimgrey).
+gv_color(svg,dodgerblue).
+gv_color(svg,firebrick).
+gv_color(svg,floralwhite).
+gv_color(svg,forestgreen).
+gv_color(svg,fuchsia).
+gv_color(svg,gainsboro).
+gv_color(svg,ghostwhite).
+gv_color(svg,gold).
+gv_color(svg,goldenrod).
+gv_color(svg,gray).
+gv_color(svg,grey).
+gv_color(svg,green).
+gv_color(svg,greenyellow).
+gv_color(svg,honeydew).
+gv_color(svg,hotpink).
+gv_color(svg,indianred).
+gv_color(svg,indigo).
+gv_color(svg,ivory).
+gv_color(svg,khaki).
+gv_color(svg,lavender).
+gv_color(svg,lavenderblush).
+gv_color(svg,lawngreen).
+gv_color(svg,lemonchiffon).
+gv_color(svg,lightblue).
+gv_color(svg,lightcoral).
+gv_color(svg,lightcyan).
+gv_color(svg,lightgoldenrodyellow).
+gv_color(svg,lightgray).
+gv_color(svg,lightgreen).
+gv_color(svg,lightgrey).
+gv_color(svg,lightpink).
+gv_color(svg,lightsalmon).
+gv_color(svg,lightseagreen).
+gv_color(svg,lightskyblue).
+gv_color(svg,lightslategray).
+gv_color(svg,lightslategrey).
+gv_color(svg,lightsteelblue).
+gv_color(svg,lightyellow).
+gv_color(svg,lime).
+gv_color(svg,limegreen).
+gv_color(svg,linen).
+gv_color(svg,magenta).
+gv_color(svg,maroon).
+gv_color(svg,mediumaquamarine).
+gv_color(svg,mediumblue).
+gv_color(svg,mediumorchid).
+gv_color(svg,mediumpurple).
+gv_color(svg,mediumseagreen).
+gv_color(svg,mediumslateblue).
+gv_color(svg,mediumspringgreen).
+gv_color(svg,mediumturquoise).
+gv_color(svg,mediumvioletred).
+gv_color(svg,midnightblue).
+gv_color(svg,mintcream).
+gv_color(svg,mistyrose).
+gv_color(svg,moccasin).
+gv_color(svg,navajowhite).
+gv_color(svg,navy).
+gv_color(svg,oldlace).
+gv_color(svg,olive).
+gv_color(svg,olivedrab).
+gv_color(svg,orange).
+gv_color(svg,orangered).
+gv_color(svg,orchid).
+gv_color(svg,palegoldenrod).
+gv_color(svg,palegreen).
+gv_color(svg,paleturquoise).
+gv_color(svg,palevioletred).
+gv_color(svg,papayawhip).
+gv_color(svg,peachpuff).
+gv_color(svg,peru).
+gv_color(svg,pink).
+gv_color(svg,plum).
+gv_color(svg,powderblue).
+gv_color(svg,purple).
+gv_color(svg,red).
+gv_color(svg,rosybrown).
+gv_color(svg,royalblue).
+gv_color(svg,saddlebrown).
+gv_color(svg,salmon).
+gv_color(svg,sandybrown).
+gv_color(svg,seagreen).
+gv_color(svg,seashell).
+gv_color(svg,sienna).
+gv_color(svg,silver).
+gv_color(svg,skyblue).
+gv_color(svg,slateblue).
+gv_color(svg,slategray).
+gv_color(svg,slategrey).
+gv_color(svg,snow).
+gv_color(svg,springgreen).
+gv_color(svg,steelblue).
+gv_color(svg,tan).
+gv_color(svg,teal).
+gv_color(svg,thistle).
+gv_color(svg,tomato).
+gv_color(svg,turquoise).
+gv_color(svg,violet).
+gv_color(svg,wheat).
+gv_color(svg,white).
+gv_color(svg,whitesmoke).
+gv_color(svg,yellow).
+gv_color(svg,yellowgreen).
diff --git a/prolog/gv/gv_color.pl b/prolog/gv/gv_color.pl
new file mode 100644
index 0000000..ac319dc
--- /dev/null
+++ b/prolog/gv/gv_color.pl
@@ -0,0 +1,68 @@
+:- module(
+  gv_color,
+  [
+    gv_color/2, % ?Colorscheme:oneof([svg,x11])
+                % ?Color:atom
+    color//1, % +Color:compound
+    colorList//1 % +Pairs:list(pair(compound,float))
+  ]
+).
+:- ensure_loaded(library('gv/gv_color.data')).
+
+/** <module> GraphViz color
+
+@author Wouter Beek
+@tbd Color value `transparent` is only available in the output formats
+     ps, svg, fig, vmrl, and the bitmap formats.
+@version 2015/08, 2015/10, 2016/02
+*/
+
+:- use_module(library(apply)).
+:- use_module(library(dcg/dcg_ext)).
+:- use_module(library(lists)).
+:- use_module(library(os/file_ext)).
+
+
+
+
+
+% color(+Color:compound)// .
+% A *color* is represented by a compound term of one of the following forms:
+%   1. `rgb(Red:nonneg,Green:nonneg,Blue:nonneg)`
+%   2. `rgba(Red:nonneg,Green:nonneg,Blue:nonneg,Alpha:nonneg)`
+%   3. `hsv(Hue:between(0.0,1.0),Saturation:between(0.0,1.0),Value:between(0.0,1.0))`
+
+color(rgb(Red,Green,Blue)) --> !,
+  "#",
+  #(3, hex_color, [Red,Green,Blue]).
+color(rgbs(Red,Green,Blue,Alpha)) --> !,
+  color(rgb(Red,Green,Blue)),
+  hex_color(Alpha).
+color(hsv(Hue,Saturation,Value)) --> !,
+  #(3, hsv_color, [Hue,Saturation,Value]).
+color(Name) -->
+  {gv_color(_, Name)},
+  atom(Name).
+
+hex_color(I) -->
+  xinteger(I).
+
+hsv_color(D, Head, Tail):-
+  format(codes(Head,Tail), '~2f', [D]).
+
+
+
+%! colorList(+Pairs:list(pair(compound,float)))// .
+
+colorList(L) -->
+  '+'(wc, L).
+
+wc(Color-Float) -->
+  color(Color),
+  (   wc_weight(Float)
+  ;   ""
+  ).
+
+wc_weight(Float) -->
+  ";",
+  float(Float).
diff --git a/prolog/gv/gv_dom.pl b/prolog/gv/gv_dom.pl
new file mode 100644
index 0000000..30c4263
--- /dev/null
+++ b/prolog/gv/gv_dom.pl
@@ -0,0 +1,29 @@
+:- module(
+  gv_dom,
+  [
+    gv_dom/3 % +ExportG, -Dom, +Opts
+  ]
+).
+
+/** <module> GraphViz DOM
+
+@author Wouter Beek
+@version 2015/07, 2016/01
+*/
+
+:- use_module(library(gv/gv_file)).
+:- use_module(library(option)).
+:- use_module(library(svg/svg_ext)).
+
+
+
+
+
+%! gv_dom(+ExportG, -Dom, +Opts) is det.
+
+gv_dom(ExportG, Dom, Opts1):-
+  % Make sure the file type of the output file is SvgDom.
+  merge_options([output(svg)], Opts1, Opts2),
+  graph_viz(ExportG, ToFile, Opts2),
+  svg_dom(ToFile, Dom),
+  delete_file(ToFile).
diff --git a/prolog/gv/gv_file.pl b/prolog/gv/gv_file.pl
new file mode 100644
index 0000000..993fcee
--- /dev/null
+++ b/prolog/gv/gv_file.pl
@@ -0,0 +1,175 @@
+:- module(
+  gv_file,
+  [
+    graph_viz/2, % +ExportG, ?File
+    graph_viz/3  % +ExportG, ?File, +Opts
+  ]
+).
+
+/** <module> GraphViz file
+
+@author Wouter Beek
+@version 2015/07, 2015/10-2015/11, 2016/01
+*/
+
+:- use_module(library(code_ext)).
+:- use_module(library(error)).
+:- use_module(library(gv/gv_graph)).
+:- use_module(library(option)).
+:- use_module(library(os/external_program)).
+:- use_module(library(os/process_ext)).
+:- use_module(library(string_ext)).
+
+:- predicate_options(graph_viz/3, 3, [
+     pass_to(file_to_gv/3, 3)
+   ]).
+:- predicate_options(file_to_gv/3, 3, [
+     method(+oneof([circo,dot,fdp,neato,osage,sfdp,twopi])),
+     output(+atom)
+   ]).
+
+:- dynamic(user:module_uses/2).
+:- multifile(user:module_uses/2).
+
+user:module_uses(gv_file, program(dot)).
+
+
+
+
+
+%! graph_viz(+ExportGraph:compound, ?File:atom) is det.
+%! graph_viz(+ExportGraph:compound, ?File:atom, +Options:list(compound)) is det.
+% Returns a file containing a GraphViz visualization of the given graph.
+%
+% The following options are supported:
+%   * method(+oneof([circo,dot,fdp,neato,osage,sfdp,twopi])
+%     The algorithm used by GraphViz for positioning the tree nodes.
+%     Default is `dot'.
+%     For possible values see gv_method/1.
+%   * output(+atom)`
+%     The file type of the generated GraphViz file.
+%     Default is `pdf`.
+%     For possible values see gv_output_type/1.
+
+graph_viz(ExportG, File):-
+  graph_viz(ExportG, File, []).
+graph_viz(ExportG, File, Opts):-
+  once(phrase(gv_graph(ExportG), Cs)),
+
+  % Be thread-safe.
+  thread_self(Id),
+  string_list_concat(["gv_file",Id], "_", ThreadName),
+  absolute_file_name(ThreadName, TmpFile, [access(write),extensions([dot])]),
+  setup_call_cleanup(
+    open(TmpFile, write, Write, [encoding(utf8)]),
+    with_output_to(Write, put_codes(Cs)),
+    close(Write)
+  ),
+  file_to_gv(TmpFile, File, Opts).
+
+
+%! file_to_gv(
+%!   +InputFile:atom,
+%!   ?OutputFile:atom,
+%!   +Options:list(compound)
+%! ) is det.
+% Converts a GraphViz DOT file to an image file, using a specific
+% visualization method.
+
+file_to_gv(InputFile, OutputFile, Opts):-
+  var(OutputFile), !,
+  option(output(Ext), Opts, pdf),
+  file_name_extension(out, Ext, LocalName),
+  absolute_file_name(LocalName, OutputFile, Opts),
+  file_to_gv(InputFile, OutputFile, Opts).
+file_to_gv(InputFile, OutputFile, Opts):-
+  option(output(dot), Opts), !,
+  (   var(OutputFile)
+  ->  OutputFile = InputFile
+  ;   rename_file(InputFile, OutputFile)
+  ).
+file_to_gv(InputFile, OutputFile, Opts):-
+  % Typecheck for `method` option.
+  option(method(Method), Opts, dot),
+  findall(Method0, gv_method(Method0), Methods),
+  must_be(oneof(Methods), Method),
+
+  % Typecheck for `output` option.
+  option(output(OutputType), Opts, pdf),
+  findall(OutputType0, gv_output_type(OutputType0), OutputTypes),
+  must_be(oneof(OutputTypes), OutputType),
+
+  % Run the GraphViz conversion command in the shell.
+  format(atom(OutputTypeFlag), "-T~a", [OutputType]),
+  format(atom(OutputFileFlag), "-o~a", [OutputFile]),
+  run_process(
+    Method,
+    [OutputTypeFlag,file(InputFile),OutputFileFlag]
+  ).
+
+
+
+
+
+% HELPERS %
+
+gv_method(circo).
+gv_method(dot).
+gv_method(fdp).
+gv_method(neato).
+gv_method(osage).
+gv_method(sfdp).
+gv_method(twopi).
+
+
+gv_output_type(bmp).
+gv_output_type(canon).
+gv_output_type(dot).
+gv_output_type(gv).
+gv_output_type(xdot).
+gv_output_type('xdot1.2').
+gv_output_type('xdot1.4').
+gv_output_type(cgimage).
+gv_output_type(cmap).
+gv_output_type(eps).
+gv_output_type(exr).
+gv_output_type(fig).
+gv_output_type(gd).
+gv_output_type(gd2).
+gv_output_type(gif).
+gv_output_type(gtk).
+gv_output_type(ico).
+gv_output_type(imap).
+gv_output_type(cmapx).
+gv_output_type(imap_np).
+gv_output_type(cmapx_np).
+gv_output_type(ismap).
+gv_output_type(jp2).
+gv_output_type(jpg).
+gv_output_type(jpeg).
+gv_output_type(jpe).
+gv_output_type(pct).
+gv_output_type(pict).
+gv_output_type(pdf).
+gv_output_type(pic).
+gv_output_type(plain).
+gv_output_type('plain-ext').
+gv_output_type(png).
+gv_output_type(pov).
+gv_output_type(ps).
+gv_output_type(ps2).
+gv_output_type(psd).
+gv_output_type(sgi).
+gv_output_type(svg).
+gv_output_type(svgz).
+gv_output_type(tga).
+gv_output_type(tif).
+gv_output_type(tiff).
+gv_output_type(tk).
+gv_output_type(vml).
+gv_output_type(vmlz).
+gv_output_type(vrml).
+gv_output_type(wbmp).
+gv_output_type(webp).
+gv_output_type(xlib).
+gv_output_type(x11).
diff --git a/prolog/gv/gv_graph.pl b/prolog/gv/gv_graph.pl
new file mode 100644
index 0000000..c95c9cc
--- /dev/null
+++ b/prolog/gv/gv_graph.pl
@@ -0,0 +1,252 @@
+:- module(
+  gv_graph,
+  [
+    gv_graph//1 % +Graph:compound
+  ]
+).
+
+/** <module> GraphViz graph
+
+Generates GraphViz graphs in the DOT format based on
+a Prolog representation of a graph.
+
+In GraphViz vertices are called 'nodes'.
+
+---
+
+@author Wouter Beek
+@version 2015/07, 2015/12
+*/
+
+:- use_module(library(apply)).
+:- use_module(library(dcg/dcg_ext)).
+:- use_module(library(gv/gv_graph_comp)).
+:- use_module(library(lists)).
+:- use_module(library(option)).
+
+
+
+
+
+%! gv_graph(+Graph:compound)// is det.
+% Wrapper around gv_graph//2 with no indentation.
+
+gv_graph(G) --> gv_graph(G, 0).
+
+
+%! gv_graph(+Graph:compound, +Indent:nonneg)// is det.
+% The follow graph attributes are supported,
+% beyond the GraphViz attributes for graphs:
+%   * `directed(+boolean)`
+%      Whether the graph is directed (`true`) or undirected (`false`).
+%      Default: `false`.
+%   * `name(+GraphName:atom)`
+%   * `strict(+StrictGraph:boolean)`
+%      This forbids the creation of self-arcs and multi-edges;
+%      they are ignored in the input file.
+%      Only in combinattion with directionality `directed`.
+%
+% ```abnf
+% graph = ["strict"] ("graph" / "digraph") [ID] "{" stmt_list "}"
+% ```
+%
+% `GraphTerm` is a compound term of the following form:
+% ```prolog
+% graph(VertexTerms,RankedVertexTerms,EdgeTerms,GraphAttributes)
+% ```
+%
+% `RankedVertexTerms` is a list of compound terms of the following form:
+% ```prolog
+% RankVertex-ContentVertices
+% ```
+%
+% @tbd Add support for subgraphs (arbitrary nesting).
+% @tbd Add support for escape strings:
+%      http://www.graphviz.org/doc/info/attrs.html#k:escString
+% @tbd Assert attributes that are generic with respect to a subgraph.
+% @tbd Not all vertex and edge properties can be shared it seems (e.g., label).
+
+gv_graph(G1, I) -->
+  {
+    include_ranks(G1, G2),
+    G2 = graph(VTerms,RankedVTerms,ETerms,GAttrs1),
+    shared_attributes(VTerms, SharedVAttrs, NewVTerms),
+    shared_attributes(ETerms, SharedEAttrs, NewETerms),
+    add_default(GAttrs1, overlap(false), GAttrs2),
+    I = 0
+  },
+
+  % The first statement in the GraphViz output.
+  % States that this file represents a graph according to the GraphViz format.
+  tab(I),
+  
+  % Strictness.
+  {select_option(strict(Strict), GAttrs2, GAttrs3, false)},
+  gv_strict(Strict),
+  
+  % Directedness.
+  {select_option(directed(Directed), GAttrs3, GAttrs4, true)},
+  gv_graph_type(Directed),
+  " ",
+  
+  % Graph name.
+  (   {select_option(name(GName), GAttrs4, GAttrs5)}
+  ->  gv_id(GName),
+      " "
+  ;   {GAttrs5 = GAttrs4}
+  ),
+
+  % The body of the DOT file appears between curly braces.
+  "{\n",
+
+  % The following lines are indented.
+  {NewI is I + 1},
+
+  % Attributes that apply to the graph as a whole.
+  gv_generic_attributes_statement(graph, NewI, GAttrs5),
+
+  % Attributes that are the same for all nodes.
+  gv_generic_attributes_statement(node, NewI, SharedVAttrs),
+
+  % Attributes that are the same for all edges.
+  gv_generic_attributes_statement(edge, NewI, SharedEAttrs),
+
+  % Only add a line_feed if some content was already written
+  % and some content is about to be written.
+  (   % Succeeds if no content was written.
+      {(GAttrs5 == [], SharedVAttrs == [], SharedEAttrs == [])}
+  ->  ""
+  ;   % Succeeds if no content is about to be written.
+      {(NewVTerms == [], RankedVTerms == [])}
+  ->  ""
+  ;   "\n"
+  ),
+
+  % The list of GraphViz nodes.
+  gv_node_statements(NewI, NewVTerms),
+  ({NewVTerms == []} -> "" ; "\n"),
+
+  % The ranked GraphViz nodes (displayed at the same height).
+  gv_ranked_node_collections(NewI, RankedVTerms),
+  ({RankedVTerms == []} -> "" ; "\n"),
+
+  {
+    findall(
+      edge(FromId,ToId,[]),
+      (
+        nth0(J1, RankedVTerms, rank(vertex(FromId,_),_)),
+        nth0(J2, RankedVTerms, rank(vertex(ToId,_),_)),
+        % We assume that the rank vertices are nicely ordered.
+        succ(J1, J2)
+      ),
+      RankEdges
+    )
+  },
+
+  % The rank edges.
+  gv_edge_statements(NewI, Directed, RankEdges),
+
+  % The non-rank edges.
+  gv_edge_statements(NewI, Directed, NewETerms),
+  
+  % Note that we do not include a line_feed here.
+
+  % We want to indent the closing curly brace.
+  tab(I),
+  "}\n".
+
+
+
+%! gv_edge_statements(
+%!   +Indent:nonneg,
+%!   +Directed:boolean,
+%!   +Statements:list(compound)
+%! )// is det.
+
+gv_edge_statements(I, Dir, L) --> *(gv_edge_statement(I, Dir), L).
+
+
+
+%! gv_edge_statements(+Indent:nonneg, +Statements:list(compound))// is det.
+
+gv_node_statements(I, L) --> *(gv_node_statement(I), L).
+
+
+
+%! gv_ranked_node_collections(+Indent:nonneg, +Collections:list)// is det.
+
+gv_ranked_node_collections(I, L) --> *(gv_ranked_node_collection(I), L).
+
+
+
+
+
+% HELPERS %
+
+%! add_default_option(
+%!   +Options:list(compound),
+%!   +Default:compound,
+%!   -NewOptions:list(compound)
+%! ) is det.
+
+add_default(L1, Opt, L2):-
+  Opt =.. [N,_Value],
+  Opt0 =.. [N,_FreshVar],
+  (   option(Opt0, L1)
+  ->  L2 = L1
+  ;   L2 = [Opt|L1]
+  ).
+
+
+
+%! gv_graph_type(+Directed:boolean)// is det.
+% The type of graph that is represented.
+
+gv_graph_type(false) --> !, "graph".
+gv_graph_type(true)  -->    "digraph".
+
+
+
+%! gv_strict(+Strict:boolean)// is det.
+% The keyword denoting that the graph is strict, i.e., has no self-arcs and
+% no multi-edges.
+% This only applies to directed graphs.
+
+gv_strict(false) --> !, "".
+gv_strict(true)  -->    "strict ".
+
+
+
+%! invlude_ranges(+Graph:compound, -GraphWithRanks:compound) is det.
+% Ensures that there is a ranks components in
+% the graph-denoting compound term.
+
+include_ranks(graph(Vs,Rs,Es,L), graph(Vs,Rs,Es,L)):- !.
+include_ranks(graph(Vs,Es,L), graph(Vs,[],Es,L)).
+
+
+
+%! shared_attributes(
+%!   +Terms:list(compound),
+%!   -SharedAttributes:list(compound),
+%!   -NewTerms:list(compound)
+%! ) is det.
+
+shared_attributes(Ts1, Shared, Ts2):-
+  maplist(term_to_attrs, Ts1, L1),
+  extract_shared(L1, Shared),
+  maplist(remove_shared_attributes(Shared), L1, L2),
+  maplist(term_change_attrs, Ts1, L2, Ts2).
+
+term_to_attrs(edge(_,_,A), A).
+term_to_attrs(vertex(_,A), A).
+
+extract_shared([], []):- !.
+extract_shared(Argss, Shared):-
+  ord_intersection(Argss, Shared).
+
+remove_shared_attributes(Shared, Args1, Args2):-
+  ord_subtract(Args1, Shared, Args2).
+
+term_change_attrs(edge(From,To,_), A, edge(From,To,A)).
+term_change_attrs(vertex(Id,_), A, vertex(Id,A)).
diff --git a/prolog/gv/gv_graph_comp.pl b/prolog/gv/gv_graph_comp.pl
new file mode 100644
index 0000000..30ea9c5
--- /dev/null
+++ b/prolog/gv/gv_graph_comp.pl
@@ -0,0 +1,309 @@
+:- module(
+  gv_graph_comp,
+  [
+    gv_edge_statement//3, % +Indent:nonneg
+                          % +Directed:boolean
+                          % +Edge:compound
+    gv_generic_attributes_statement//3, % +Kind:oneof([edge,graph,node])
+                                        % +Indent:nonneg
+                                        % +CategoryAttributes:list(compound)
+    gv_id//1, % +Name:compound
+    gv_node_statement//2, % +Indent:nonneg
+                          % +Vertex:compound
+    gv_ranked_node_collection//2 % +Indent:nonneg
+                                 % +Rank
+  ]
+).
+
+/** <module> GraphViz graph components
+
+```abnf
+attr_list = "[" [a_list] "]" [attr_list]
+a_list = ID "=" ID [","] [a_list]
+```
+
+@author Wouter Beek
+@see http://www.graphviz.org/content/dot-language
+@version 2015/07-2015/08, 2015/10-2016/01
+*/
+
+:- use_module(library(apply)).
+:- use_module(library(dcg/dcg_ext)).
+:- use_module(library(gv/gv_attrs)).
+:- use_module(library(gv/gv_html)).
+:- use_module(library(error)).
+:- use_module(library(lists)).
+:- use_module(library(ordsets)).
+
+
+
+
+
+%! gv_edge_statement(
+%!   +Indent:nonneg,
+%!   +Directed:boolean,
+%!   +Edge:compound
+%! )// is det.
+% A GraphViz statement describing an edge.
+%
+% @arg Indent The indentation level at which the edge statement is written.
+% @arg Directed Whether the graph is directed or not.
+% @arg GraphAttributes The attributes of the graph. Some of these attributes
+%      may be used in the edge statement (e.g., the colorscheme).
+% @arg Edge A compound term in the GIFormat, representing an edge.
+%
+% @tbd Instead of gv_node_id//1 we could have a gv_subgraph//1
+%      at the from and/or to location.
+% @tbd Add support for multiple, consecutive occurrences of gv_edge_rhs//2.
+
+gv_edge_statement(I, Dir, edge(From,To,Attrs)) -->
+  tab(I),
+
+  gv_node_id(From), " ",
+  gv_edge_operator(Dir), " ",
+  gv_node_id(To), " ",
+
+  % We want `colorscheme/1` from the edges and
+  % `directionality/1` from the graph.
+  gv_attrs(edge, Attrs),
+  "\n".
+
+%! gv_edge_operator(+Directed:boolean)// .
+% The binary edge operator between two vertices.
+% The operator that is used depends on whether the graph is directed or
+% undirected.
+%
+% @arg Directed Whether an edge is directed (operator `->`) or
+%      undirected (operator `--`).
+
+gv_edge_operator(Dir) -->
+  {must_be(boolean, Dir)},
+  ({Dir == false} -> "--" ; {Dir == true} -> "->").
+
+
+
+%! gv_generic_attributes_statement(
+%!   +Kind:oneof([edge,graph,node]),
+%!   +Indent:nonneg,
+%!   +CategoryAttrs
+%! )//
+% A GraphViz statement describing generic attributes for a category of items.
+%
+% @arg Kind The category of items for to the attributes apply.
+%      Possible values: `edge`, `graph`, and `node`.
+% @arg Indent An integer indicating the number of tabs.
+% @arg GraphAttributes A list of name-value pairs.
+% @arg CategoryAttributes A list of name-value pairs.
+%
+% ```
+% attr_stmt = (graph / node / edge) attr_list
+% ```
+
+gv_generic_attributes_statement(_, _, []) --> !, "".
+gv_generic_attributes_statement(Kind, I, Attrs) -->
+  tab(I),
+  gv_kind(Kind), " ",
+  gv_attrs(Kind, Attrs),
+  "\n", !.
+
+
+
+%! gv_kind(+Kind:oneof([edge,graph,node]))// .
+
+gv_kind(Kind) --> {must_be(oneof([edge,graph,node]), Kind)}, atom(Kind).
+
+
+
+%! gv_node_statement(+Indent:nonneg, +Vertex:compound)// is det.
+% A GraphViz statement describing a vertex (GraphViz calls vertices 'nodes').
+
+gv_node_statement(I, vertex(Id,Attrs)) -->
+  tab(I),
+  gv_node_id(Id),
+  gv_attrs(node, Attrs),
+  "\n".
+
+
+
+%! gv_ranked_node_collection(+Indent:nonneg, Rank:pair)// is det.
+
+gv_ranked_node_collection(I, RankVTerm-VTerms) -->
+  tab(I),
+  "{\n",
+
+  % The rank attribute.
+  {NewI is I + 1},
+  tab(NewI),
+  gv_attr(subgraph, rank(same)),
+  "\n",
+
+  % Vertice statements.
+  *(gv_node_statement(NewI), [RankVTerm|VTerms]),
+
+  % We want to indent the closing curly brace.
+  tab(I),
+  "\n}".
+
+
+
+
+
+% HELPERS %
+
+%! gv_attrs(
+%!   +Kind:oneof([edge,graph,node]),
+%!   +Attributes:list(compound)
+%! )// is det.
+
+gv_attrs(Kind, L) --> "[", *(gv_attr(Kind), L), "]".
+
+
+%! gv_attr(+Context:oneof([edge,graph,node]), +Attribute:compound)// is det.
+% A single GraphViz attribute.
+% We assume that the attribute has already been validated.
+
+gv_attr(Context, Attr) -->
+  {Attr =.. [N,V]},
+  gv_id(N), "=", gv_attr_value(Context, N=V), ";".
+
+
+
+%! gv_id(+Id:compound)// is det.
+% Parse a GraphViz identifier.
+% There are 4 variants:
+%   1. Any string of alphabetic (`[a-zA-Z'200-'377]`) characters,
+%      underscores (`_`) or digits (`[0-9]`), not beginning with a digit.
+%   2. A numeral `[-]?(.[0-9]+ | [0-9]+(.[0-9]*)? )`.
+%   3. Any double-quoted string (`"..."`) possibly containing
+%      escaped quotes (`\"`).
+%      In quoted strings in DOT, the only escaped character is
+%      double-quote (`"`). That is, in quoted strings, the dyad `\"`
+%      is converted to `"`. All other characters are left unchanged.
+%      In particular, `\\` remains `\\`.
+%      Layout engines may apply additional escape sequences.
+%      Represented by a Prolog term of the form `double_quoted_string(ATOM)`.
+%   4. An HTML string (`<...>`).
+%      Represented by a Prolog term of the form `html_like_label(COMPOUND)`.
+%
+% @tbd Add support for HTML-like labels:
+%      http://www.graphviz.org/doc/info/shapes.html#html
+%      This requires an XML grammar!
+
+% HTML strings (assumed to be the same as HTML-like labels).
+gv_id(html_like_label(Content)) --> !,
+  gv_html_like_label(Content).
+% Double-quoted strings.
+% The quotes are already part of the given atom.
+gv_id(double_quoted_string(Atom)) --> !,
+  "\"", atom(Atom), "\"".
+% Numerals.
+gv_id(N) -->
+  {number(N)}, !,
+  {number_codes(N, Cs)},
+  Cs.
+% Alpha-numeric strings.
+gv_id(Atom) -->
+  {atom_codes(Atom, [H|T])},
+  gv_id_first(H),
+  gv_id_rest(T), !,
+  % Variant 1 identifiers should not be (case-variants of) a
+  % GraphViz keyword.
+  {\+ gv_keyword([H|T])}.
+
+
+%! gv_id_first(+First:code)// is det.
+% Generates the first character of a GraphViz identifier.
+
+gv_id_first(C)   --> alpha(C), !.
+gv_id_first(0'_) --> "_".
+
+
+%! gv_id_rest(+NonFirst:code)// is det.
+% Generates a non-first character of a GraphViz identifier.
+
+gv_id_rest([H|T])   --> alphadigit(H), !, gv_id_rest(T).
+gv_id_rest([0'_|T]) --> "_",           !, gv_id_rest(T).
+gv_id_rest([])      --> "".
+
+
+
+%! gv_keyword(+Codes:list(code)) is semidet.
+% Succeeds if the given codes for a GraphViz reserved keyword.
+
+gv_keyword(Cs):-
+  % Obviously, the keywords do not occur on the difference list input.
+  % So we must use phrase/[2,3].
+  phrase(gv_keyword, Cs).
+
+
+%! gv_keyword// .
+% GraphViz has reserved keywords that cannot be used as identifiers.
+% GraphViz keywords are case-insensitive.
+
+gv_keyword --> "digraph".
+gv_keyword --> "edge".
+gv_keyword --> "graph".
+gv_keyword --> "node".
+gv_keyword --> "strict".
+gv_keyword --> "subgraph".
+
+
+
+%! gv_node_id(+NodeId:compound)// .
+% GraphViz node identifiers can be of the following two types:
+%   1. A GraphViz identifier, see gv_id//1.
+%   2. A GraphViz identifier plus a GraphViz port indicator, see gv_port//0.
+%
+% @tbd Add support for GraphViz port indicators
+%      inside GraphViz node identifiers.
+
+gv_node_id(Id) --> gv_id(Id), !.
+%gv_node_id(_) --> gv_id(_), gv_port.
+gv_node_id(Id) --> {type_error(gv_node_id, Id)}.
+
+
+
+%! gv_numeral(-Number)// is det.
+% ```bnf
+% ('-')? ( '.' [0-9]+ | [0-9]+ ( '.' [0-9]* )? )
+% ```
+
+gv_numeral(N) -->
+  ("-" -> {Sg = -1} ; {Sg = 1}),
+  (   "."
+  ->  {I = 0}, +(digit, Ds), {pos_frac(Ds, Frac)}
+  ;   +(digit, Ds1), {pos_sum(Ds1, I)},
+      ("." -> *(digit, Ds2), {pos_frac(Ds2, Frac)} ; {Frac = 0.0})
+  ),
+  {N is Sg * (I + Frac)}.
+
+
+
+%! gv_port// is det.
+
+gv_port --> gv_port_location, (gv_port_angle ; "").
+gv_port --> gv_port_angle, (gv_port_location ; "").
+gv_port --> ":", gv_compass_pt(_).
+
+gv_port_angle --> "@", gv_id(_).
+
+gv_port_location --> ":", gv_id(_).
+gv_port_location --> ":[", gv_id(_), ",", gv_id(_), "]".
+
+
+
+%! gv_compass_pt(+Direction:oneof(['_',c,e,n,ne,nw,s,se,sw,w]))// .
+% ```
+% compass_pt : ( n | ne | e | se | s | sw | w | nw | c | _ )
+% ```
+
+gv_compass_pt('_') --> "_".
+gv_compass_pt(c)   --> "c".
+gv_compass_pt(e)   --> "e".
+gv_compass_pt(n)   --> "n".
+gv_compass_pt(ne)  --> "ne".
+gv_compass_pt(nw)  --> "nw".
+gv_compass_pt(s)   --> "s".
+gv_compass_pt(se)  --> "se".
+gv_compass_pt(sw)  --> "sw".
+gv_compass_pt(w)   --> "w".
diff --git a/prolog/gv/gv_html.pl b/prolog/gv/gv_html.pl
new file mode 100644
index 0000000..b5c1368
--- /dev/null
+++ b/prolog/gv/gv_html.pl
@@ -0,0 +1,255 @@
+:- module(
+  gv_html,
+  [
+    gv_html_like_label//1 % +Content:compound
+  ]
+).
+
+/** <module> GraphViz: HTML-like labels
+
+Grammar taken from the GraphViz Web site:
+
+```
+label :   text
+        | table
+text :   textitem
+       | text textitem
+textitem :   string
+           | <BR/>
+           | <FONT> text </FONT>
+           | <I> text </I>
+           | <B> text </B>
+           | <U> text </U>
+           | <O> text </O>
+           | <SUB> text </SUB>
+           | <SUP> text </SUP>
+           | <S> text </S>
+table : [ <FONT> ] <TABLE> rows </TABLE> [ </FONT> ]
+rows :   row
+       | rows row
+       | rows <HR/> row
+row: <TR> cells </TR>
+cells :   cell
+        | cells cell
+        | cells <VR/> cell
+cell:   <TD> label </TD>
+      | <TD> <IMG/> </TD>
+```
+
+---
+
+@author Wouter Beek
+@see http://www.graphviz.org/content/node-shapes#html
+@version 2015/07, 2015/12
+*/
+
+:- use_module(library(dcg/dcg_ext)).
+:- use_module(library(html/html_dcg)).
+
+
+
+
+
+%! gv_html_like_label(+Content:compound)// is det.
+
+gv_html_like_label(Content) --> "<", label(Content), ">".
+
+
+
+%! cell(+Contents:compound)// is det.
+% Supported attributes for `TD`:
+%   - `ALIGN="CENTER|LEFT|RIGHT|TEXT"`
+%   - `BALIGN="CENTER|LEFT|RIGHT"`
+%   - `BGCOLOR="color"`
+%   - `BORDER="value"`
+%   - `CELLPADDING="value"`
+%   - `CELLSPACING="value"`
+%   - `COLOR="color"`
+%   - `COLSPAN="value"`
+%   - `FIXEDSIZE="FALSE|TRUE"`
+%   - `GRADIENTANGLE="value"`
+%   - `HEIGHT="value"`
+%   - `HREF="value"`
+%   - `ID="value"`
+%   - `PORT="portName"`
+%   - `ROWSPAN="value"`
+%   - `SIDES="value"`
+%   - `STYLE="value"`
+%   - `TARGET="value"`
+%   - `TITLE="value"`
+%   - `TOOLTIP="value"`
+%   - `VALIGN="MIDDLE|BOTTOM|TOP"`
+%   - `WIDTH="value"`
+%
+% Supported attributes for `IMG`:
+%   - `SCALE="FALSE|TRUE|WIDTH|HEIGHT|BOTH"`
+%   - `SRC="value"`
+
+cell(td(Contents)) --> !,
+  cell(td([],Contents)).
+cell(td(Attrs1,Image)) -->
+  {(Image =.. [img,Attrs2] -> true ; Image == img -> Attrs2 = [])}, !,
+  html_element(td, Attrs1, html_element(img,Attrs2)).
+cell(td(Attrs,Contents)) -->
+  html_element(td, Attrs, label(Contents)).
+
+
+
+%! cells(+Contents:list(compound))// is det.
+
+cells([H,vr|T]) --> !, cell(H), html_element(vr), cells(T).
+cells([H|T])    --> !, cell(H), cells(T).
+cells([])       --> "".
+
+
+
+%! label(+Content:compound)// is det.
+% GraphViz HTML-like label.
+
+label(Content) --> table(Content), !.
+label(Content) --> text(Content).
+
+
+
+%! row(+Contents:compound)// is det.
+
+row(tr(Contents)) --> html_element(tr, [], cells(Contents)).
+
+
+
+%! rows(+Contents:list)// is det.
+
+rows([hr|T]) --> !, html_element(hr), rows(T).
+rows([H|T]) --> row(H), !, rows(T).
+rows([]) --> "".
+
+
+
+%! table(+Contents:compound)// is det.
+% ```
+% table : [ <FONT> ] <TABLE> rows </TABLE> [ </FONT> ]
+% ```
+%
+% Supported attributes for `TABLE`:
+%   - `ALIGN="CENTER|LEFT|RIGHT"`
+%   - `BGCOLOR="color"`
+%   - `BORDER="value"`
+%   - `CELLBORDER="value"`
+%   - `CELLPADDING="value"`
+%   - `CELLSPACING="value"`
+%   - `COLOR="color"`
+%   - `COLUMNS="value"`
+%   - `FIXEDSIZE="FALSE|TRUE"`
+%   - `GRADIENTANGLE="value"`
+%   - `HEIGHT="value"`
+%   - `HREF="value"`
+%   - `ID="value"`
+%   - `PORT="portName"`
+%   - `ROWS="value"`
+%   - `SIDES="value"`
+%   - `STYLE="value"`
+%   - `TARGET="value"`
+%   - `TITLE="value"`
+%   - `TOOLTIP="value"`
+%   - `VALIGN="MIDDLE|BOTTOM|TOP"`
+%   - `WIDTH="value"`
+%
+% Supported attributes for `FONT`:
+%  - `COLOR="color"`
+%    Sets the color of the font within the scope of `<FONT>...</FONT>`,
+%     or the border color of the table or cell within the scope of
+%     `<TABLE>...</TABLE>`, or `<TD>...</TD>`.
+%    This color can be overridden by a `COLOR` attribute in descendents.
+%    By default, the font color is determined by the `fontcolor` attribute
+%     of the corresponding node, edge or graph, and the border color is
+%     determined by the color attribute of the corresponding node, edge or
+%     graph.
+%   - `FACE="fontname"`
+%   - `POINT-SIZE="value"`
+
+table(table(Contents)) --> !,
+  table(table([],Contents)).
+table(table(Attrs,Contents)) --> !,
+  html_element(table, Attrs, rows(Contents)).
+table(font(Table)) --> !,
+  table(font([],Table)).
+table(font(Attrs1,Table)) -->
+  {(  Table =.. [table,Attrs2,Contents]
+  ->  true
+  ;   Table =.. [table,Contents]
+  ->  Attrs2 = []
+  )},
+  html_element(font, Attrs1, table(table(Attrs2,Contents))).
+
+
+
+%! text(+Contents:list)// .
+% ```
+% text :   textitem
+%        | text textitem
+% ```
+
+text(Contents) --> {is_list(Contents)}, !, '+'(textitem, Contents).
+text(Content)  --> text([Content]).
+
+
+
+%! textitem(+Content:compound)// .
+% ```
+% textitem :   string
+%            | <BR/>
+%            | <FONT> text </FONT>
+%            | <I> text </I>
+%            | <B> text </B>
+%            | <U> text </U>
+%            | <O> text </O>
+%            | <SUB> text </SUB>
+%            | <SUP> text </SUP>
+%            | <S> text </S>
+% ```
+%
+% Supported attributes for BR:
+%   * `ALIGN="CENTER|LEFT|RIGHT"`
+%
+% Supported attributes for FONT:
+%   * COLOR="color"
+%   * FACE="fontname"
+%   * POINT-SIZE="value"
+
+textitem(br(Attrs)) --> !,
+  html_element(br, Attrs).
+% Compound term: parser.
+textitem(Compound) -->
+  {var(Compound)}, !,
+  html_element(Name, _, text(Content)),
+  {
+    supported_html_element(Name),
+    Compound =.. [Name,Content]
+  }.
+% Compound term: generator.
+textitem(Compound) -->
+  {
+    Compound =.. [Name,Content], !,
+    supported_html_element(Name)
+  },
+  html_element(Name, _, text(Content)).
+textitem(String) -->
+  html_string(String).
+
+
+
+
+
+% HELPERS %
+
+%! supported_html_element(+Name:atom) is semidet.
+%! supported_html_element(-Name:atom) is multi.
+
+supported_html_element(b).
+supported_html_element(font).
+supported_html_element(i).
+supported_html_element(o).
+supported_html_element(s).
+supported_html_element(sub).
+supported_html_element(sup).
+supported_html_element(u).
diff --git a/prolog/tree/tree_viz.pl b/prolog/tree/tree_viz.pl
new file mode 100644
index 0000000..4382aa6
--- /dev/null
+++ b/prolog/tree/tree_viz.pl
@@ -0,0 +1,65 @@
+:- module(
+  tree_viz,
+  [
+    tree_export_graph/2, % +Tree, ExportG
+    tree_export_graph/3, % +Tree, ExportG, +Opts
+    tree_viz/2,          % +Tree, ?File
+    tree_viz/3           % +Tree, ?File, +Opts
+  ]
+).
+
+/** <module> Tree visualization
+
+Export trees to GraphViz.
+
+@author Wouter Beek
+@version 2016/01-2016/02
+*/
+
+:- use_module(library(graph/build_export_graph)).
+:- use_module(library(gv/gv_file)).
+:- use_module(library(ordsets)).
+:- use_module(library(tree/l_tree)).
+:- use_module(library(tree/s_tree)).
+
+:- predicate_options(tree_export_graph/3, 3, [
+     pass_to(build_export_graph/4, 4)
+   ]).
+:- predicate_options(tree_viz/3, 3, [
+     pass_to(graph_viz/3, 3),
+     pass_to(tree_to_graph/3, 3)
+   ]).
+
+
+
+
+
+%! tree_export_graph(+Tree, -ExportG) is det.
+%! tree_export_graph(+Tree, -ExportG, +Opts) is det.
+% Opts are passed to build_export_graph/4.
+
+tree_export_graph(Tree, ExportG) :-
+  tree_export_graph(Tree, ExportG, []).
+
+tree_export_graph(Tree, ExportG, Opts) :-
+  (   is_s_tree(Tree)
+  ->  tree_to_graph(Tree, G)
+  ;   is_l_tree(Tree)
+  ->  l_tree_to_graph(Tree, G)
+  ),
+  build_export_graph(G, ExportG, Opts).
+
+
+
+%! tree_viz(+Tree, ?File) is det.
+%! tree_viz(+Tree, ?File, +Opts) is det.
+% Stores the given tree term into a GraphViz file.
+%
+% Options are passed to export_graph_to_gv_file/3, tree_to_graph/3.
+
+tree_viz(Tree, File) :-
+  tree_viz(Tree, File, []).
+
+tree_viz(Tree, File, Opts) :-
+  tree_export_graph(Tree, ExportG, Opts),
+  graph_viz(ExportG, File, Opts).
diff --git a/test/gv_attrs.log b/test/gv_attrs.log
new file mode 100644
index 0000000..a9c6010
--- /dev/null
+++ b/test/gv_attrs.log
@@ -0,0 +1,169 @@
+assert(gv_attr('Damping',[graph],[double],'0.99','0.0','neato only')).
+assert(gv_attr('K',[cluster,graph],[double],'0.3','0','sfdp, fdp only')).
+assert(gv_attr('URL',[cluster,edge,graph,node],[escString],_,'','svg, postscript, map only')).
+assert(gv_attr('_background',[graph],[string],_,'','')).
+assert(gv_attr(area,[cluster,node],[double],'1.0','>0','patchwork only')).
+assert(gv_attr(arrowhead,[edge],[arrowType],normal,'','')).
+assert(gv_attr(arrowsize,[edge],[double],'1.0','0.0','')).
+assert(gv_attr(arrowtail,[edge],[arrowType],normal,'','')).
+assert(gv_attr(bb,[graph],[rect],'','','write only')).
+assert(gv_attr(bgcolor,[cluster,graph],[color,colorList],_,'','')).
+assert(gv_attr(center,[graph],[bool],false,'','')).
+assert(gv_attr(charset,[graph],[string],'"UTF-8"','','')).
+assert(gv_attr(clusterrank,[graph],[clusterMode],local,'','dot only')).
+assert(gv_attr(color,[cluster,edge,node],[color,colorList],black,'','')).
+assert(gv_attr(colorscheme,[cluster,edge,graph,node],[string],'','','')).
+assert(gv_attr(comment,[edge,graph,node],[string],'','','')).
+assert(gv_attr(compound,[graph],[bool],false,'','dot only')).
+assert(gv_attr(concentrate,[graph],[bool],false,'','')).
+assert(gv_attr(constraint,[edge],[bool],true,'','dot only')).
+assert(gv_attr(decorate,[edge],[bool],false,'','')).
+assert(gv_attr(defaultdist,[graph],[double],'1+(avg. len)*sqrt(|V|)',epsilon,'neato only')).
+assert(gv_attr(dim,[graph],[int],'2','2','sfdp, fdp, neato only')).
+assert(gv_attr(dimen,[graph],[int],'2','2','sfdp, fdp, neato only')).
+assert(gv_attr(dir,[edge],[dirType],'forward(directed)none(undirected)','','')).
+assert(gv_attr(diredgeconstraints,[graph],[string,bool],false,'','neato only')).
+assert(gv_attr(distortion,[node],[double],'0.0','-100.0','')).
+assert(gv_attr(dpi,[graph],[double],'96.00.0','','svg, bitmap output only')).
+assert(gv_attr(edgeURL,[edge],[escString],'','','svg, map only')).
+assert(gv_attr(edgehref,[edge],[escString],'','','svg, map only')).
+assert(gv_attr(edgetarget,[edge],[escString],_,'','svg, map only')).
+assert(gv_attr(edgetooltip,[edge],[escString],'','','svg, cmap only')).
+assert(gv_attr(epsilon,[graph],[double],'.0001 * # nodes(mode == KK).0001(mode == major)','','neato only')).
+assert(gv_attr(esep,[graph],[addDouble,addPoint],'+3','','not dot')).
+assert(gv_attr(fillcolor,[cluster,edge,node],[color,colorList],'lightgrey(nodes)black(clusters)','','')).
+assert(gv_attr(fixedsize,[node],[bool,string],false,'','')).
+assert(gv_attr(fontcolor,[cluster,edge,graph,node],[color],black,'','')).
+assert(gv_attr(fontname,[cluster,edge,graph,node],[string],'"Times-Roman"','','')).
+assert(gv_attr(fontnames,[graph],[string],'','','svg only')).
+assert(gv_attr(fontpath,[graph],[string],'system-dependent','','')).
+assert(gv_attr(fontsize,[cluster,edge,graph,node],[double],'14.0','1.0','')).
+assert(gv_attr(forcelabels,[graph],[bool],true,'','')).
+assert(gv_attr(gradientangle,[cluster,graph,node],[int],'','','')).
+assert(gv_attr(group,[node],[string],'','','dot only')).
+assert(gv_attr(headURL,[edge],[escString],'','','svg, map only')).
+assert(gv_attr(head_lp,[edge],[point],'','','write only')).
+assert(gv_attr(headclip,[edge],[bool],true,'','')).
+assert(gv_attr(headhref,[edge],[escString],'','','svg, map only')).
+assert(gv_attr(headlabel,[edge],[lblString],'','','')).
+assert(gv_attr(headport,[edge],[portPos],center,'','')).
+assert(gv_attr(headtarget,[edge],[escString],_,'','svg, map only')).
+assert(gv_attr(headtooltip,[edge],[escString],'','','svg, cmap only')).
+assert(gv_attr(height,[node],[double],'0.5','0.02','')).
+assert(gv_attr(href,[cluster,edge,graph,node],[escString],'','','svg, postscript, map only')).
+assert(gv_attr(id,[cluster,edge,graph,node],[escString],'','','svg, postscript, map only')).
+assert(gv_attr(image,[node],[string],'','','')).
+assert(gv_attr(imagepath,[graph],[string],'','','')).
+assert(gv_attr(imagescale,[node],[bool,string],false,'','')).
+assert(gv_attr(inputscale,[graph],[double],_,'','fdp, neato only')).
+assert(gv_attr(label,[cluster,edge,graph,node],[lblString],'"\\N" (nodes)"" (otherwise)','','')).
+assert(gv_attr(labelURL,[edge],[escString],'','','svg, map only')).
+assert(gv_attr(label_scheme,[graph],[int],'0','0','sfdp only')).
+assert(gv_attr(labelangle,[edge],[double],'-25.0','-180.0','')).
+assert(gv_attr(labeldistance,[edge],[double],'1.0','0.0','')).
+assert(gv_attr(labelfloat,[edge],[bool],false,'','')).
+assert(gv_attr(labelfontcolor,[edge],[color],black,'','')).
+assert(gv_attr(labelfontname,[edge],[string],'"Times-Roman"','','')).
+assert(gv_attr(labelfontsize,[edge],[double],'14.0','1.0','')).
+assert(gv_attr(labelhref,[edge],[escString],'','','svg, map only')).
+assert(gv_attr(labeljust,[cluster,graph],[string],'"c"','','')).
+assert(gv_attr(labelloc,[cluster,graph,node],[string],'"t"(clusters)"b"(root graphs)"c"(nodes)','','')).
+assert(gv_attr(labeltarget,[edge],[escString],_,'','svg, map only')).
+assert(gv_attr(labeltooltip,[edge],[escString],'','','svg, cmap only')).
+assert(gv_attr(landscape,[graph],[bool],false,'','')).
+assert(gv_attr(layer,[cluster,edge,node],[layerRange],'','','')).
+assert(gv_attr(layerlistsep,[graph],[string],'","','','')).
+assert(gv_attr(layers,[graph],[layerList],'','','')).
+assert(gv_attr(layerselect,[graph],[layerRange],'','','')).
+assert(gv_attr(layersep,[graph],[string],'" :\\t"','','')).
+assert(gv_attr(layout,[graph],[string],'','','')).
+assert(gv_attr(len,[edge],[double],'1.0(neato)0.3(fdp)','','fdp, neato only')).
+assert(gv_attr(levels,[graph],[int],'MAXINT','0.0','sfdp only')).
+assert(gv_attr(levelsgap,[graph],[double],'0.0','','neato only')).
+assert(gv_attr(lhead,[edge],[string],'','','dot only')).
+assert(gv_attr(lheight,[cluster,graph],[double],'','','write only')).
+assert(gv_attr(lp,[cluster,edge,graph],[point],'','','write only')).
+assert(gv_attr(ltail,[edge],[string],'','','dot only')).
+assert(gv_attr(lwidth,[cluster,graph],[double],'','','write only')).
+assert(gv_attr(margin,[cluster,graph,node],[double,point],'<device-dependent>','','')).
+assert(gv_attr(maxiter,[graph],[int],'100 * # nodes(mode == KK)200(mode == major)600(fdp)','','fdp, neato only')).
+assert(gv_attr(mclimit,[graph],[double],'1.0','','dot only')).
+assert(gv_attr(mindist,[graph],[double],'1.0','0.0','circo only')).
+assert(gv_attr(minlen,[edge],[int],'1','0','dot only')).
+assert(gv_attr(mode,[graph],[string],major,'','neato only')).
+assert(gv_attr(model,[graph],[string],shortpath,'','neato only')).
+assert(gv_attr(mosek,[graph],[bool],false,'','neato only')).
+assert(gv_attr(nodesep,[graph],[double],'0.25','0.02','')).
+assert(gv_attr(nojustify,[cluster,edge,graph,node],[bool],false,'','')).
+assert(gv_attr(normalize,[graph],[double,bool],false,'','not dot')).
+assert(gv_attr(notranslate,[graph],[bool],false,'','neato only')).
+assert(gv_attr('nslimit nslimit1',[graph],[double],'','','dot only')).
+assert(gv_attr(ordering,[graph,node],[string],'','','dot only')).
+assert(gv_attr(orientation,[node],[double],'0.0','360.0','')).
+assert(gv_attr(orientation,[graph],[string],'','','')).
+assert(gv_attr(outputorder,[graph],[outputMode],breadthfirst,'','')).
+assert(gv_attr(overlap,[graph],[string,bool],true,'','not dot')).
+assert(gv_attr(overlap_scaling,[graph],[double],'-4','-1.0e10','prism only')).
+assert(gv_attr(overlap_shrink,[graph],[bool],true,'','prism only')).
+assert(gv_attr(pack,[graph],[bool,int],false,'','')).
+assert(gv_attr(packmode,[graph],[packMode],node,'','')).
+assert(gv_attr(pad,[graph],[double,point],'0.0555 (4 points)','','')).
+assert(gv_attr(page,[graph],[double,point],'','','')).
+assert(gv_attr(pagedir,[graph],[pagedir],'BL','','')).
+assert(gv_attr(pencolor,[cluster],[color],black,'','')).
+assert(gv_attr(penwidth,[cluster,edge,node],[double],'1.0','0.0','')).
+assert(gv_attr(peripheries,[cluster,node],[int],'shape default(nodes)1(clusters)','0','')).
+assert(gv_attr(pin,[node],[bool],false,'','fdp, neato only')).
+assert(gv_attr(pos,[edge,node],[point,splineType],'','','')).
+assert(gv_attr(quadtree,[graph],[quadType,bool],normal,'','sfdp only')).
+assert(gv_attr(quantum,[graph],[double],'0.0','0.0','')).
+assert(gv_attr(rank,[subgraph],[rankType],'','','dot only')).
+assert(gv_attr(rankdir,[graph],[rankdir],'TB','','dot only')).
+assert(gv_attr(ranksep,[graph],[double,doubleList],'0.5(dot)1.0(twopi)','0.02','twopi, dot only')).
+assert(gv_attr(ratio,[graph],[double,string],'','','')).
+assert(gv_attr(rects,[node],[rect],'','','write only')).
+assert(gv_attr(regular,[node],[bool],false,'','')).
+assert(gv_attr(remincross,[graph],[bool],true,'','dot only')).
+assert(gv_attr(repulsiveforce,[graph],[double],'1.0','0.0','sfdp only')).
+assert(gv_attr(resolution,[graph],[double],'96.00.0','','svg, bitmap output only')).
+assert(gv_attr(root,[graph,node],[string,bool],'<none>(graphs)false(nodes)','','circo, twopi only')).
+assert(gv_attr(rotate,[graph],[int],'0','','')).
+assert(gv_attr(rotation,[graph],[double],'0','','sfdp only')).
+assert(gv_attr(samehead,[edge],[string],'','','dot only')).
+assert(gv_attr(sametail,[edge],[string],'','','dot only')).
+assert(gv_attr(samplepoints,[node],[int],'8(output)20(overlap and image maps)','','')).
+assert(gv_attr(scale,[graph],[double,point],'','','not dot')).
+assert(gv_attr(searchsize,[graph],[int],'30','','dot only')).
+assert(gv_attr(sep,[graph],[addDouble,addPoint],'+4','','not dot')).
+assert(gv_attr(shape,[node],[shape],ellipse,'','')).
+assert(gv_attr(shapefile,[node],[string],'','','')).
+assert(gv_attr(showboxes,[edge,graph,node],[int],'0','0','dot only')).
+assert(gv_attr(sides,[node],[int],'4','0','')).
+assert(gv_attr(size,[graph],[double,point],'','','')).
+assert(gv_attr(skew,[node],[double],'0.0','-100.0','')).
+assert(gv_attr(smoothing,[graph],[smoothType],'"none"','','sfdp only')).
+assert(gv_attr(sortv,[cluster,graph,node],[int],'0','0','')).
+assert(gv_attr(splines,[graph],[bool,string],'','','')).
+assert(gv_attr(start,[graph],[startType],'','','fdp, neato only')).
+assert(gv_attr(style,[cluster,edge,graph,node],[style],'','','')).
+assert(gv_attr(stylesheet,[graph],[string],'','','svg only')).
+assert(gv_attr(tailURL,[edge],[escString],'','','svg, map only')).
+assert(gv_attr(tail_lp,[edge],[point],'','','write only')).
+assert(gv_attr(tailclip,[edge],[bool],true,'','')).
+assert(gv_attr(tailhref,[edge],[escString],'','','svg, map only')).
+assert(gv_attr(taillabel,[edge],[lblString],'','','')).
+assert(gv_attr(tailport,[edge],[portPos],center,'','')).
+assert(gv_attr(tailtarget,[edge],[escString],_,'','svg, map only')).
+assert(gv_attr(tailtooltip,[edge],[escString],'','','svg, cmap only')).
+assert(gv_attr(target,[cluster,edge,graph,node],[escString,string],_,'','svg, map only')).
+assert(gv_attr(tooltip,[cluster,edge,node],[escString],'','','svg, cmap only')).
+assert(gv_attr(truecolor,[graph],[bool],'','','bitmap output only')).
+assert(gv_attr(vertices,[node],[pointList],'','','write only')).
+assert(gv_attr(viewport,[graph],[viewPort],'','','')).
+assert(gv_attr(voro_margin,[graph],[double],'0.05','0.0','not dot')).
+assert(gv_attr(weight,[edge],[int,double],'1','0(dot,twopi)1(neato,fdp)','')).
+assert(gv_attr(width,[node],[double],'0.75','0.01','')).
+assert(gv_attr(xdotversion,[graph],[string],'','','xdot only')).
+assert(gv_attr(xlabel,[edge,node],[lblString],'','','')).
+assert(gv_attr(xlp,[edge,node],[point],'','','write only')).
+assert(gv_attr(z,[node],[double],'0.0','-MAXFLOAT-1000','')).
diff --git a/test/test.pl b/test/test.pl
new file mode 100644
index 0000000..83dc023
--- /dev/null
+++ b/test/test.pl
@@ -0,0 +1,15 @@
+%/fca
+:- use_module(library(fca/fca_viz)).
+%/graph
+:- use_module(library(graph/build_export_graph)).
+%/gv
+:- use_module(library(gv/gv_attrs)).
+:- use_module(library(gv/gv_attr_type)).
+:- use_module(library(gv/gv_color)).
+:- use_module(library(gv/gv_dom)).
+:- use_module(library(gv/gv_file)).
+:- use_module(library(gv/gv_graph)).
+:- use_module(library(gv/gv_graph_comp)).
+:- use_module(library(gv/gv_html)).
+%/tree
+:- use_module(library(tree/tree_viz)).
 
 
diff --git a/.gitignore b/.gitignore
index bd24270..b25c15b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1 @@
 *~
-*#
-*.db
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..a63cbff
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,6 @@
+[submodule "Prolog-Library-Collection"]
+	path = Prolog-Library-Collection
+	url = https://github.com/wouterbeek/Prolog-Library-Collection.git
+[submodule "plHtml"]
+	path = plHtml
+	url = https://github.com/wouterbeek/plHtml.git
diff --git a/LICENSE.txt b/LICENSE.txt
deleted file mode 100644
index c7b7798..0000000
--- a/LICENSE.txt
+++ /dev/null
@@ -1,21 +0,0 @@
-The MIT License (MIT)
-
-Copyright (c) 2015 Wouter Beek
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
diff --git a/Prolog-Library-Collection b/Prolog-Library-Collection
new file mode 160000
index 0000000..5b34b01
--- /dev/null
+++ b/Prolog-Library-Collection
@@ -0,0 +1 @@
+Subproject commit 5b34b01ab0cc82a56e6fd90fc26e75da22f5a0fe
diff --git a/README.md b/README.md
deleted file mode 100644
index 23a45db..0000000
--- a/README.md
+++ /dev/null
@@ -1,151 +0,0 @@
-plGraphViz
-==========
-
-This library allows you to easily export graphs represented as Prolog terms
-using [GraphViz](http://www.graphviz.org/), an advanced graph drawing library.
-The Prolog terms have the following form:
-
-~~~prolog
-graph(Vertices, Edges, GraphAttrs)
-~~~
-
-`Vertices` and `Edges` are lists of compound terms of the following form:
-
-~~~prolog
-vertex(Id, VertexAttrs)
-edge(FromId, ToId, EdgeAttrs)
-~~~
-
-`Id` identifies a vertex and may or may not occur in any of the edges
-(i.e., unconnected vertices are allowed).
-`FromId` and `ToId` may occur in the list of vertices,
-in order to draw an edge between vertices with set attributes.
-
-Attributes have the form `Name=Value`.
-`GraphAttrs` are attributes of the graph.
-`VertexAttrs` are attributes of the vertex.
-`EdgeAttrs` are attributes of the edge.
-
-Attribute values are given as Prolog terms as well,
-and are type-checked before exporting.
-Many of the GraphViz attributes are supported.
-New ones are added on an as-needed bases
-(open an issue on Github if you want to see a specific feature added!).
-HTML-like labels are supported, allowing complex tables to be shown
-inside vertices.
-
----
-
-### Installation
-
-~~~shell
-$ git clone https://github.com/wouterbeek/plGraphViz.git
-$ cd plGraphViz
-$ git submodule update --init
-~~~
-
-### Example
-
-~~~prolog
-$ swipl run.pl
-?- export_graph_to_gv_file(
-     graph([vertex(1,[]),vertex(2,[])],[edge(1,2,[])],[]),
-     File,
-     [method(sfdp),output(png)]
-   ).
-   File = 'PATH/plGraphViz/data/tmp.png'
-~~~
-
-![](https://raw.githubusercontent.com/wouterbeek/plGraphViz/master/example1.png "Example graph.")
-
-The graphic can be saved to a different file by instantiating
-the `File` argument.
-
----
-
-### 'Method' option
-
-Option `method(+atom)` sets the drawing method that is used by GraphViz
- to place the vertices and edges.
-The following values are supported.
-
-| **Value**       | **Description**         |
-| `circo`         | Circular layout.        |
-| `dot` (default) | Directed graph.         |
-| `fdp`           | Undirected graph.       |
-| `neato`         | Undirected graph.       |
-| `osage`         | Tree map.               |
-| `sfdp`          | Large undirected graph. |
-| `twopi`         | Radical layouts.        |
-
----
-
-### 'Output' option
-
-Option `output(+atom)` sets the type of file the graph is written to.
-The following values are supported.
-
-| **Value**             | **Description**                       |
-|:---------------------:|:--------------------------------------|
-| `bmp`                 | Windows Bitmap Format                 |
-| `canon`               |                                       |
-| `dot`                 |                                       |
-| `gv`,  `xdot`, `xdot1.2`, `xdot1.4` | DOT                     |
-| `cgimage`             | CGImage bitmap format                 |
-| `cmap`                | Client-side imagemap (deprecated)     |
-| `eps`                 | Encapsulated PostScript               |
-| `exr`                 | OpenEXR                               |
-| `fig`                 |                                       |
-| `gd`, `gd2`           | GD/GD2 formats                        |
-| `gif`                 |                                       |
-| `gtk`                 | GTK canvas                            |
-| `ico`                 | Icon Image File Format                |
-| `imap`                |                                       |
-| `cmapx`               | Server-side and client-side imagemaps |
-| `imap_np`, `cmapx_np` | Server-side and client-side imagemaps |
-| `ismap`               | Server-side imagemap (deprecated)     |
-| `jp2`                 | JPEG 2000                             |
-| `jpg`, `jpeg`, `jpe`  | JPEG                                  |
-| `pct`, `pict`         | PICT                                  |
-| `pdf` (default)       | Portable Document Format (PDF)        |
-| `pic`                 | Kernighan's PIC graphics language     |
-| `plain`, `plain-ext`  | Simple text format                    |
-| `png`                 | Portable Network Graphics format      |
-| `pov`                 | POV-Ray markup language (prototype)   |
-| `ps`                  | PostScript                            |
-| `ps2`                 | PostScript for PDF                    |
-| `psd`                 | PSD                                   |
-| `sgi`                 | SGI                                   |
-| `svg`, `svgz`         | Scalable Vector Graphics              |
-| `tga`                 | Truevision TGA                        |
-| `tif`, `tiff`         | TIFF (Tag Image File Format)          |
-| `tk`                  | TK graphics                           |
-| `vml`, `vmlz`         | Vector Markup Language (VML)          |
-| `vrml`                | VRML                                  |
-| `wbmp`                | Wireless BitMap format                |
-| `webp`                | Image format for the Web              |
-| `xlib`, `x11`         | Xlib canvas                           |
-
----
-
-### HTML-like labels
-
-Example of using HTML-like labels:
-
-~~~prolog
-export_graph_to_gv_file(
-  graph(
-    [vertex(1,[]),vertex(2,[label=html(table([tr([td(a),td(b)]),tr([td(c),td(d)])]))])],
-    [edge(1,2,[label='From 1 to 2.'])],
-    []
-  ),
-  File,
-  []
-).
-~~~
-
-![](https://raw.githubusercontent.com/wouterbeek/plGraphViz/master/example2.png "Example graph with HTML-like labels.")
-
----
-
-Developed during 2013-2014 by [Wouter Beek](http://www.wouterbeek.com).
diff --git a/data/gv_attrs_scrape.pl b/data/gv_attrs_scrape.pl
deleted file mode 100644
index bcc7654..0000000
--- a/data/gv_attrs_scrape.pl
+++ /dev/null
@@ -1,119 +0,0 @@
-:- module(
-  gv_attrs_scrape,
-  [
-    gv_attrs_scrape/1 % +File
-  ]
-).
-
-/** <module> GraphViz: Scrape attributes
-
-Writes compound terms of the following form to file:
-
-```prolog
-gv_attr(
-  ?Name,
-  ?UsedBy:list(oneof([cluster,edge,graph,node,subgraph])),
-  ?Types:list(atom),
-  ?Default,
-  ?Minimum,
-  ?Notes
-) is nondet.
-```
-
-@author Wouter Beek
-@version 2015/10, 2016/07
-*/
-
-:- use_module(library(apply)).
-:- use_module(library(dcg/dcg_ext)).
-:- use_module(library(debug)).
-:- use_module(library(gv/gv_attr_type)).
-:- use_module(library(http/http_download)).
-:- use_module(library(lists)).
-:- use_module(library(os/io)).
-:- use_module(library(pl_term)).
-:- use_module(library(print_ext)).
-:- use_module(library(xpath)).
-:- use_module(library(xpath/xpath_table)).
-:- use_module(library(yall)).
-
-
-
-
-
-%! gv_attrs_scrape(+File) is det.
-
-gv_attrs_scrape(File):-
-  debug(gv, "Updating the GraphViz attributes table.", []),
-  call_to_stream(File, [In,Meta,Meta]>>gv_attrs_download(In)).
-
-
-gv_attrs_download(Write):-
-  gv_attrs_iri(Iri),
-  html_download(Iri, Dom),
-  xpath_chk(Dom, //table(@align=lower_case(center)), TableDom),
-  xpath_table(TableDom, _, Rows),
-  maplist(write_attr_row(Write), Rows).
-
-
-write_attr_row(Write, [Name,UsedBy1,Types1,Default1,Minimum,Notes]):-
-  atom_phrase(translate_usedby(UsedBy2), UsedBy1),
-  once(atom_phrase(translate_type(Types2), Types1)),
-  sort(UsedBy2, UsedBy3),
-  translate_default(Default1, Default2),
-  with_output_to(
-    Write,
-    write_fact(gv_attr(Name, UsedBy3, Types2, Default2, Minimum, Notes))
-  ).
-
-gv_attrs_iri('http://www.graphviz.org/doc/info/attrs.html').
-
-
-
-
-
-% HELPERS %
-
-%! translate_default(+Default1, -Default2) is det.
-
-% The empty string is represented by the empty atom.
-translate_default('""', ''):- !.
-% The absence of a default value is represented by an uninstantiated variable.
-translate_default('<none>', _):- !.
-translate_default(Default, Default).
-
-
-
-%! translate_type(-Types:list(atom))// is det.
-
-translate_type([H|T]) -->
-  gv_attr_type(H),
-  whites,
-  translate_type(T).
-translate_type([H]) -->
-  gv_attr_type(H).
-translate_type([]) --> "".
-
-
-
-%! translated_usedby(
-%!   -UsedBy:list(oneof([cluster,edge,graph,node,subgraph]))
-%! )// is det.
-
-translate_usedby([cluster|T]) -->
-  "C", !,
-  translate_usedby(T).
-translate_usedby([edge|T]) -->
-  "E", !,
-  translate_usedby(T).
-translate_usedby([graph|T]) -->
-  "G", !,
-  translate_usedby(T).
-translate_usedby([node|T]) -->
-  "N", !,
-  translate_usedby(T).
-translate_usedby([subgraph|T]) -->
-  "S", !,
-  translate_usedby(T).
-translate_usedby([]) -->
-  "".
diff --git a/data/gv_color_scrape.pl b/data/gv_color_scrape.pl
deleted file mode 100644
index 102a02a..0000000
--- a/data/gv_color_scrape.pl
+++ /dev/null
@@ -1,52 +0,0 @@
-:- module(
-  gv_color_scrape,
-  [
-    gv_color_scrape/1 % +File
-  ]
-).
-
-/** <module> GraphViz: Scrape colors
-
-@author Wouter Beek
-@version 2015/10, 2016/07
-*/
-
-:- use_module(library(apply)).
-:- use_module(library(debug)).
-:- use_module(library(http/http_download)).
-:- use_module(library(lists)).
-:- use_module(library(os/io)).
-:- use_module(library(pl_term)).
-:- use_module(library(print_ext)).
-:- use_module(library(xpath)).
-:- use_module(library(xpath/xpath_table)).
-:- use_module(library(yall)).
-
-
-
-
-
-%! gv_color_scrape(+File) is det.
-
-gv_color_scrape(File):-
-  debug(io, "Updating the GraphViz color table.", []),
-  call_to_stream(File, [In,Meta,Meta]>>gv_color_download(In)).
-
-
-gv_color_download(Write):-
-  gv_color_iri(Iri),
-  html_download(Iri, Dom),
-  xpath_chk(Dom, //table(1), TableDom1),
-  xpath_chk(Dom, //table(2), TableDom2),
-  maplist(write_color_table(Write), [x11,svg], [TableDom1,TableDom2]).
-
-
-write_color_table(Write, Colorscheme, TableDom):-
-  xpath_table(TableDom, _, Rows),
-  append(Rows, Cells),
-  forall(
-    member(Cell, Cells),
-    with_output_to(Write, write_fact(gv_color(Colorscheme, Cell)))
-  ).
-
-gv_color_iri('http://www.graphviz.org/doc/info/colors.html').
diff --git a/example1.png b/example1.png
deleted file mode 100644
index 31e7ba6..0000000
Binary files a/example1.png and /dev/null differ
diff --git a/example2.png b/example2.png
deleted file mode 100644
index 6e44b16..0000000
Binary files a/example2.png and /dev/null differ
diff --git a/gv_attr_type.pl b/gv_attr_type.pl
new file mode 100644
index 0000000..7a64c82
--- /dev/null
+++ b/gv_attr_type.pl
@@ -0,0 +1,417 @@
+:- module(
+  gv_attr_type,
+  [
+    gv_attr_type/1, % ?Type:atom
+    addDouble//1, % +Double:float
+    addPoint//1, % +Point:compound
+    arrowType//1, % +ArrowType:atom
+    bool//1, % +Boolean:boolean
+    clusterMode//1, % +ClusterMode:atom
+    dirType//1, % +DirectionType:oneof([back,both,forward,none])
+    double//1, % +Double:float
+    doubleList//1, % +Doubles:list(float)
+    escString//1,
+    %layerList//1,
+    %layerRange//1,
+    lblString//1,
+    int//1, % +Integer:integer
+    outputMode//1, % +OutputMode:atom
+    %packMode//1,
+    pagedir//1, % +Pagedir:atom
+    point//1, % +Point:compound
+    pointList//1, % +Points:list(compound)
+    %portPos//1,
+    quadType//1, % +QuadType:atom
+    rankType//1, % +RankType:atom
+    rankdir//1, % +RankDirection:atom
+    rect//1, % +Rectangle:compound
+    shape//1,
+    smoothType//1, % +SmoothType:atom
+    %splineType//1,
+    %startType//1,
+    string//1, % ?Content:atom
+    style//2 % +Context:oneof([cluster,edge,node])
+             % +Style:atom
+    %viewPort//1
+  ]
+).
+:- reexport(
+  plGraphViz(gv_color),
+  [
+    color//1, % +Color:compound
+    colorList//1 % +ColorList:list(compound)
+  ]
+).
+
+/** <module> GraphViz attribute types
+
+@author Wouter Beek
+@version 2014/06
+*/
+
+:- use_module(dcg(dcg_abnf)).
+:- use_module(dcg(dcg_cardinal)).
+:- use_module(dcg(dcg_content)).
+
+:- use_module(plGraphViz(gv_html)).
+
+
+
+%! gv_attr_type(?Type:atom) is nondet.
+
+gv_attr_type(addDouble).
+gv_attr_type(addPoint).
+gv_attr_type(arrowType).
+gv_attr_type(bool).
+gv_attr_type(color).
+gv_attr_type(colorList).
+gv_attr_type(clusterMode).
+gv_attr_type(dirType).
+gv_attr_type(double).
+gv_attr_type(doubleList).
+gv_attr_type(escString).
+gv_attr_type(layerList).
+gv_attr_type(layerRange).
+gv_attr_type(lblString).
+gv_attr_type(int).
+gv_attr_type(outputMode).
+gv_attr_type(packMode).
+gv_attr_type(pagedir).
+gv_attr_type(point).
+gv_attr_type(pointList).
+gv_attr_type(portPos).
+gv_attr_type(quadType).
+gv_attr_type(rankType).
+gv_attr_type(rankdir).
+gv_attr_type(rect).
+gv_attr_type(shape).
+gv_attr_type(smoothType).
+gv_attr_type(splineType).
+gv_attr_type(startType).
+gv_attr_type(string).
+gv_attr_type(style).
+gv_attr_type(viewPort).
+
+
+%! addDouble(+Float:float)// .
+% An *addDouble* is represented by a Prolog float.
+
+addDouble(Float) -->
+  '?'(`+`),
+  double(Float).
+
+
+%! addPoint(+Point:compound)// .
+% An *addPoint* is represented by a compound of the following form:
+% `point(X:float,Y:float,InputOnly:boolean)`.
+
+addPoint(Point) -->
+  '?'(`+`),
+  point(Point).
+
+
+%! arrowType(+ArrowType:atom)// .
+
+arrowType(V) -->
+  {arrowType(V)},
+  atom(V).
+
+arrowType(V):-
+  primitive_shape(V).
+arrowType(V):-
+  derived(V).
+arrowType(V):-
+  backwards_compatible(V).
+
+primitive_shape(box).
+primitive_shape(crow).
+primitive_shape(circle).
+primitive_shape(diamond).
+primitive_shape(dot).
+primitive_shape(inv).
+primitive_shape(none).
+primitive_shape(normal).
+primitive_shape(tee).
+primitive_shape(vee).
+
+derived(odot).
+derived(invdot).
+derived(invodot).
+derived(obox).
+derived(odiamond).
+
+backwards_compatible(ediamond).
+backwards_compatible(empty).
+backwards_compatible(halfopen).
+backwards_compatible(invempty).
+backwards_compatible(open).
+
+
+bool(false) --> `false`.
+bool(false) --> `no`.
+bool(true) --> `true`.
+bool(true) --> `yes`.
+
+
+%! clusterMode(+ClusterMode:atom)// .
+
+clusterMode(V) -->
+  {clusterMode(V)},
+  atom(V).
+
+clusterMode(global).
+clusterMode(local).
+clusterMode(none).
+
+
+%! dirType(+DirectionType:oneof([back,both,forward,none]))// .
+
+dirType(DirType) -->
+  {dirType(DirType)},
+  atom(DirType).
+
+dirType(back).
+dirType(both).
+dirType(forward).
+dirType(none).
+
+
+double(Double1) -->
+  % float//1 will check for float type.
+  {Double2 is Double1 * 1.0},
+  float(Double2).
+
+
+doubleList([H|T]) -->
+  double(H),
+  '*'(doubleList1, T).
+
+doubleList1(Float) -->
+  `:`,
+  double(Float).
+
+
+%! escString(+String:atom)// .
+% @tbd Support for context-dependent replacements.
+
+escString(String) -->
+  atom(String).
+
+
+% @tbd layerList
+
+
+% @tbd layerRange
+
+
+lblString(V) -->
+  escString(V).
+lblString(V) -->
+  gv_html_like_label(V).
+
+
+int(V) -->
+  integer(V).
+
+
+outputMode(V) -->
+  {outputMode(V)},
+  atom(V).
+
+outputMode(breadthfirst).
+outputMode(edgesfirst).
+outputMode(nodesfirst).
+
+
+% @tbd packMode
+
+
+pagedir(V) -->
+  {pagedir(V)},
+  atom(V).
+
+pagedir('BL').
+pagedir('BR').
+pagedir('LB').
+pagedir('LT').
+pagedir('RB').
+pagedir('RT').
+pagedir('TL').
+pagedir('TR').
+
+
+%! point(+Point:compound)// .
+% A *point* is represented by a compound of the following form:
+% `point(X:float,Y:float,InputOnly:boolean)`.
+
+point(point(X,Y,InputOnly)) -->
+  float(X),
+  `,`,
+  float(Y),
+  input_only(InputOnly).
+
+input_only(false) --> [].
+input_only(true) --> `!`.
+
+
+pointList(Points) -->
+  '*'(point, Points).
+
+
+% @tbd portPos
+
+
+quadType(V) -->
+  {quadType(V)},
+  atom(V).
+
+quadType(fast).
+quadType(none).
+quadType(normal).
+
+
+rankType(V) -->
+  {rankType(V)},
+  atom(V).
+
+rankType(max).
+rankType(min).
+rankType(same).
+rankType(sink).
+rankType(source).
+
+
+rankdir(V) -->
+  {rankdir(V)},
+  atom(V).
+
+rankdir('BT').
+rankdir('LR').
+rankdir('RL').
+rankdir('TB').
+
+
+rect(rect(LowerLeftX,LowerLeftY,UpperRightX,UpperRightY)) -->
+  float(LowerLeftX), `,`,
+  float(LowerLeftY), `,`,
+  float(UpperRightX), `,`,
+  float(UpperRightY).
+
+
+shape(V) -->
+  {polygon_based_shape(V)},
+  atom(V).
+
+polygon_based_shape(assembly).
+polygon_based_shape(box).
+polygon_based_shape(box3d).
+polygon_based_shape(cds).
+polygon_based_shape(circle).
+polygon_based_shape(component).
+polygon_based_shape(diamond).
+polygon_based_shape(doublecircle).
+polygon_based_shape(doubleoctagon).
+polygon_based_shape(egg).
+polygon_based_shape(ellipse).
+polygon_based_shape(fivepoverhang).
+polygon_based_shape(folder).
+polygon_based_shape(hexagon).
+polygon_based_shape(house).
+polygon_based_shape(insulator).
+polygon_based_shape(invhouse).
+polygon_based_shape(invtrapezium).
+polygon_based_shape(invtriangle).
+polygon_based_shape(larrow).
+polygon_based_shape(lpromoter).
+polygon_based_shape('Mcircle').
+polygon_based_shape('Mdiamond').
+polygon_based_shape('Msquare').
+polygon_based_shape(none).
+polygon_based_shape(note).
+polygon_based_shape(noverhang).
+polygon_based_shape(octagon).
+polygon_based_shape(oval).
+polygon_based_shape(parallelogram).
+polygon_based_shape(pentagon).
+polygon_based_shape(plaintext).
+polygon_based_shape(point).
+polygon_based_shape(polygon).
+polygon_based_shape(primersite).
+polygon_based_shape(promoter).
+polygon_based_shape(proteasesite).
+polygon_based_shape(proteinstab).
+polygon_based_shape(rarrow).
+polygon_based_shape(rect).
+polygon_based_shape(rectangle).
+polygon_based_shape(restrictionsite).
+polygon_based_shape(ribosite).
+polygon_based_shape(rnastab).
+polygon_based_shape(rpromoter).
+polygon_based_shape(septagon).
+polygon_based_shape(signature).
+polygon_based_shape(square).
+polygon_based_shape(tab).
+polygon_based_shape(terminator).
+polygon_based_shape(threepoverhang).
+polygon_based_shape(trapezium).
+polygon_based_shape(triangle).
+polygon_based_shape(tripleoctagon).
+polygon_based_shape(utr).
+
+
+smoothType(V) -->
+  {smoothType(V)},
+  atom(V).
+
+smoothType(avg_dist).
+smoothType(graph_dist).
+smoothType(none).
+smoothType(power_dist).
+smoothType(rng).
+smoothType(spring).
+smoothType(triangle).
+
+
+% @tbd splineType
+
+
+% @tbd startType
+
+
+%! string(?Content:atom)// .
+% A GraphViz string.
+
+string(Content) -->
+  atom(Content).
+
+
+%! style(?Context:oneof([cluster,edge,node]), ?Style:atom) is nondet.
+
+style(Context, Style) -->
+  {style(Context, Style)},
+  atom(Style).
+
+style(cluster, bold).
+style(cluster, dashed).
+style(cluster, dotted).
+style(cluster, filled).
+style(cluster, rounded).
+style(cluster, solid).
+style(cluster, striped).
+style(edge, bold).
+style(edge, dashed).
+style(edge, dotted).
+style(edge, solid).
+style(node, bold).
+style(node, dashed).
+style(node, diagonals).
+style(node, dotted).
+style(node, filled).
+style(node, rounded).
+style(node, solid).
+style(node, striped).
+style(node, wedged).
+
+
+% @tbd viewPort
+
diff --git a/gv_attrs.pl b/gv_attrs.pl
new file mode 100644
index 0000000..dd0145a
--- /dev/null
+++ b/gv_attrs.pl
@@ -0,0 +1,184 @@
+:- module(
+  gv_attrs,
+  [
+    gv_attr/3 % +Context:oneof([cluster,edge,graph,node,subgraph])
+              % +Attr1:nvpair
+              % +Attr2:nvpair
+  ]
+).
+
+/** <module> GraphViz attributes v2
+
+@author Wouter Beek
+@version 2014/06
+*/
+
+:- use_module(library(apply)).
+:- use_module(library(persistency)).
+:- use_module(library(xpath)).
+
+:- use_module(dcg(dcg_content)).
+:- use_module(dcg(dcg_generic)).
+:- use_module(generics(db_ext)).
+:- use_module(os(file_ext)).
+
+:- use_module(plHtml(html)).
+:- use_module(plHtml(html_table)).
+
+:- use_module(plGraphViz(gv_attr_type)). % DCGs implementing attribute types.
+
+:- db_add_novel(user:prolog_file_type(log, logging)).
+
+%! gv_attr(
+%!   ?Name:atom,
+%!   ?UsedBy:list(oneof([cluster,edge,graph,node,subgraph])),
+%!   ?Types:list(atom),
+%!   ?Default,
+%!   ?Minimum,
+%!   ?Notes:atom
+%! ) is nondet.
+
+:- persistent(
+  gv_attr(
+    name:atom,
+    used_by:list(oneof([cluster,edge,graph,node,subgraph])),
+    types:list(atom),
+    default,
+    minimum,
+    notes:atom
+  )
+).
+
+:- initialization(gv_attrs_init).
+
+
+
+gv_attr(Context, N=V, N=V):-
+  var(V), !,
+  gv_attr(N, UsedBy, _, V, _, _),
+  memberchk(Context, UsedBy).
+gv_attr(Context, N=V1, N=V2):-
+  gv_attr(N, UsedBy, Types, _, Minimum, _),
+  memberchk(Context, UsedBy),
+  member(Type, Types),
+  (
+    Type == style
+  ->
+    Dcg =.. [Type,Context,V1]
+  ;
+    Dcg =.. [Type,V1]
+  ),
+  once(dcg_phrase(Dcg, V2)),
+  check_minimum(V1, Minimum).
+
+check_minimum(_, ''):- !.
+check_minimum(V, Min1):-
+  atom_number(Min1, Min2),
+  Min2 =< V.
+
+
+
+% INITIALIZATION
+
+%! assert_gv_attr_row(+Row:list(atom)) is det.
+
+assert_gv_attr_row([Name,UsedBy1,Types1,Default1,Minimum,Notes]):-
+  dcg_phrase(translate_usedby(UsedBy2), UsedBy1),
+  once(dcg_phrase(translate_type(Types2), Types1)),
+  sort(UsedBy2, UsedBy3),
+  translate_default(Default1, Default2),
+  assert_gv_attr(Name, UsedBy3, Types2, Default2, Minimum, Notes).
+
+
+%! gv_attrs_downloads is det.
+% Downloads the table describing GraphViz attributes from `graphviz.org`.
+
+gv_attrs_download:-
+  gv_attrs_url(Url),
+  download_html(Url, Dom, [html_dialect(html4),verbose(silent)]),
+
+  xpath(Dom, //table(@align=center), TableDom),
+  % @tbd This does not work, since in `record_name(Element, Name)`,
+  %      `Element` is a signleton list whereas a compound term is expected.
+  %%%%xpath(Dom, /html/body/table, TableDom),
+
+  html_to_table(TableDom, _, Rows),
+  maplist(assert_gv_attr_row, Rows).
+
+
+%! gv_attrs_file(-File:atom) is det.
+
+gv_attrs_file(File):-
+  absolute_file_name(
+    data(gv_attrs),
+    File,
+    [access(write),file_type(logging)]
+  ).
+
+
+%! gv_attrs_init is det.
+
+gv_attrs_init:-
+  gv_attrs_file(File),
+  safe_db_attach(File),
+  file_age(File, Age),
+  gv_attrs_update(Age).
+
+
+%! gv_attrs_update(+Age:float) is det.
+
+% The persistent store is still fresh.
+gv_attrs_update(Age):-
+  once(gv_attr(_, _, _, _, _, _)),
+  Age < 8640000, !.
+% The persistent store has become stale, so refresh it.
+gv_attrs_update(_):-
+  retractall_gv_attr(_, _, _, _, _, _),
+  gv_attrs_download.
+
+
+%! gv_attrs_url(-Url:url) is det.
+
+gv_attrs_url('http://www.graphviz.org/doc/info/attrs.html').
+
+
+%! safe_db_attach(+File:atom) is det.
+
+safe_db_attach(File):-
+  exists_file(File), !,
+  db_attach(File, []).
+safe_db_attach(File):-
+  touch_file(File),
+  safe_db_attach(File).
+
+
+%! translate_default(+Default1:atom, -Default2:atom) is det.
+
+% The empty string is represented by the empty atom.
+translate_default('""', ''):- !.
+% The absence of a default value is represented by an uninstantiated variable.
+translate_default('<none>', _):- !.
+translate_default(Default, Default).
+
+
+%! translate_type(-Types:list(atom))// is det.
+
+translate_type([H|T]) -->
+  {gv_attr_type(H)},
+  atom(H),
+  whites,
+  translate_type(T).
+translate_type([]) --> !, [].
+
+
+%! translated_usedby(
+%!   -UsedBy:list(oneof([cluster,edge,graph,node,subgraph]))
+%! )// is det.
+
+translate_usedby([cluster|T]) --> `C`, !, translate_usedby(T).
+translate_usedby([edge|T]) --> `E`, !, translate_usedby(T).
+translate_usedby([graph|T]) --> `G`, !, translate_usedby(T).
+translate_usedby([node|T]) --> `N`, !, translate_usedby(T).
+translate_usedby([subgraph|T]) --> `S`, !, translate_usedby(T).
+translate_usedby([]) --> [].
+
diff --git a/gv_color.pl b/gv_color.pl
new file mode 100644
index 0000000..19d6e05
--- /dev/null
+++ b/gv_color.pl
@@ -0,0 +1,151 @@
+:- module(
+  gv_color,
+  [
+    gv_color/2, % ?Colorscheme:oneof([svg,x11])
+                % ?Color:atom
+    color//1, % +Color:compound
+    colorList//1 % +Pairs:list(pair(compound,float))
+  ]
+).
+
+/** <module> GraphViz color
+
+@author Wouter Beek
+@tbd Color value `transparent` is only available in the output formats
+     ps, svg, fig, vmrl, and the bitmap formats.
+@version 2014/06
+*/
+
+:- use_module(library(apply)).
+:- use_module(library(lists)).
+:- use_module(library(persistency)).
+:- use_module(library(xpath)).
+
+:- use_module(dcg(dcg_abnf)).
+:- use_module(dcg(dcg_cardinal)).
+:- use_module(dcg(dcg_content)).
+:- use_module(generics(db_ext)).
+:- use_module(os(file_ext)).
+:- use_module(sparql(sparql_char)).
+
+:- use_module(plHtml(html)).
+:- use_module(plHtml(html_table)).
+
+:- db_add_novel(user:prolog_file_type(log, logging)).
+
+%! gv_color(?Colorscheme:oneof([svg,x11]), ?Color:atom) is nondet.
+
+:- persistent(gv_color(colorscheme:oneof([svg,x11]),color:atom)).
+
+:- initialization(gv_color_init).
+
+
+
+% color(+Color:compound)// .
+% A *color* is represented by a compound term of one of the following forms:
+%   1. `rgb(Red:nonneg,Green:nonneg,Blue:nonneg)`
+%   2. `rgba(Red:nonneg,Green:nonneg,Blue:nonneg,Alpha:nonneg)`
+%   3. `hsv(Hue:between(0.0,1.0),Saturation:between(0.0,1.0),Value:between(0.0,1.0))`
+
+color(rgb(Red,Green,Blue)) --> !,
+  `#`,
+  '#'(3, hex_color, [Red,Green,Blue]).
+color(rgbs(Red,Green,Blue,Alpha)) --> !,
+  `#`,
+  '#'(4, hex_color, [Red,Green,Blue,Alpha]).
+color(hsv(Hue,Saturation,Value)) --> !,
+  '#'(3, hsv_color, [Hue,Saturation,Value]).
+color(Name) -->
+  {gv_color(_, Name)},
+  atom(Name).
+
+hex_color(I) -->
+  {W1 is I / 16},
+  'HEX'(W1),
+  {W2 is I mod 16},
+  'HEX'(W2).
+
+hsv_color(D, Head, Tail):-
+  format(codes(Head,Tail), '~2f', [D]).
+
+
+%! colorList(+Pairs:list(pair(compound,float)))// .
+
+colorList(Pairs) -->
+  '+'(wc, Pairs).
+
+wc(Color-Float) -->
+  color(Color),
+  '?'(wc_weight(Float)).
+
+wc_weight(Float) -->
+  `;`,
+  float(Float).
+
+
+
+% INITIALIZATION
+
+%! gv_color_download is det.
+
+gv_color_download:-
+  gv_color_url(Url),
+  download_html(Url, Dom, [html_dialect(html4),verbose(silent)]),
+  xpath(Dom, //table(1), TableDom1),
+  xpath(Dom, //table(2), TableDom2),
+  maplist(assert_color_table, [x11,svg], [TableDom1,TableDom2]).
+
+assert_color_table(Colorscheme, TableDom):-
+  html_to_table(TableDom, _, Rows),
+  append(Rows, Cells),
+  forall(
+    member(Cell, Cells),
+    assert_gv_color(Colorscheme, Cell)
+  ).
+
+
+%! gv_color_file(-File:atom) is det.
+
+gv_color_file(File):-
+  absolute_file_name(
+    data(gv_color),
+    File,
+    [access(write),file_type(logging)]
+  ).
+
+
+%! gv_color_init is det.
+
+gv_color_init:-
+  gv_color_file(File),
+  safe_db_attach(File),
+  file_age(File, Age),
+  gv_color_update(Age).
+
+
+%! gv_color_update(+Age:float) is det.
+
+% The persistent store is still fresh.
+gv_color_update(Age):-
+  once(gv_color(_, _)),
+  Age < 8640000, !.
+% The persistent store has become stale, so refresh it.
+gv_color_update(_):-
+  retractall_gv_color(_, _),
+  gv_color_download.
+
+
+%! gv_color_url(-Url:url) is det.
+
+gv_color_url('http://www.graphviz.org/doc/info/colors.html').
+
+
+%! safe_db_attach(+File:atom) is det.
+
+safe_db_attach(File):-
+  exists_file(File), !,
+  db_attach(File, []).
+safe_db_attach(File):-
+  touch_file(File),
+  safe_db_attach(File).
+
diff --git a/gv_dot.pl b/gv_dot.pl
new file mode 100644
index 0000000..9888973
--- /dev/null
+++ b/gv_dot.pl
@@ -0,0 +1,496 @@
+:- module(
+  gv_dot,
+  [
+    gv_graph//1 % +GraphTerm:compound
+  ]
+).
+
+/** <module> GraphViz DOT generator
+
+DCG rules for GraphViz DOT file generation.
+
+Methods for writing to the GraphViz DOT format.
+
+In GraphViz vertices are called 'nodes'.
+
+@author Wouter Beek
+@see http://www.graphviz.org/content/dot-language
+@version 2013/07, 2013/09, 2014/03-2014/06
+*/
+
+:- use_module(library(apply)).
+:- use_module(library(lists)).
+:- use_module(library(ordsets)).
+
+:- use_module(dcg(dcg_abnf)).
+:- use_module(dcg(dcg_ascii)).
+:- use_module(dcg(dcg_content)).
+:- use_module(dcg(dcg_generic)).
+:- use_module(dcg(dcg_meta)).
+:- use_module(dcg(dcg_os)).
+
+:- use_module(plGraphViz(gv_attrs)).
+:- use_module(plGraphViz(gv_html)).
+:- use_module(plGraphViz(gv_numeral)).
+
+
+
+%! gv_attribute(+Attribute:nvpair)// is det.
+% A single GraphViz attribute.
+% We assume that the attribute has already been validated.
+
+gv_attribute(Name=Val) -->
+  gv_id(Name), `=`, gv_id(Val), `;`.
+
+
+%! gv_attribute_list(
+%!   +Context:oneof([cluster,edge,graph,node,subgraph]),
+%!   +GraphAttributes:list(nvpair),
+%!   +Attributes:list(nvpair)
+%! )// .
+% ~~~{.abnf}
+% attr_list = "[" [a_list] "]" [attr_list]
+% a_list = ID "=" ID [","] [a_list]
+% ~~~
+
+% Attributes occur between square brackets.
+gv_attribute_list(Context, _, Attrs1) -->
+  {maplist(gv_attr(Context), Attrs1, Attrs2)},
+  bracketed(square, '*'(gv_attribute, Attrs2)).
+
+
+%! gv_compass_pt(+Direction:oneof(['_',c,e,n,ne,nw,s,se,sw,w]))// .
+% ~~~
+% compass_pt : (n | ne | e | se | s | sw | w | nw | c | _)
+% ~~~
+
+gv_compass_pt('_') --> `_`.
+gv_compass_pt(c) --> `c`.
+gv_compass_pt(e) --> `e`.
+gv_compass_pt(n) --> `n`.
+gv_compass_pt(ne) --> `ne`.
+gv_compass_pt(nw) --> `nw`.
+gv_compass_pt(s) --> `s`.
+gv_compass_pt(se) --> `se`.
+gv_compass_pt(sw) --> `sw`.
+gv_compass_pt(w) --> `w`.
+
+
+%! gv_edge_operator(+Directed:boolean)// .
+% The binary edge operator between two vertices.
+% The operator that is used depends on whether the graph is directed or
+% undirected.
+%
+% @arg Directed Whether an edge is directed (operator `->`) or
+%               undirected (operator `--`).
+
+gv_edge_operator(false) --> !, `--`.
+gv_edge_operator(true) --> arrow(right, 2).
+
+
+%! gv_edge_statement(
+%!   +Indent:nonneg,
+%!   +Directed:boolean,
+%!   +GraphAttributes:list(nvpair),
+%!   +EdgeTerm:compound
+%! )// is det.
+% A GraphViz statement describing an edge.
+%
+% @arg Indent The indentation level at which the edge statement is written.
+% @arg Directed Whether the graph is directed or not.
+% @arg GraphAttributes The attributes of the graph. Some of these attributes
+%      may be used in the edge statement (e.g., the colorscheme).
+% @arg EdgeTerm A compound term in the GIFormat, representing an edge.
+%
+% @tbd Instead of gv_node_id//1 we could have a gv_subgraph//1
+%      at the from and/or to location.
+% @tbd Add support for multiple, consecutive occurrences of gv_edge_rhs//2.
+
+gv_edge_statement(I, Directed, GAttrs, edge(FromId,ToId,EAttrs)) -->
+  indent(I),
+  gv_node_id(FromId), ` `,
+
+  gv_edge_operator(Directed), ` `,
+
+  gv_node_id(ToId), ` `,
+
+  % We want `colorscheme/1` from the edges and
+  % `directionality/1` from the graph.
+  gv_attribute_list(edge, GAttrs, EAttrs),
+  newline.
+
+
+%! gv_generic_attributes_statement(
+%!   +Kind:oneof([edge,graph,node]),
+%!   +Indent:integer,
+%!   +GraphAttributes:list(nvpair),
+%!   +CategoryAttributes:list(nvpair)
+%! )//
+% A GraphViz statement describing generic attributes for a category of items.
+%
+% @arg Kind The category of items for to the attributes apply.
+%      Possible values: `edge`, `graph`, and `node`.
+% @arg Indent An integer indicating the number of tabs.
+% @arg GraphAttributes A list of name-value pairs.
+% @arg CategoryAttributes A list of name-value pairs.
+%
+% ~~~
+% attr_stmt = (graph / node / edge) attr_list
+% ~~~
+
+gv_generic_attributes_statement(_, _, _, []) --> [], !.
+gv_generic_attributes_statement(Kind, I, GraphAttrs, KindAttrs) -->
+  indent(I),
+  gv_kind(Kind), ` `,
+  gv_attribute_list(Kind, GraphAttrs, KindAttrs), newline.
+
+
+%! gv_graph(+GraphTerm:compound)//
+% The follow graph attributes are supported,
+% beyond the GraphViz attributes for graphs:
+%   1. `directonality(+Directed:oneof([directed,undirected]))`
+%      A directed graph uses the keyword `digraph`.
+%      An undirected graph uses the keyword `graph`.
+%   2. `name(+GraphName:atom)`
+%   3. `strict(+StrictGraph:boolean)`
+%      This forbids the creation of self-arcs and multi-edges;
+%      they are ignored in the input file.
+%      Only in combinattion with directionality `directed`.
+%
+% ~~~{.abnf}
+% graph = ["strict"] ("graph" / "digraph") [ID] "{" stmt_list "}"
+% ~~~
+%
+% `GraphTerm` is a compound term of the following form:
+% ~~~{.pl}
+% graph(VertexTerms,RankedVertexTerms,EdgeTerms,GraphAttributes)
+% ~~~
+%
+% `RankedVertexTerms` is a list of compound terms of the following form:
+% ~~~{.pl}
+% rank(RankNode,ContentNodes)
+% ~~~
+%
+% @tbd Add support for subgraphs (arbitrary nesting).
+% @tbd Add support for escape strings:
+%      http://www.graphviz.org/doc/info/attrs.html#k:escString
+% @tbd Assert attributes that are generic with respect to a subgraph.
+% @tbd Not all vertex and edge properties can be shared it seems (e.g., label).
+
+gv_graph(graph(VTerms, ETerms, GAttrs)) -->
+  gv_graph(graph(VTerms, [], ETerms, GAttrs)).
+
+gv_graph(graph(VTerms, RankedVTerms, ETerms, GAttrs1)) -->
+  {
+    shared_attributes(VTerms, SharedVAttrs, NewVTerms),
+    shared_attributes(ETerms, SharedEAttrs, NewETerms),
+    select_nvpair(strict=Strict, GAttrs1, GAttrs2, false),
+    select_nvpair(directed=Directed, GAttrs2, GAttrs3, true),
+    select_nvpair(name=GName, GAttrs3, GAttrs4, noname),
+    add_default_nvpair(GAttrs4, overlap, false, GAttrs5),
+    I = 0
+  },
+
+  % The first statement in the GraphViz output.
+  % States that this file represents a graph according to the GraphViz format.
+  indent(I),
+  gv_strict(Strict),
+  gv_graph_type(Directed), ` `,
+  gv_id(GName), ` `,
+  bracketed(
+    curly,
+    gv_graph1(
+      I,
+      NewVTerms, SharedVAttrs, RankedVTerms,
+      NewETerms, SharedEAttrs,
+      Directed, GAttrs5
+    )
+  ),
+  newline.
+
+gv_graph1(
+  I,
+  NewVTerms, SharedVAttrs, RankedVTerms,
+  NewETerms, SharedEAttrs,
+  Directed, GAttrs
+) -->
+  newline,
+
+  % The following lines are indented.
+  {NewI is I + 1},
+
+  % Attributes that apply to the graph as a whole.
+  gv_generic_attributes_statement(graph, NewI, GAttrs, GAttrs),
+
+  % Attributes that are the same for all nodes.
+  gv_generic_attributes_statement(node, NewI, GAttrs, SharedVAttrs),
+
+  % Attributes that are the same for all edges.
+  gv_generic_attributes_statement(edge, NewI, GAttrs, SharedEAttrs),
+
+  % Only add a newline if some content was written in the previous three
+  % lines.
+  ({(GAttrs == [], SharedVAttrs == [], SharedEAttrs == [])} -> `` ; newline),
+
+  % The list of GraphViz nodes.
+  '*'(gv_node_statement(NewI, GAttrs), NewVTerms),
+  ({NewVTerms == []} -> `` ; newline),
+
+  % The ranked GraphViz nodes (displayed at the same height).
+  '*'(gv_ranked_node_collection(NewI, GAttrs), RankedVTerms),
+  ({RankedVTerms == []} -> `` ; newline),
+
+  {
+    findall(
+      edge(FromId,ToId,[]),
+      (
+        nth0(Index1, RankedVTerms, rank(vertex(FromId,_,_),_)),
+        nth0(Index2, RankedVTerms, rank(vertex(ToId,_,_),_)),
+        % We assume that the rank vertices are nicely ordered.
+        succ(Index1, Index2)
+      ),
+      RankEdges
+    )
+  },
+
+  % The rank edges.
+  '*'(gv_edge_statement(NewI, Directed, GAttrs), RankEdges),
+
+  % The non-rank edges.
+  '*'(gv_edge_statement(NewI, Directed, GAttrs), NewETerms),
+
+  % Note that we do not include a newline here.
+
+  % We want to indent the closing curly brace.
+  indent(I).
+
+
+%! gv_graph_type(+Directed:boolean)// .
+% The type of graph that is represented.
+
+gv_graph_type(false) --> `graph`.
+gv_graph_type(true) --> `digraph`.
+
+
+%! gv_id(?Atom:atom)// is det.
+% Parse a GraphViz identifier.
+% There are 4 variants:
+%   1. Any string of alphabetic (`[a-zA-Z'200-'377]`) characters,
+%      underscores (`_`) or digits (`[0-9]`), not beginning with a digit.
+%   2. A numeral `[-]?(.[0-9]+ | [0-9]+(.[0-9]*)? )`.
+%   3. Any double-quoted string (`"..."`) possibly containing
+%      escaped quotes (`\"`).
+%      In quoted strings in DOT, the only escaped character is
+%      double-quote (`"`). That is, in quoted strings, the dyad `\"`
+%      is converted to `"`. All other characters are left unchanged.
+%      In particular, `\\` remains `\\`.
+%      Layout engines may apply additional escape sequences.
+%   4. An HTML string (`<...>`).
+%
+% @tbd Add support for HTML-like labels:
+%      http://www.graphviz.org/doc/info/shapes.html#html
+%      This requires an XML grammar!
+
+% HTML strings (variant 4).
+gv_id(Atom) -->
+  dcg_atom_codes(gv_html_like_label, Atom), !.
+% Alpha-numeric strings (variant 1).
+gv_id(Atom) -->
+  {atom_codes(Atom, [H|T])},
+  gv_id_first(H),
+  gv_id_rest(T), !,
+  % Variant 1 identifiers should not be (case-variants of) a
+  % GraphViz keyword.
+  {\+ gv_keyword([H|T])}.
+% Numerals (variant 2)
+gv_id(N) -->
+  {number(N)}, !,
+  gv_numeral(N).
+% Double-quoted strings (variant 3).
+% The quotes are already part of the given atom.
+gv_id(Atom) -->
+  {
+    atom_codes(Atom, [H|T]),
+    append(S, [H], T)
+  },
+  dcg_between(double_quote(H), gv_quoted_string(S)), !.
+% Double-quoted strings (variant 3).
+% The quotes are not in the given atom. They are written anyway.
+gv_id(Atom) -->
+  quoted(double_quote, dcg_atom_codes(gv_quoted_string, Atom)), !.
+
+gv_id_first(X) --> ascii_letter(X).
+gv_id_first(X) --> underscore(X).
+
+gv_id_rest([]) --> [].
+gv_id_rest([H|T]) -->
+  (ascii_alpha_numeric(H) ; underscore(H)),
+  gv_id_rest(T).
+
+
+%! gv_keyword(+Codes:list(code)) is semidet.
+% Succeeds if the given codes for a GraphViz reserved keyword.
+
+gv_keyword(Codes):-
+  % Obviously, the keywords do not occur on the difference list input.
+  % So we must use phrase/[2,3].
+  phrase(gv_keyword, Codes).
+
+%! gv_keyword// .
+% GraphViz has reserved keywords that cannot be used as identifiers.
+% GraphViz keywords are case-insensitive.
+
+gv_keyword --> `digraph`.
+gv_keyword --> `edge`.
+gv_keyword --> `graph`.
+gv_keyword --> `node`.
+gv_keyword --> `strict`.
+gv_keyword --> `subgraph`.
+
+
+%! gv_kind(+Kind:oneof([edge,graph,node]))// .
+
+gv_kind(edge) --> `edge`.
+gv_kind(graph) --> `graph`.
+gv_kind(node) --> `node`.
+
+
+%! gv_node_id(+NodeId:atom)// .
+% GraphViz node identifiers can be of the following two types:
+%   1. A GraphViz identifier, see gv_id//1.
+%   2. A GraphViz identifier plus a GraphViz port indicator, see gv_port//0.
+%
+% @tbd Add support for GraphViz port indicators
+%      inside GraphViz node identifiers.
+
+gv_node_id(Id) -->
+  gv_id(Id).
+%gv_node_id(_) -->
+%  gv_id(_),
+%  gv_port.
+
+
+%! gv_node_statement(
+%!   +Indent:integer,
+%!   +GraphAttributes,
+%!   +VertexTerm:compound
+%! )// .
+% A GraphViz statement describing a vertex (GraphViz calls vertices 'nodes').
+
+gv_node_statement(I, GraphAttrs, vertex(Id,_,VAttrs)) -->
+  indent(I),
+  gv_node_id(Id), ` `,
+  gv_attribute_list(node, GraphAttrs, VAttrs), newline.
+
+
+gv_port -->
+  gv_port_location,
+  '?'(gv_port_angle).
+gv_port -->
+  gv_port_angle,
+  '?'(gv_port_location).
+gv_port -->
+  `:`,
+  gv_compass_pt(_).
+
+gv_port_angle -->
+  `@`,
+  gv_id(_).
+
+gv_port_location -->
+  `:`,
+  gv_id(_).
+gv_port_location -->
+  `:`,
+  bracketed(
+    round,
+    (
+      gv_id(_),
+      `,`,
+      gv_id(_)
+    )
+  ).
+
+
+gv_quoted_string([]) --> [].
+% Just to be sure, we do not allow the double quote
+% that closes the string to be escaped.
+gv_quoted_string([X]) -->
+  {X \== 92}, !,
+  [X].
+% A double quote is only allowed if it is escaped by a backslash.
+gv_quoted_string([92,34|T]) --> !,
+  gv_quoted_string(T).
+% Add the backslash escape character.
+gv_quoted_string([34|T]) --> !,
+  `\\\"`,
+  gv_quoted_string(T).
+% All other characters are allowed without escaping.
+gv_quoted_string([H|T]) -->
+  [H],
+  gv_quoted_string(T).
+
+
+gv_ranked_node_collection(
+  I,
+  GraphAttrs,
+  rank(Rank_V_Term,Content_V_Terms)
+) -->
+  indent(I),
+  bracketed(curly, (
+    newline,
+
+    % The rank attribute.
+    {NewI is I + 1},
+    indent(NewI), gv_attribute(rank=same), `;`, newline,
+
+    '*'(gv_node_statement(NewI, GraphAttrs), [Rank_V_Term|Content_V_Terms]),
+
+    % We want to indent the closing curly brace.
+    indent(I)
+  )),
+  newline.
+
+
+%! gv_strict(+Strict:boolean)// is det.
+% The keyword denoting that the graph is strict, i.e., has no self-arcs and
+% no multi-edges.
+% This only applies to directed graphs.
+
+gv_strict(false) --> [].
+gv_strict(true) --> `strict `.
+
+
+
+% Helpers
+
+add_default_nvpair(Attrs1, N, Default, Attrs2):-
+  add_default_nvpair(Attrs1, N, Default, _, Attrs2).
+
+add_default_nvpair(Attrs, N, _, V, Attrs):-
+  memberchk(N=V, Attrs), !.
+add_default_nvpair(Attrs1, N, Default, Default, Attrs2):-
+  ord_add_element(Attrs1, N=Default, Attrs2).
+
+select_nvpair(N=V, Attrs1, Attrs2, _):-
+  memberchk(N=V, Attrs1), !,
+  select(N=V, Attrs1, Attrs2).
+select_nvpair(_=Default, Attrs, Attrs, Default).
+
+
+extract_shared([], []):- !.
+extract_shared(Argss, Shared):-
+  ord_intersection(Argss, Shared).
+
+remove_shared_attributes(Shared, Args1, Args2):-
+  ord_subtract(Args1, Shared, Args2).
+
+shared_attributes(Terms1, Shared, Terms2):-
+  maplist(term_components(Func), Terms1, Args1, Args2, Args3a),
+  extract_shared(Args3a, Shared),
+  maplist(remove_shared_attributes(Shared), Args3a, Args3b),
+  maplist(term_components(Func), Terms2, Args1, Args2, Args3b).
+
+term_components(Func, Term, Arg1, Arg2, Arg3):-
+  Term =.. [Func,Arg1,Arg2,Arg3].
+
diff --git a/gv_file.pl b/gv_file.pl
new file mode 100644
index 0000000..c780692
--- /dev/null
+++ b/gv_file.pl
@@ -0,0 +1,211 @@
+:- module(
+  gv_file,
+  [
+    gif_to_gv_file/3, % +GraphInterchangeFormat:compound
+                      % ?ToFile:atom
+                      % +Options:list(nvpair)
+    graph_to_svg_dom/3, % +GraphInterchangeFormat:compound
+                        % -SvgDom:list(compound)
+                        % +Options:list(nvpair)
+    open_dot/1 % +File:file
+  ]
+).
+
+/** <module> GraphViz file
+
+Predicates for converting GIF-formatted terms
+into GraphViz output files or SVG DOM structures.
+
+Also converts between GraphViz DOT formatted files
+and GraphViz output files or SVG DOM structures.
+
+@author Wouter Beek
+@version 2011-2013/09, 2013/11-2014/01, 2014/05, 2014/07
+*/
+
+:- use_module(library(option)).
+:- use_module(library(process)).
+:- use_module(library(predicate_options)). % Declarations.
+
+:- use_module(generics(codes_ext)).
+:- use_module(generics(db_ext)).
+:- use_module(generics(error_ext)).
+:- use_module(generics(trees)).
+:- use_module(os(file_ext)).
+:- use_module(os(run_ext)).
+:- use_module(os(safe_file)).
+:- use_module(svg(svg_file)).
+:- use_module(ugraph(ugraph_export)).
+
+:- use_module(plGraphViz(gv_dot)).
+
+:- dynamic(user:file_type_program/2).
+:- multifile(user:file_type_program/2).
+
+:- dynamic(user:module_uses/2).
+:- multifile(user:module_uses/2).
+
+:- dynamic(user:prolog_file_type/2).
+:- multifile(user:prolog_file_type/2).
+
+% Register DOT.
+:- db_add_novel(user:prolog_file_type(dot, dot)).
+:- db_add_novel(user:prolog_file_type(dot, graphviz)).
+:- db_add_novel(user:file_type_program(dot, dotty)).
+:- db_add_novel(user:file_type_program(dot, dotx)).
+:- db_add_novel(user:module_uses(gv_file, file_type(dot))).
+
+% Register JPG/JPEG.
+:- db_add_novel(user:prolog_file_type(jpeg, jpeg)).
+:- db_add_novel(user:prolog_file_type(jpeg, graphviz_output)).
+:- db_add_novel(user:prolog_file_type(jpg, jpeg)).
+:- db_add_novel(user:prolog_file_type(jpg, graphviz_output)).
+
+% Register PDF.
+:- db_add_novel(user:prolog_file_type(pdf, pdf)).
+:- db_add_novel(user:prolog_file_type(pdf, graphviz_output)).
+
+% Register PNG.
+:- db_add_novel(user:prolog_file_type(png, png)).
+:- db_add_novel(user:prolog_file_type(png, graphviz_output)).
+
+% Register SVG.
+:- db_add_novel(user:prolog_file_type(svg, graphviz_output)).
+:- db_add_novel(user:prolog_file_type(svg, svg)).
+
+% Register XDOT.
+:- db_add_novel(user:prolog_file_type(xdot, graphviz_output)).
+:- db_add_novel(user:prolog_file_type(xdot, xdot)).
+
+:- predicate_options(graph_to_svg_dom/3, 3, [
+     pass_to(gif_to_gv_file/3, 3)
+   ]).
+:- predicate_options(gif_to_gv_file/3, 3, [
+     pass_to(to_gv_file/3, 3)
+   ]).
+:- predicate_options(to_gv_file/3, 3, [
+     pass_to(convert_gv/3, 3)
+   ]).
+:- predicate_options(convert_gv/3, 3, [
+     method(+oneof([dot,sfdp])),
+     to_file_type(+oneof([dot,jpeg,pdf,svg,xdot]))
+   ]).
+
+
+
+%! gif_to_gv_file(+Gif:compound, -ToFile:atom, +Options:list(nvpair)) is det.
+% Returns a file containing a GraphViz visualization of the given graph.
+%
+% The following options are supported:
+%   * =|method(+Method:oneof([dot,sfdp])|=
+%     The algorithm used by GraphViz for positioning the tree nodes.
+%     Either =dot= (default) or =sfdp=.
+%   * =|to_file_type(+FileType:oneof([dot,jpeg,pdf,svg,xdot])|=
+%     The file type of the generated GraphViz file.
+%     Default: `pdf`.
+
+gif_to_gv_file(Gif, ToFile, Options):-
+  once(phrase(gv_graph(Gif), Codes)),
+  to_gv_file(Codes, ToFile, Options).
+
+
+%! graph_to_svg_dom(
+%!   +GraphInterchangeFormat:compound,
+%!   -SvgDom:list(compound),
+%!   +Options:list(nvpair)
+%! ) is det.
+
+graph_to_svg_dom(Gif, SvgDom, Options1):-
+  % Make sure the file type of the output file is SvgDom.
+  merge_options([to_file_type=svg], Options1, Options2),
+  gif_to_gv_file(Gif, ToFile, Options2),
+  file_to_svg(ToFile, SvgDom),
+  safe_delete_file(ToFile).
+
+
+%! open_dot(+File:atom) is det.
+% Opens the given DOT file.
+%
+% @tbd Test support on Windows.
+% @tbd Test support on OS-X.
+
+open_dot(File):-
+  once(find_program_by_file_type(dot, Program)),
+  run_program(Program, [File]).
+
+
+
+% SUPPORT PREDICATES %
+
+%! convert_gv(+FromFile:atom, ?ToFile:atom, +Options:list(nvpair)) is det.
+% Converts a GraphViz DOT file to an image file, using a specific
+% visualization method.
+
+convert_gv(FromFile, ToFile, Options):-
+  option(to_file_type(dot), Options), !,
+  rename_file(FromFile, ToFile).
+convert_gv(FromFile, ToFile, Options):-
+  % The input file must be readable.
+  access_file(FromFile, read),
+
+  % The method option.
+  option(method(Method), Options, dot),
+  must_be(oneof([dot,sfdp]), Method),
+
+  % The file type option.
+  option(to_file_type(ToFileType), Options, pdf),
+  prolog_file_type(ToExtension, ToFileType),
+  prolog_file_type(ToExtension, graphviz_output), !,
+
+  % The output file is either given or created.
+  (
+    var(ToFile)
+  ->
+    absolute_file_name(
+      data(export),
+      ToFile,
+      [access(write),file_type(ToFileType)]
+    )
+  ;
+    is_absolute_file_name(ToFile),
+    % The given output file must match a certain file extension.
+    file_name_extension(_, ToExtension, ToFile)
+  ),
+  % Now that we have the output file we can prevent the
+  % file type / file extension translation predicates from bakctracking.
+  !,
+
+  % Run the GraphViz conversion command in the shell.
+  format(atom(OutputType), '-T~w', [ToExtension]),
+  process_create(
+    path(Method),
+    [OutputType,FromFile,'-o',ToFile],
+    [process(PID)]
+  ),
+  process_wait(PID, exit(ShellStatus)),
+  exit_code_handler('GraphViz', ShellStatus).
+
+
+%! to_gv_file(+Codes:list(code), ?ToFile:atom, +Options:list(nvpair)) is det.
+
+to_gv_file(Codes, ToFile, Options):-
+  absolute_file_name(
+    data(tmp),
+    FromFile,
+    [access(write),file_type(graphviz)]
+  ),
+  setup_call_cleanup(
+    open(FromFile, write, Out, [encoding(utf8),type(test)]),
+    put_codes(Out, Codes),
+    close(Out)
+  ),
+  convert_gv(FromFile, ToFile, Options),
+
+  %%%%% DEB: Store DOT file.
+  %%%%ignore((
+  %%%%  file_type_alternative(ToFile, graphviz, DOT_File),
+  %%%%  safe_copy_file(FromFile, DOT_File)
+  %%%%)),
+
+  safe_delete_file(FromFile).
+
diff --git a/gv_html.pl b/gv_html.pl
new file mode 100644
index 0000000..b8d3190
--- /dev/null
+++ b/gv_html.pl
@@ -0,0 +1,60 @@
+:- module(
+  gv_html,
+  [
+    gv_html_like_label//1 % +Codes:list(code)
+  ]
+).
+
+/** <module> GraphViz HTML
+
+@author Wouter Beek
+@version 2013/07, 2013/09, 2014/03-2014/06
+*/
+
+:- use_module(dcg(dcg_content)).
+
+:- use_module(plHtml(html_dcg)).
+
+
+
+%! gv_html_label(+Codes:list(code))// .
+%
+% @see http://www.graphviz.org/doc/info/shapes.html#html
+
+gv_html_label --> gv_html_text, !.
+gv_html_label --> gv_html_table, !.
+gv_html_label --> [].
+
+gv_html_like_label --> bracketed(angular, gv_html_label).
+
+gv_html_like_label(Content) --> bracketed(angular, html_dcg(Content)).
+
+gv_html_table --> html_element(table, _, gv_html_rows).
+gv_html_table --> html_element(font, _, html_element(table, _, gv_html_rows)).
+
+gv_html_rows --> gv_html_row, gv_html_rows.
+gv_html_rows --> gv_html_row, html_element(hr, _), gv_html_rows.
+gv_html_rows --> gv_html_row.
+
+gv_html_row --> html_element(tr, _, gv_html_cells).
+
+gv_html_cell --> html_element(td, _, gv_html_label).
+gv_html_cell --> html_element(td, _, html_element(img, _)).
+
+gv_html_cells --> gv_html_cell, gv_html_cells.
+gv_html_cells --> gv_html_cell.
+gv_html_cells --> gv_html_cell, html_element(vr, _), gv_html_cells.
+
+gv_html_text --> gv_html_textitem, gv_html_text.
+gv_html_text --> gv_html_textitem.
+
+gv_html_textitem --> html_string, !.
+gv_html_textitem --> html_entity, !.
+gv_html_textitem --> html_element(br, _), !.
+gv_html_textitem --> html_element(font, _, gv_html_text), !.
+gv_html_textitem --> html_element(i, _, gv_html_text), !.
+gv_html_textitem --> html_element(b, _, gv_html_text), !.
+gv_html_textitem --> html_element(u, _, gv_html_text), !.
+gv_html_textitem --> html_element(sub, _, gv_html_text), !.
+gv_html_textitem --> html_element(sup, _, gv_html_text), !.
+
diff --git a/gv_numeral.pl b/gv_numeral.pl
new file mode 100644
index 0000000..9b82fc0
--- /dev/null
+++ b/gv_numeral.pl
@@ -0,0 +1,71 @@
+:- module(
+  gv_numeral,
+  [
+    gv_numeral//1 % ?Value:number
+  ]
+).
+
+/** <module> GraphViz numeral
+
+@author Wouter Beek
+@version 2014/05-2014/06
+*/
+
+:- use_module(dcg(dcg_abnf)).
+:- use_module(dcg(dcg_cardinal)).
+:- use_module(math(math_ext)).
+
+
+
+%! gv_numeral(?Value:number)// .
+% ~~~{.bnf}
+% ('-')? ( '.' [0-9]+ | [0-9]+ ( '.' [0-9]* )? )
+% ~~~
+
+gv_numeral(N) -->
+  {nonvar(N)},
+  {number_sign_parts(N, Sign, Abs)},
+  ({Sign =:= -1} -> `-` ; ``),
+  gv_numeral_abs(Abs).
+gv_numeral(N) -->
+  {var(N)},
+  'sign?'(Sign),
+  gv_numeral_abs(Abs),
+  {number_sign_parts(N, Sign, Abs)}.
+
+
+gv_numeral_abs(N) -->
+  {nonvar(N)},
+  {number_integer_parts(N, N1, N2)},
+  (
+    {N2 =:= 0}
+  ->
+    integer(N1)
+  ;
+    {N1 =:= 0}
+  ->
+    `.`,
+    integer(N2)
+  ;
+    integer(N1),
+    '?'((`.`, 'integer?'(N2)))
+  ).
+gv_numeral_abs(N) -->
+  {var(N)},
+  (
+    `.`,
+    integer(N2)
+  ->
+    {N1 = 0}
+  ;
+    integer(N1),
+    (
+      `.`
+    ->
+      'integer?'(N2)
+    ;
+      {N2 = 0}
+    )
+  ),
+  {number_integer_parts(N, N1, N2)}.
+
diff --git a/gv_tree.pl b/gv_tree.pl
new file mode 100644
index 0000000..afeb8f6
--- /dev/null
+++ b/gv_tree.pl
@@ -0,0 +1,43 @@
+:- module(
+  gv_tree,
+  [
+    tree_to_gv_file/3 % +Tree:compound
+                      % ?ToFile:atom
+                      % +Options:list(nvpair)
+  ]
+).
+
+/** <module> GraphViz tree
+
+Export trees to GraphViz.
+
+@author Wouter Beek
+@version 2014/06-2014/07
+*/
+
+:- use_module(library(aggregate)).
+
+:- use_module(generics(trees)).
+
+:- use_module(plGraphViz(gv_file)).
+:- use_module(plGraphViz(gv_gif)).
+
+
+%! tree_to_gv_file(
+%!   +Tree:compound,
+%!   ?ToFile:atom,
+%!   +Options:list(nvpair)
+%! ) is det.
+% Stores the given tree term into a GraphViz file.
+%
+% Options are passed on to create_gif/3 and gif_to_gv_file/3.
+
+tree_to_gv_file(Tree, ToFile, Options):-
+  tree_to_gif(Tree, Gif, Options),
+  gif_to_gv_file(Gif, ToFile, Options).
+
+
+tree_to_gif(H-T, Gif, Options):-
+  tree_to_vertices_edges(Tree, Vs, Es),
+  create_gif(Vs, Es, Gif, Options).
+
diff --git a/index.pl b/index.pl
new file mode 100644
index 0000000..7461d13
--- /dev/null
+++ b/index.pl
@@ -0,0 +1,2 @@
+% Index of project plGraphViz (empty).
+
diff --git a/load.pl b/load.pl
new file mode 100644
index 0000000..4b117d1
--- /dev/null
+++ b/load.pl
@@ -0,0 +1,12 @@
+% Load file for plGraphViz.
+
+:- dynamic(user:prolog/3).
+:- multifile(user:prolog/3).
+   user:project(plGraphViz, 'GraphViz support for SWI-Prolog.', plGraphViz).
+
+:- use_module(load_project).
+:- load_project(plGraphViz, [
+    plc-'Prolog-Library-Collection',
+    plHtml
+]).
+
diff --git a/load_project.pl b/load_project.pl
new file mode 100644
index 0000000..5599b5e
--- /dev/null
+++ b/load_project.pl
@@ -0,0 +1,120 @@
+:- module(
+  load_project,
+  [
+    load_project/2, % +Parent:atom
+                    % +ChildProjects:list(or([atom,pair(atom)]))
+    load_subproject/2, % +ParentFileSearchPath:atom
+                       % +Child:or([atom,pair(atom)])
+    set_data_subdirectory/1 % +ParentDirectory:atom
+  ]
+).
+
+/** <module> Load project
+
+Generic code for loading a project:
+  * Create a subdirectory for data.
+  * Load the root of subprojects onto the file search path.
+  * Load the index of subprojects onto the file search path.
+
+@author Wouter Beek
+@version 2014/06/14
+*/
+
+:- use_module(library(ansi_term)). % Colorized terminal messages.
+:- use_module(library(apply)).
+
+:- dynamic(user:project/2).
+:- multifile(user:project/2).
+:- dynamic(user:project/3).
+:- multifile(user:project/3).
+
+
+
+load_project(Parent, ChildProjects):-
+  parent_alias(Parent, ParentFsp),
+
+  % Entry point.
+  source_file(load_project(_,_), ThisFile),
+  file_directory_name(ThisFile, ThisDir),
+  assert(user:file_search_path(ParentFsp, ThisDir)),
+  assert(user:file_search_path(project, ThisDir)),
+
+  % Set the data subdirectory.
+  set_data_subdirectory(ThisDir),
+
+  % Load the root of submodules onto the file search path.
+  maplist(load_subproject(ParentFsp), ChildProjects),
+
+  % Load the index into the file search path.
+  load_project_index(ParentFsp).
+
+
+
+%! load_subproject(
+%!   +ParentFileSearchPath:atom,
+%!   +Child:or([atom,pair(atom)])
+%! ) is det.
+
+load_subproject(ParentFsp, ChildFsp-ChildDir):- !,
+  load_subproject_file_search_path(ParentFsp, ChildFsp, ChildDir),
+  load_project_index(ChildFsp).
+load_subproject(ParentFsp, Child):-
+  load_subproject(ParentFsp, Child-Child).
+
+
+%! load_subproject_file_search_path(
+%!   +ParentFileSearchPath:atom,
+%!   +ChildFileSearchPath:atom,
+%!   +ChildDirectory:atom
+%! ) is det.
+
+% The file search path for the subproject has already been set.
+load_subproject_file_search_path(_, ChildFsp, _):-
+  user:file_search_path(ChildFsp, _).
+load_subproject_file_search_path(ParentFsp, ChildFsp, ChildDir):-
+  Spec =.. [ParentFsp,ChildDir],
+  absolute_file_name(Spec, _, [access(read),file_type(directory)]), !,
+  assert(user:file_search_path(ChildFsp, Spec)).
+load_subproject_file_search_path(_, ChildFsp, ChildDir):-
+  print_message(warning, missing_subproject_directory(ChildFsp,ChildDir)).
+
+
+%! load_project_index(+FileSearchPath:atom) is det.
+
+load_project_index(Fsp):-
+  Spec =.. [Fsp,index],
+  absolute_file_name(
+    Spec,
+    File,
+    [access(read),file_errors(fail),file_type(prolog)]
+  ), !,
+  ensure_loaded(File).
+load_project_index(_).
+
+
+%! parent_alias(+Parent:atom, -ParentFsp:atom) is det.
+
+parent_alias(Parent, ParentFsp):-
+  user:project(Parent, _, ParentFsp), !.
+parent_alias(Parent, Parent).
+
+
+%! set_data_subdirectory(+ParentDirectory:atom) is det.
+
+set_data_subdirectory(ParentDir):-
+  directory_file_path(ParentDir, data, DataDir),
+  make_directory_path(DataDir),
+  assert(user:file_search_path(data, DataDir)).
+
+
+
+:- multifile(prolog:message//1).
+
+prolog:message(missing_subproject_directory(ChildFsp,ChildDir)) -->
+  [
+    'The ~a submodule is not present.'-[ChildFsp], nl,
+    'Check whether subdirectory ~a is present in your project directory:'-[ChildDir], nl,
+    '    git submodule init', nl,
+    '    git submodule update'
+  ].
+
diff --git a/pack.pl b/pack.pl
deleted file mode 100644
index 989108a..0000000
--- a/pack.pl
+++ /dev/null
@@ -1,9 +0,0 @@
-author('Wouter Beek', 'me@wouterbeek.com').
-download('https://github.com/wouterbeek/plGraphViz/release/*.zip').
-home('https://github.com/wouterbeek/plGraphViz').
-maintainer('Wouter Beek', 'me@wouterbeek.com').
-name(plGraphViz).
-packager('Wouter Beek', 'me@wouterbeek.com').
-requires('Prolog-Library-Collection').
-title(plGraphViz).
-version('0.0.1').
diff --git a/plHtml b/plHtml
new file mode 160000
index 0000000..08480c4
--- /dev/null
+++ b/plHtml
@@ -0,0 +1 @@
+Subproject commit 08480c40acaee5f70bfb21e3c641bef1e7d258bd
diff --git a/prolog/fca/fca_viz.pl b/prolog/fca/fca_viz.pl
deleted file mode 100644
index 083dbb2..0000000
--- a/prolog/fca/fca_viz.pl
+++ /dev/null
@@ -1,101 +0,0 @@
-:- module(
-  fca_viz,
-  [
-    fca_export_graph/2, % +Context, -ExportGraph
-    fca_export_graph/3, % +Context, -ExportGraph, :Opts
-    fca_viz/2,          % +Context, ?File
-    fca_viz/3           % +Context, ?File, :Opts
-  ]
-).
-
-/** <module> FCA visualization
-
-@author Wouter Beek
-@version 2015/11-2016/01
-*/
-
-:- use_module(library(aggregate)).
-:- use_module(library(dcg/dcg_ext)).
-:- use_module(library(dcg/dcg_pl)).
-:- use_module(library(fca/fca)).
-:- use_module(library(graph/build_export_graph)).
-:- use_module(library(graph/s/s_graph)).
-:- use_module(library(gv/gv_file)).
-:- use_module(library(option)).
-:- use_module(library(ordsets)).
-
-:- meta_predicate(fca_export_graph(+,?,:)).
-:- meta_predicate(fca_viz(+,?,:)).
-
-:- predicate_options(fca_export_graph/3, 3, [
-     concept_label(+callable)
-   ]).
-:- predicate_options(fca_viz/3, 3, [
-     pass_to(fca_export_graph/3, 3),
-     pass_to(graph_viz/3, 3)
-   ]).
-
-is_meta(concept_label).
-
-
-
-
-
-%! fca_export_graph(+Context:compound, -ExportGraph:compound) is det.
-% Wrapper around fca_export_graph/3 with default options.
-
-fca_export_graph(Context, ExportG):-
-  fca_export_graph(Context, ExportG, []).
-
-
-%! fca_export_graph(
-%!   +Context:compound,
-%!   -ExportGraph:compound,
-%!   :Options:list(compound)
-%! ) is det.
-% The following optios are supported:
-%   * concept_label(+callable)
-%     DCG writing the labels for individual concepts.
-
-fca_export_graph(Context, ExportG, Opts1):-
-  fca_hasse(Context, Hasse),
-  meta_options(is_meta, Opts1, Opts2),
-  option(concept_label(Label_3), Opts2, concept_label),
-  merge_options(
-    [
-      vertex_label(Label_3),
-      vertex_rank(fca:concept_cardinality)
-    ],
-    Opts2,
-    Opts3
-  ),
-  build_export_graph(Hasse, ExportG, Opts3).
-
-
-
-%! fca_viz(+Context:compound, ?File:atom) is det.
-% Wrapper around fca_viz/3 with default options.
-
-fca_viz(Context, File):-
-  fca_viz(Context, File, []).
-
-
-%! fca_viz(+Context:compound, ?File:atom, :Options:list(compound)) is det.
-
-fca_viz(Context, File, Opts1):-
-  meta_options(is_meta, Opts1, Opts2),
-  statistics(process_cputime, Time1),
-  fca_export_graph(Context, ExportG, Opts2),
-  ExportG = graph(_,_,Es,_),
-  aggregate_all(max(N), (member(edge(V1,V2,_), Es), (N = V1 ; N = V2)), N),
-  ignore(option(number_of_vertices(N), Opts2)),
-  statistics(process_cputime, Time2),
-  Time is Time2 - Time1,
-  ignore(option(process_cputime(Time), Opts2)),
-  graph_viz(ExportG, File, Opts2).
-
-
-
-%! concept_label(+Concept:compound)// is det.
-
-concept_label(concept(Os,As)) --> set(Os), " / ", set(As).
diff --git a/prolog/graph/build_export_graph.pl b/prolog/graph/build_export_graph.pl
deleted file mode 100644
index 5134069..0000000
--- a/prolog/graph/build_export_graph.pl
+++ /dev/null
@@ -1,369 +0,0 @@
-:- module(
-  build_export_graph,
-  [
-    build_export_graph/2, % +Graph, -ExportGraph
-    build_export_graph/3 % +Graph
-                         % -ExportGraph:compound
-                         % +Options:list(compound)
-  ]
-).
-
-/** <module> Build graph representation for exporting
-
-Support for building GIF representations.
-
-# Graph Intermediate Format (GIF)
-
-## Graph
-
-```prolog
-graph(Vs:ordset(compound),Ranks,Es:compound,Attributes:list(compound))
-```
-
-### Edge
-
-```prolog
-edge(FromVertexId,ToVertexId,Attributes:list(compound))
-```
-
-### Rank
-
-```prolog
-RankVertex:compound-ContentVertices:ordset(compound)
-```
-
-### Vertex
-
-```prolog
-vertex(Id,Attributes:list(compound))
-```
-
-# Property functions
-
-Edge label:
-  1. [[graph_edge]] edge_label/2
-
-Vertex coordinates:
-  1. [[circle_coords]] circular_coord/4
-  2. [[random_coords]] random_coord/4
-
----
-
-@author Wouter Beek
-@version 2015/07, 2015/09-2016/01
-*/
-
-:- use_module(library(apply)).
-:- use_module(library(dcg/dcg_ext)).
-:- use_module(library(graph/s/s_graph)).
-:- use_module(library(list_ext)).
-:- use_module(library(option_ext)).
-:- use_module(library(ordsets)).
-:- use_module(library(pairs)).
-
-:- predicate_options(build_export_graph/4, 4, [
-     pass_to(edge_term/3, 3),
-     pass_to(graph_attributes/2, 2),
-     pass_to(vertex_term/3, 3)
-   ]).
-:- predicate_options(edge_term/3, 3, [
-     edge_arrowhead(+callable),
-     edge_color(+callable),
-     edge_id(+callable),
-     edge_label(+callable),
-     edge_penwidth(+callable),
-     edge_style(+callable)
-   ]).
-:- predicate_options(graph_attributes/2, 2, [
-     graph_charset(+oneof(['iso-8859-1','Latin1','UTF-8'])),
-     graph_colorscheme(+oneof([none,svg,x11])),
-     graph_directed(+boolean),
-     graph_fontsize(+float),
-     graph_label(+atom),
-     graph_overlap(+boolean)
-   ]).
-:- predicate_options(vertex_term/3, 3, [
-     vertex_color(+callable),
-     vertex_id(+callable),
-     vertex_image(+callable),
-     vertex_label(+callable),
-     vertex_peripheries(+callable),
-     vertex_position(+callable),
-     vertex_rank(+callable),
-     vertex_shape(+callable),
-     vertex_uri(+callable)
-   ]).
-
-:- meta_predicate(build_export_graph(+,-,:)).
-
-is_meta(edge_arrowhead).
-is_meta(edge_color).
-is_meta(edge_id).
-is_meta(edge_label).
-is_meta(edge_penwidth).
-is_meta(edge_style).
-is_meta(vertex_color).
-is_meta(vertex_id).
-is_meta(vertex_image).
-is_meta(vertex_label).
-is_meta(vertex_peripheries).
-is_meta(vertex_position).
-is_meta(vertex_rank).
-is_meta(vertex_shape).
-is_meta(vertex_uri).
-
-
-
-
-
-%! build_export_graph(+Graph, -ExportGraph:compound) is det.
-% Wrapper around build_export_graph/3 with default options.
-
-build_export_graph(G, ExportG):-
-  build_export_graph(G, ExportG, []).
-
-
-%! build_export_graph(
-%!   +Graph,
-%!   -ExportGraph:compound,
-%!   +Options:list(compound)
-%! ) is det.
-% Graph is either:
-%   * a coumpound term `graph(Vs,Es)`, or
-%   * an unlabeled graph as defined by `library(ugraph)`.
-%
-% The following options are supported:
-%   * `vertex_rank(:RankFunction)`
-%     Assigns a non-negative integer to each vertex.
-%     No default.
-
-build_export_graph(G, graph(VTerms2,VRanks,ETerms,GAttrs), Opts1):-
-  graph_components(G, Vs, Es),
-  meta_options(is_meta, Opts1, Opts2),
-  maplist(vertex_term0(Vs, Opts2), Vs, VTerms1),
-  build_export_ranks(Vs, VTerms1, VRanks, VTerms2, Opts2),
-  maplist(edge_term0(Vs, Opts2), Es, ETerms),
-  graph_attributes(GAttrs, Opts2).
-
-vertex_term0(Vs, Opts, V, VTerm) :- vertex_term(Vs, V, VTerm, Opts).
-
-edge_term0(Vs, Opts, E, ETerm) :- edge_term(Vs, E, ETerm, Opts).
-
-graph_components(graph(Vs,Es), Vs, Es):- !.
-graph_components(G, Vs, Es):-
-  s_graph_components(G, Vs, Es).
-
-build_export_ranks(Vs, VTerms, VRanks, [], Opts):-
-  option(vertex_rank(VRank_2), Opts), !,
-  maplist(VRank_2, Vs, Ranks),
-  pairs_keys_values(Pairs, Ranks, VTerms),
-  group_pairs_by_key(Pairs, GroupedPairs),
-  build_export_rank_terms(GroupedPairs, VRanks).
-build_export_ranks(_, VTerms, [], VTerms, _).
-
-build_export_rank_terms([N-VTerms|T1], [RankTerm-VTerms|T2]):- !,
-  build_export_rank_term(N, RankTerm),
-  build_export_rank_terms(T1, T2).
-build_export_rank_terms([], []).
-
-build_export_rank_term(N, vertex(Id,[label(""),shape(none)])):-
-  format(atom(Id), "r~d", [N]).
-
-
-
-%! edge_term(
-%!   +Vertices:ordset(compound),
-%!   +Edge:compound,
-%!   -EdgeTerm:compound,
-%!   +Options:list(compound)
-%! ) is det.
-% The following options are supported:
-%   * `edge_arrowhead(+callable)`
-%     No default.
-%   * `edge_color(+callable)`
-%     No default.
-%   * `edge_id(+callable)`
-%     Function that assignes the unique identifiers for an edge's
-%     incident vertices.
-%   * `edge_label(+callable)`
-%     No default.
-%   * `edge_penwidth(+callable)`
-%     No default.
-%   * `edge_style(+callable)`
-%     No default.
-
-edge_term(Vs, E, edge(FromId,ToId,EAttrs), Opts):-
-  % Arrowhead
-  if_option(edge_arrowhead(Arrowhead_2), Opts,
-    call(Arrowhead_2, E, EArrowhead)
-  ),
-  
-  % Color.
-  if_option(edge_color(ColorFunction), Opts, call(ColorFunction, E, EColor)),
-  
-  % Id.
-  (   option(edge_id(Id_2), Opts)
-  ->  call(Id_2, E, FromId, ToId)
-  ;   edge_components(E, FromV, ToV),
-      nth0chk(FromId, Vs, FromV),
-      nth0chk(ToId, Vs, ToV)
-  ),
-  
-  % Label.
-  if_option(edge_label(ELabel_3), Opts, string_phrase(dcg_call(ELabel_3, E), ELabel)),
-  
-  % Penwidth.
-  if_option(edge_penwidth(Penwidth_2), Opts, call(Penwidth_2, E, EPenwidth)),
-  
-  % Style.
-  if_option(edge_style(Style_2), Opts, call(Style_2, E, EStyle)),
-  
-  exclude(
-    option_has_var_value,
-    [
-      arrowhead(EArrowhead),
-      color(EColor),
-      label(ELabel),
-      penwidth(EPenwidth),
-      style(EStyle)
-    ],
-    EAttrs
-  ).
-
-
-
-%! graph_attributes(
-%!   -GraphAttributes:list(compound),
-%!   +Options:list(compound)
-%! ) is det.
-% The following options are supported:
-%   * `graph_charset(+oneof(['iso-8859-1','Latin1','UTF-8']))`
-%     The name of the character set that is used to encode text in the graph.
-%     Default: `UTF-8`.
-%   * `graph_colorscheme(+oneof([none,svg,x11]))`
-%     The colorscheme from which the color in this graph are taken.
-%     Default: `x11`.
-%   * `graph_directed(+boolean)`
-%     Whether the graph is directed (`true`) or undirected (`false`).
-%     Default: `false`.
-%   * `graph_fontsize(+float)`
-%     The font size of text in the graph.
-%     Default: `11.0`.
-%   * `graph_label(+atom)`
-%     The graph label.
-%     No default.
-%   * `graph_overlap(+boolean)`
-%     Whether the vertices are allowed to overlap.
-%     Default: `false`.
-
-graph_attributes(GAttrs, Opts):-
-  % Characer set.
-  option(graph_charset(Charset), Opts, 'UTF-8'),
-  % Colorscheme.
-  option(graph_colorscheme(Colorscheme), Opts, x11),
-  % Directed.
-  option(graph_directed(Directed), Opts, false),
-  % Fontsize.
-  option(graph_fontsize(Fontsize), Opts, 11.0),
-  % Label.
-  % Defaults to the empty string.
-  option(graph_label(GLabel), Opts, '""'),
-  % Overlap.
-  option(graph_overlap(Overlap), Opts, false),
-  exclude(
-    option_has_var_value,
-    [
-      charset(Charset),
-      colorscheme(Colorscheme),
-      directed(Directed),
-      fontsize(Fontsize),
-      label(GLabel),
-      overlap(Overlap)
-    ],
-    GAttrs
-  ).
-
-
-
-%! vertex_term(
-%!   +Vertices:ordset(compound),
-%!   +Vertex:compound,
-%!   -VertexTerm:compound,
-%!   +Options:list(compound)
-%! ) is det.
-% The following options are supported:
-%   * `vertex_color(:ColorFunction)`
-%     A function that assigns colors to vertices.
-%     No default.
-%   * `vertex_id(:ColorFunction)`
-%     A functions that assigns unique identifiers to vertices.
-%   * `vertex_image(:ImageFunction)`
-%     A function that assigns images to vertices.
-%     No default.
-%   * `vertex_label(:LabelFunction)`
-%     A function that assigns labels to vertices.
-%     No default.
-%   * `vertex_peripheries(:PeripheriesFunction)`
-%     A function that assinges peripheries to vertices.
-%     No default.
-%   * `vertex_position(:PositionFunction)`
-%     No default.
-%   * `vertex_shape(:ShapeFunction)`
-%     A function that assinges shapes to vertices.
-%     No default.
-%   * `vertex_uri(:UriFunction)`
-
-vertex_term(Vs, V, vertex(VId,VAttrs), Opts):-
-  % Color.
-  if_option(vertex_color(Color_2), Opts, call(Color_2, V, VColor)),
-  
-  % Id.
-  (option(vertex_id(Id_2), Opts) -> call(Id_2, V, VId) ; nth0chk(VId, Vs, V)),
-  
-  % Image.
-  ignore(if_option(vertex_image(Image_2), Opts, call(Image_2, V, VImage))),
-
-  % Label.
-  if_option(vertex_label(VLabel_2), Opts, string_phrase(dcg_call(VLabel_2, V), VLabel)),
-
-  % Peripheries.
-  if_option(vertex_peripheries(Peripheries_2), Opts,
-    call(Peripheries_2, V, VPeripheries)
-  ),
-
-  % Position.
-  if_option(vertex_position(Position_4), Opts,
-    call(Position_4, Vs, Opts, V, VPosition)
-  ),
-
-  % Shape.
-  if_option(vertex_shape(Shape_2), Opts, call(Shape_2, V, VShape)),
-  
-  % URI
-  if_option(vertex_uri(Uri_2), Opts, call(Uri_2, V, VUri)),
-
-  exclude(
-    option_has_var_value,
-    [
-      color(VColor),
-      image(VImage),
-      label(VLabel),
-      peripheries(VPeripheries),
-      pos(VPosition),
-      shape(VShape),
-      'URL'(VUri)
-    ],
-    VAttrs
-  ).
-
-
-
-
-
-% HELPERS %
-
-%! edge_components(+Edge:compound, -FromV, -ToV) is det.
-
-edge_components(edge(FromV,_,ToV), FromV, ToV):- !.
-edge_components(edge(FromV,ToV), FromV, ToV):- !.
-edge_components(FromV-ToV, FromV, ToV):- !.
diff --git a/prolog/gv/gv_attr_type.pl b/prolog/gv/gv_attr_type.pl
deleted file mode 100644
index f55fce5..0000000
--- a/prolog/gv/gv_attr_type.pl
+++ /dev/null
@@ -1,421 +0,0 @@
-:- module(
-  gv_attr_type,
-  [
-    gv_attr_type//1, % ?Type:atom
-    addDouble//1, % +Double:float
-    addPoint//1, % +Point:compound
-    arrowType//1, % +ArrowType:atom
-    bool//1, % +Boolean:boolean
-    clusterMode//1, % +ClusterMode:atom
-    dirType//1, % +DirectionType:oneof([back,both,forward,none])
-    double//1, % +Double:float
-    doubleList//1, % +Doubles:list(float)
-    escString//1,
-    %layerList//1,
-    %layerRange//1,
-    lblString//1,
-    int//1, % +Integer:integer
-    outputMode//1, % +OutputMode:atom
-    %packMode//1,
-    pagedir//1, % +Pagedir:atom
-    point//1, % +Point:compound
-    pointList//1, % +Points:list(compound)
-    %portPos//1,
-    quadType//1, % +QuadType:atom
-    rankType//1, % +RankType:atom
-    rankdir//1, % +RankDirection:atom
-    rect//1, % +Rectangle:compound
-    shape//1,
-    smoothType//1, % +SmoothType:atom
-    %splineType//1,
-    %startType//1,
-    string//1, % ?Content:atom
-    style//2 % +Context:oneof([cluster,edge,node])
-             % +Style:atom
-    %viewPort//1
-  ]
-).
-:- reexport(
-  library(gv/gv_color),
-  [
-    color//1, % +Color:compound
-    colorList//1 % +ColorList:list(compound)
-  ]
-).
-
-/** <module> GraphViz attribute types
-
-@author Wouter Beek
-@version 2015/07, 2015/11, 2016/02
-*/
-
-:- use_module(library(dcg/dcg_ext), except([string//1])).
-:- use_module(library(gv/gv_html)).
-
-
-
-
-
-%! gv_attr_type(?Type:atom) is nondet.
-
-gv_attr_type(addDouble)   --> "addDouble".
-gv_attr_type(addPoint)    --> "addPoint".
-gv_attr_type(arrowType)   --> "arrowType".
-gv_attr_type(bool)        --> "bool".
-gv_attr_type(color)       --> "color".
-gv_attr_type(colorList)   --> "colorList".
-gv_attr_type(clusterMode) --> "clusterMode".
-gv_attr_type(dirType)     --> "dirType".
-gv_attr_type(double)      --> "double".
-gv_attr_type(doubleList)  --> "doubleList".
-gv_attr_type(escString)   --> "escString".
-gv_attr_type(layerList)   --> "layerList".
-gv_attr_type(layerRange)  --> "layerRange".
-gv_attr_type(lblString)   --> "lblString".
-gv_attr_type(int)         --> "int".
-gv_attr_type(outputMode)  --> "outputMode".
-gv_attr_type(packMode)    --> "packMode".
-gv_attr_type(pagedir)     --> "pagedir".
-gv_attr_type(point)       --> "point".
-gv_attr_type(pointList)   --> "pointList".
-gv_attr_type(portPos)     --> "portPos".
-gv_attr_type(quadType)    --> "quadType".
-gv_attr_type(rankType)    --> "rankType".
-gv_attr_type(rankdir)     --> "rankdir".
-gv_attr_type(rect)        --> "rect".
-gv_attr_type(shape)       --> "shape".
-gv_attr_type(smoothType)  --> "smoothType".
-gv_attr_type(splineType)  --> "splineType".
-gv_attr_type(startType)   --> "startType".
-gv_attr_type(string)      --> "string".
-gv_attr_type(style)       --> "style".
-gv_attr_type(viewPort)    --> "viewPort".
-
-
-
-%! addDouble(+Float:float)// .
-% An *addDouble* is represented by a Prolog float.
-
-addDouble(N) --> ("+", ! ; ""), float(N).
-
-
-
-%! addPoint(+Point:compound)// .
-% An *addPoint* is represented by a compound of the following form:
-% `point(X:float,Y:float,InputOnly:boolean)`.
-
-addPoint(Point) --> ("+", ! ; ""), point(Point).
-
-
-
-%! arrowType(+ArrowType:atom)// .
-
-arrowType(V) --> primitive_shape(V).
-arrowType(V) --> derived(V).
-arrowType(V) --> backwards_compatible(V).
-
-primitive_shape(box)     --> "box".
-primitive_shape(crow)    --> "crow".
-primitive_shape(circle)  --> "circle".
-primitive_shape(diamond) --> "diamond".
-primitive_shape(dot)     --> "dot".
-primitive_shape(inv)     --> "inv".
-primitive_shape(none)    --> "none".
-primitive_shape(normal)  --> "normal".
-primitive_shape(tee)     --> "tee".
-primitive_shape(vee)     --> "vee".
-
-derived(odot)     --> "odot".
-derived(invdot)   --> "invdot".
-derived(invodot)  --> "invodot".
-derived(obox)     --> "obox".
-derived(odiamond) --> "odiamond".
-
-backwards_compatible(ediamond) --> "ediamond".
-backwards_compatible(empty)    --> "empty".
-backwards_compatible(halfopen) --> "halfopen".
-backwards_compatible(invempty) --> "invempty".
-backwards_compatible(open)     --> "open".
-
-
-
-%! bool(+Value:boolean)// .
-
-bool(false) --> "false".
-bool(false) --> "no".
-bool(true)  --> "true".
-bool(true)  --> "yes".
-
-
-
-%! clusterMode(+ClusterMode:atom)// .
-
-clusterMode(global) --> "global".
-clusterMode(local) --> "local".
-clusterMode(none) --> "none".
-
-
-
-%! dirType(+DirectionType:oneof([back,both,forward,none]))// .
-
-dirType(back) --> "back".
-dirType(both) --> "both".
-dirType(forward) --> "forward".
-dirType(none) --> "none".
-
-
-
-%! double(+Double:float)// .
-
-double(N) --> float(N).
-
-
-
-%! doubleList(+Doubles:list(float))// .
-
-doubleList([H|T]) --> double(H), (":", !, doubleList(T) ; {T = []}).
-
-
-
-%! escString(+String:or([atom,string]))// .
-% @tbd Support for context-dependent replacements.
-
-escString(S1) -->
-  {(  string(S1)
-  ->  string_phrase(escape_double_quotes, S1, S2)
-  ;   atom_phrase(escape_double_quotes, S1, S2)
-  )},
-  "\"", atom(S2), "\"".
-
-escape_double_quotes, [0'\\,0'"] --> [0'"], !, escape_double_quotes.
-escape_double_quotes, [X]        --> [X],   !, escape_double_quotes.
-escape_double_quotes             --> "".
-
-
-
-% @tbd layerList
-
-
-
-% @tbd layerRange
-
-
-
-%! lblString(+String:compound)// .
-
-lblString(html_like_label(V)) --> gv_html_like_label(V).
-lblString(V) --> escString(V).
-
-
-
-%! int(+Integer:integer)// .
-
-int(V) --> integer(V).
-
-
-
-%! outputMode(+OutputMode:atom)// .
-
-outputMode(breadthfirst) --> "breadthfirst".
-outputMode(edgesfirst)   --> "edgesfirst".
-outputMode(nodesfirst)   --> "nodesfirst".
-
-
-
-% @tbd packMode
-
-
-
-%! pagedir(+PageDirection:atom)// .
-
-pagedir('BL') --> "BL".
-pagedir('BR') --> "BR".
-pagedir('LB') --> "LB".
-pagedir('LT') --> "LT".
-pagedir('RB') --> "RB".
-pagedir('RT') --> "RT".
-pagedir('TL') --> "TL".
-pagedir('TR') --> "TR".
-
-
-
-%! point(+Point:compound)// .
-% A *point* is represented by a compound of the following form:
-% `point(X:float,Y:float,Changeable:boolean)`.
-
-point(point(X,Y,Changeable)) -->
-  float(X), ",", float(Y),
-  input_changeable(Changeable).
-
-input_changeable(false) --> "".