diff --git a/dotparse.cabal b/dotparse.cabal
new file mode 100644
--- /dev/null
+++ b/dotparse.cabal
@@ -0,0 +1,68 @@
+cabal-version:   2.4
+name:            dotparse
+version:         0.0.1
+synopsis:        dot language parsing and printing.
+description:     This package provides parsing and printing of the dot language.
+
+  .
+  == Usage
+  .
+  >>> :set -XOverloadedLabels
+  >>> :set -XOverloadedStrings
+  >>> import DotParse
+  >>> import Chart
+  >>> import DotParse.Examples (exInt)
+  >>> ex <- processGraph exInt
+  >>> writeChartSvg "other/ex.svg" (graphToChartWith defaultChartConfig ex)
+  .
+  ![usage example](docs/other/ex.svg)
+  .
+
+category:        project
+author:          Tony Day
+maintainer:      tonyday567@gmail.com
+copyright:       Tony Day (c) 2022
+license:         BSD-3-Clause
+homepage:        https://github.com/tonyday567/dotparse#readme
+bug-reports:     https://github.com/tonyday567/dotparse/issues
+build-type:      Simple
+extra-doc-files: other/*.svg
+
+source-repository head
+  type:     git
+  location: https://github.com/tonyday567/dotparse
+
+library
+  hs-source-dirs:     src
+  default-extensions:
+  ghc-options:
+    -Wall -Wcompat -Wincomplete-record-updates
+    -Wincomplete-uni-patterns -Wredundant-constraints -fwrite-ide-info
+    -hiedir=.hie
+
+  build-depends:
+    , algebraic-graphs    ^>=0.6
+    , base                >=4.7    && <5
+    , bytestring          ^>=0.11.3
+    , chart-svg           ^>=0.3
+    , containers          ^>=0.6
+    , flatparse           ^>=0.3.2
+    , neat-interpolation  ^>=0.5.1
+    , numhask             ^>=0.10
+    , numhask-space       ^>=0.10
+    , optics-core         ^>=0.4
+    , process-extras      ^>=0.7.4
+    , template-haskell    >=2.16 && < 2.20
+    , text                ^>=1.2
+    , these               ^>=1.1.1
+
+  exposed-modules:
+    DotParse
+    DotParse.Examples
+    DotParse.Examples.NumHask
+    DotParse.FlatParse
+    DotParse.FlatParse.TH
+    DotParse.Types
+
+  other-modules:
+  default-language:   Haskell2010
diff --git a/other/ex.svg b/other/ex.svg
new file mode 100644
--- /dev/null
+++ b/other/ex.svg
@@ -0,0 +1,25 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="444.5569342690883" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-0.25 -489.73 435.64812 489.98012" height="500.0"><style>svg {
+  color-scheme: light dark;
+}
+{
+  .canvas g, .title g, .axisbar g, .ticktext g, .tickglyph g, .ticklines g, .legendContent g text {
+    fill: #0d0d0d;
+  }
+  .ticklines g, .tickglyph g, .legendBorder g {
+    stroke: #0d0d0d;
+  }
+  .legendBorder g {
+    fill: #f0f0f0;
+  }
+}
+@media (prefers-color-scheme:dark) {
+  .canvas g, .title g, .axisbar g, .ticktext g, .tickglyph g, .ticklines g, .legendContent g text {
+    fill: #f0f0f0;
+  }
+  .ticklines g, .tickglyph g, .legendBorder g {
+    stroke: #f0f0f0;
+  }
+  .legendBorder g {
+    fill: #0d0d0d;
+  }
+}</style><g class="chart"><g class="edges"><g fill="rgb(0%, 0%, 0%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.0" stroke-opacity="1.0"><path d="M 365.08,-231.92 C 369.74,-219.55 376.14,-202.55 380.83,-190.12 L 380.94,-189.83"/></g><g fill="rgb(0%, 0%, 0%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.0" stroke-opacity="1.0"><path d="M 345.88,-236.43 C 330.22,-222.58 304.18,-199.5500 288.4,-185.6 L 288.03,-185.27"/></g><g fill="rgb(0%, 0%, 0%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.0" stroke-opacity="1.0"><path d="M 352.92,-231.92 C 348.5200,-220.23 342.56,-204.4 337.96,-192.2100 L 337.85,-191.9200"/></g><g fill="rgb(0%, 0%, 0%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.0" stroke-opacity="1.0"><path d="M 373.16,-160.86 C 369.07,-157.94 364.48,-154.94 360.0,-152.59 C 314.22,-128.55 255.61,-110.4200 226.73,-102.2400 L 226.39,-102.15"/></g><g fill="rgb(0%, 0%, 0%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.0" stroke-opacity="1.0"><path d="M 380.92,-155.6200 C 376.26,-143.2600 369.86,-126.2600 365.17,-113.8300 L 365.06,-113.53"/></g><g fill="rgb(0%, 0%, 0%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.0" stroke-opacity="1.0"><path d="M 393.08,-155.6200 C 397.48,-143.9300 403.44,-128.11 408.04,-115.91 L 408.1500,-115.62"/></g><g fill="rgb(0%, 0%, 0%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.0" stroke-opacity="1.0"><path d="M 206.93,-114.5800 C 203.7400,-142.49 198.0,-199.54 198.0,-248.04 C 198.0,-326.33 198.0,-326.33 198.0,-326.33 C 198.0,-368.23 190.59,-379.02 174.0,-417.4800 C 168.08,-431.2 158.97,-445.51 151.82,-455.7900 L 151.48,-456.2800"/></g><g fill="rgb(0%, 0%, 0%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.0" stroke-opacity="1.0"><path d="M 219.82,-81.8180 C 230.38,-68.517 246.3700,-48.382 257.0,-35.004 L 257.25,-34.689"/></g><g fill="rgb(0%, 0%, 0%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.0" stroke-opacity="1.0"><path d="M 209.7,-78.213 C 210.15,-66.984 210.74,-52.3090 211.21,-40.653 L 211.22,-40.378"/></g><g fill="rgb(0%, 0%, 0%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.0" stroke-opacity="1.0"><path d="M 198.65,-81.47 C 189.33,-68.875 175.63,-50.3620 165.86,-37.1710 L 165.63,-36.861"/></g><g fill="rgb(0%, 0%, 0%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.0" stroke-opacity="1.0"><path d="M 194.12,-85.6940 C 177.33,-74.682 149.19,-56.222 125.0,-40.2960 C 121.09,-37.725 116.9000,-34.956 112.94,-32.344 L 112.56,-32.092"/></g><g fill="rgb(0%, 0%, 0%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.0" stroke-opacity="1.0"><path d="M 157.4200,-463.38 C 188.35,-450.0200 254.93,-421.2500 286.17,-407.76 L 286.53,-407.6"/></g><g fill="rgb(0%, 0%, 0%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.0" stroke-opacity="1.0"><path d="M 125.6700,-461.7600 C 102.19,-448.4 57.559,-423.0000 33.801,-409.4700 L 33.523,-409.32"/></g><g fill="rgb(0%, 0%, 0%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.0" stroke-opacity="1.0"><path d="M 136.37,-453.77 C 129.4000,-427.06 117.0000,-373.02 117.0000,-326.33 C 117.0000,-326.33 117.0000,-326.33 117.0000,-248.04 C 117.0000,-201.1400 114.55,-146.06 113.08,-117.2700 L 113.06,-116.9300"/></g><g fill="rgb(0%, 0%, 0%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.0" stroke-opacity="1.0"><path d="M 296.79,-382.47 C 292.18,-370.61 285.93,-354.5 281.31,-342.58 L 281.2,-342.3000"/></g><g fill="rgb(0%, 0%, 0%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.0" stroke-opacity="1.0"><path d="M 316.44,-387.21 C 331.91,-374.2300 357.26,-352.96 373.54,-339.3 L 373.92,-338.98"/></g><g fill="rgb(0%, 0%, 0%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.0" stroke-opacity="1.0"><path d="M 309.21,-382.47 C 313.56,-371.27 319.38,-356.2800 323.91,-344.61 L 324.02,-344.33"/></g><g fill="rgb(0%, 0%, 0%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.0" stroke-opacity="1.0"><path d="M 288.12,-312.73 C 303.78,-298.8800 329.82,-275.85 345.6,-261.89 L 345.97,-261.56"/></g><g fill="rgb(0%, 0%, 0%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.0" stroke-opacity="1.0"><path d="M 281.29,-308.21 C 285.95,-296.27 292.3,-280.01 297.1,-267.7100 L 297.21,-267.42"/></g><g fill="rgb(0%, 0%, 0%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.0" stroke-opacity="1.0"><path d="M 268.71,-308.21 C 264.11,-296.44 257.88,-280.47 253.1,-268.2400 L 252.99,-267.9500"/></g><g fill="rgb(0%, 0%, 0%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.0" stroke-opacity="1.0"><path d="M 121.5300,-78.586 C 128.3500,-66.5220 137.5,-50.339 144.3500,-38.226 L 144.51,-37.941"/></g><g fill="rgb(0%, 0%, 0%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.0" stroke-opacity="1.0"><path d="M 107.96,-76.697 C 105.56,-65.522 102.51,-51.375 100.09,-40.155 L 100.04,-39.89"/></g><g fill="rgb(0%, 0%, 0%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.0" stroke-opacity="1.0"><path d="M 155.27,-40.632 C 157.03,-69.326 160.0,-124.6400 160.0,-171.74 C 160.0,-326.33 160.0,-326.33 160.0,-326.33 C 160.0,-372.6000 150.26,-426.5 144.73,-453.4 L 144.6600,-453.7100"/></g><g fill="rgb(0%, 0%, 0%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.0" stroke-opacity="1.0"><path d="M 89.524,-39.429 C 80.174,-67.503 64.0,-123.1 64.0,-171.74 C 64.0,-326.33 64.0,-326.33 64.0,-326.33 C 64.0,-380.31 106.53,-433.6800 128.37,-457.52 L 128.63,-457.8000"/></g></g><g class="shapes"><g fill="rgb(2%, 73%, 80%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="0.5" fill-opacity="0.2" stroke-opacity="1.0"><circle cy="-249.04" cx="359.0" r="18.0"/></g><g fill="rgb(2%, 73%, 80%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="0.5" fill-opacity="0.2" stroke-opacity="1.0"><circle cy="-172.74" cx="387.0" r="18.0"/></g><g fill="rgb(2%, 73%, 80%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="0.5" fill-opacity="0.2" stroke-opacity="1.0"><circle cy="-96.445" cx="208.99999999999997" r="18.0"/></g><g fill="rgb(2%, 73%, 80%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="0.5" fill-opacity="0.2" stroke-opacity="1.0"><circle cy="-471.48" cx="141.0" r="18.0"/></g><g fill="rgb(2%, 73%, 80%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="0.5" fill-opacity="0.2" stroke-opacity="1.0"><circle cy="-399.48" cx="303.0" r="18.0"/></g><g fill="rgb(2%, 73%, 80%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="0.5" fill-opacity="0.2" stroke-opacity="1.0"><circle cy="-325.3299999999999" cx="275.0" r="18.0"/></g><g fill="rgb(2%, 73%, 80%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="0.5" fill-opacity="0.2" stroke-opacity="1.0"><circle cy="-172.74" cx="275.0" r="18.0"/></g><g fill="rgb(2%, 73%, 80%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="0.5" fill-opacity="0.2" stroke-opacity="1.0"><circle cy="-96.445" cx="359.0" r="18.0"/></g><g fill="rgb(2%, 73%, 80%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="0.5" fill-opacity="0.2" stroke-opacity="1.0"><circle cy="-20.148" cx="268.0" r="18.0"/></g><g fill="rgb(2%, 73%, 80%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="0.5" fill-opacity="0.2" stroke-opacity="1.0"><circle cy="-399.48" cx="18.0" r="18.0"/></g><g fill="rgb(2%, 73%, 80%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="0.5" fill-opacity="0.2" stroke-opacity="1.0"><circle cy="-325.3299999999999" cx="389.0" r="20.14812"/></g><g fill="rgb(2%, 73%, 80%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="0.5" fill-opacity="0.2" stroke-opacity="1.0"><circle cy="-249.04" cx="304.0" r="19.49832"/></g><g fill="rgb(2%, 73%, 80%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="0.5" fill-opacity="0.2" stroke-opacity="1.0"><circle cy="-172.74" cx="330.99999999999994" r="20.14812"/></g><g fill="rgb(2%, 73%, 80%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="0.5" fill-opacity="0.2" stroke-opacity="1.0"><circle cy="-96.445" cx="415.0" r="20.14812"/></g><g fill="rgb(2%, 73%, 80%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="0.5" fill-opacity="0.2" stroke-opacity="1.0"><circle cy="-20.148" cx="212.0" r="20.14812"/></g><g fill="rgb(2%, 73%, 80%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="0.5" fill-opacity="0.2" stroke-opacity="1.0"><circle cy="-96.445" cx="112.0" r="20.14812"/></g><g fill="rgb(2%, 73%, 80%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="0.5" fill-opacity="0.2" stroke-opacity="1.0"><circle cy="-325.3299999999999" cx="330.99999999999994" r="20.14812"/></g><g fill="rgb(2%, 73%, 80%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="0.5" fill-opacity="0.2" stroke-opacity="1.0"><circle cy="-249.04" cx="246.0" r="20.14812"/></g><g fill="rgb(2%, 73%, 80%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="0.5" fill-opacity="0.2" stroke-opacity="1.0"><circle cy="-20.148" cx="154.0" r="20.14812"/></g><g fill="rgb(2%, 73%, 80%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="0.5" fill-opacity="0.2" stroke-opacity="1.0"><circle cy="-20.148" cx="96.0" r="20.14812"/></g></g><g class="labels"><g fill="rgb(-51%, 30%, 37%)" stroke="none" stroke-width="0.0" font-size="14.0" fill-opacity="1.0" text-anchor="middle"><text x="359.0" y="-245.33999999999997">0</text><text x="387.0" y="-169.04000000000002">1</text><text x="208.99999999999997" y="-92.745">2</text><text x="141.0" y="-467.78000000000003">3</text><text x="303.0" y="-395.78">4</text><text x="275.0" y="-321.63">5</text><text x="275.0" y="-169.04000000000002">6</text><text x="359.0" y="-92.745">7</text><text x="268.0" y="-16.448">8</text><text x="18.0" y="-395.78">9</text><text x="389.0" y="-321.63">10</text><text x="304.0" y="-245.33999999999997">11</text><text x="330.99999999999994" y="-169.04000000000002">12</text><text x="415.0" y="-92.745">13</text><text x="212.0" y="-16.448">14</text><text x="112.0" y="-92.745">15</text><text x="330.99999999999994" y="-321.63">16</text><text x="246.0" y="-245.33999999999997">17</text><text x="154.0" y="-16.448">18</text><text x="96.0" y="-16.448">19</text></g></g></g><g class="hud"></g></svg>
diff --git a/other/ex0.svg b/other/ex0.svg
new file mode 100644
--- /dev/null
+++ b/other/ex0.svg
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 2.49.3 (20211023.0002)
+ -->
+<!-- Pages: 1 -->
+<svg width="8pt" height="8pt"
+ viewBox="0.00 0.00 8.00 8.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 4)">
+<polygon fill="white" stroke="transparent" points="-4,4 -4,-4 4,-4 4,4 -4,4"/>
+</g>
+</svg>
diff --git a/other/ex1.svg b/other/ex1.svg
new file mode 100644
--- /dev/null
+++ b/other/ex1.svg
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 2.49.3 (20211023.0002)
+ -->
+<!-- Title: D Pages: 1 -->
+<svg width="188pt" height="116pt"
+ viewBox="0.00 0.00 188.00 116.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 112)">
+<title>D</title>
+<polygon fill="white" stroke="transparent" points="-4,4 -4,-112 184,-112 184,4 -4,4"/>
+<!-- A -->
+<g id="node1" class="node">
+<title>A</title>
+<polygon fill="none" stroke="black" points="90,-108 63,-90 90,-72 117,-90 90,-108"/>
+<text text-anchor="middle" x="90" y="-86.3" font-family="Times,serif" font-size="14.00">A</text>
+</g>
+<!-- B -->
+<g id="node2" class="node">
+<title>B</title>
+<polygon fill="none" stroke="black" points="54,-36 0,-36 0,0 54,0 54,-36"/>
+<text text-anchor="middle" x="27" y="-14.3" font-family="Times,serif" font-size="14.00">B</text>
+</g>
+<!-- A&#45;&gt;B -->
+<g id="edge1" class="edge">
+<title>A&#45;&gt;B</title>
+<path fill="none" stroke="grey" stroke-dasharray="5,2" d="M80.43,-78.37C72.13,-69.14 59.79,-55.44 49.07,-43.52"/>
+<polygon fill="grey" stroke="grey" points="51.6,-41.1 42.31,-36.01 46.4,-45.78 51.6,-41.1"/>
+</g>
+<!-- C -->
+<g id="node3" class="node">
+<title>C</title>
+<ellipse fill="none" stroke="black" cx="90" cy="-18" rx="18" ry="18"/>
+<text text-anchor="middle" x="90" y="-14.3" font-family="Times,serif" font-size="14.00">C</text>
+</g>
+<!-- A&#45;&gt;C -->
+<g id="edge2" class="edge">
+<title>A&#45;&gt;C</title>
+<path fill="none" stroke="black" d="M88,-71.7C88,-63.98 88,-54.71 88,-46.11"/>
+<path fill="none" stroke="transparent" d="M90,-71.7C90,-63.98 90,-54.71 90,-46.11"/>
+<path fill="none" stroke="black" d="M92,-71.7C92,-63.98 92,-54.71 92,-46.11"/>
+<polygon fill="black" stroke="black" points="93.5,-46.1 90,-36.1 86.5,-46.1 93.5,-46.1"/>
+</g>
+<!-- D -->
+<g id="node4" class="node">
+<title>D</title>
+<ellipse fill="none" stroke="black" cx="153" cy="-18" rx="27" ry="18"/>
+<text text-anchor="middle" x="153" y="-14.3" font-family="Times,serif" font-size="14.00">D</text>
+</g>
+<!-- A&#45;&gt;D -->
+<g id="edge3" class="edge">
+<title>A&#45;&gt;D</title>
+<path fill="none" stroke="black" stroke-width="5" d="M99.57,-78.37C110.24,-66.51 127.58,-47.25 139.59,-33.89"/>
+</g>
+</g>
+</svg>
diff --git a/other/ex10.svg b/other/ex10.svg
new file mode 100644
--- /dev/null
+++ b/other/ex10.svg
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 2.49.3 (20211023.0002)
+ -->
+<!-- Title: H Pages: 1 -->
+<svg width="97pt" height="62pt"
+ viewBox="0.00 0.00 97.00 62.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 58)">
+<title>H</title>
+<polygon fill="white" stroke="transparent" points="-4,4 -4,-58 93,-58 93,4 -4,4"/>
+<!-- aHtmlTable -->
+<g id="node1" class="node">
+<title>aHtmlTable</title>
+<text text-anchor="start" x="18.5" y="-33.8" font-family="Times,serif" font-size="14.00">col 1</text>
+<text text-anchor="start" x="57.5" y="-33.8" font-family="Times,serif" font-size="14.00">foo</text>
+<text text-anchor="start" x="13.5" y="-12.8" font-family="Times,serif" font-size="14.00">COL 2</text>
+<text text-anchor="start" x="58" y="-12.8" font-family="Times,serif" font-size="14.00">bar</text>
+<polygon fill="none" stroke="blue" points="8,-4 8,-50 81,-50 81,-4 8,-4"/>
+</g>
+</g>
+</svg>
diff --git a/other/ex11.svg b/other/ex11.svg
new file mode 100644
--- /dev/null
+++ b/other/ex11.svg
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 2.49.3 (20211023.0002)
+ -->
+<!-- Pages: 1 -->
+<svg width="296pt" height="133pt"
+ viewBox="0.00 0.00 296.00 133.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 129)">
+<polygon fill="white" stroke="transparent" points="-4,4 -4,-129 292,-129 292,4 -4,4"/>
+<!-- tbl -->
+<g id="node1" class="node">
+<title>tbl</title>
+<polygon fill="none" stroke="blue" points="8,-99.5 8,-120.5 127,-120.5 127,-99.5 8,-99.5"/>
+<text text-anchor="start" x="58" y="-106.3" font-family="Times,serif" font-size="14.00">foo</text>
+<polygon fill="none" stroke="blue" points="127,-99.5 127,-120.5 202,-120.5 202,-99.5 127,-99.5"/>
+<text text-anchor="start" x="155.5" y="-106.3" font-family="Times,serif" font-size="14.00">bar</text>
+<polygon fill="none" stroke="blue" points="202,-99.5 202,-120.5 280,-120.5 280,-99.5 202,-99.5"/>
+<text text-anchor="start" x="231" y="-106.3" font-family="Times,serif" font-size="14.00">baz</text>
+<polygon fill="none" stroke="blue" points="8,-24.5 8,-99.5 127,-99.5 127,-24.5 8,-24.5"/>
+<polygon fill="none" stroke="orange" points="14,-72.5 14,-93.5 52,-93.5 52,-72.5 14,-72.5"/>
+<text text-anchor="start" x="19" y="-79.3" font-family="Times,serif" font-size="14.00">one &#160;</text>
+<polygon fill="none" stroke="orange" points="52,-72.5 52,-93.5 87,-93.5 87,-72.5 52,-72.5"/>
+<text text-anchor="start" x="55" y="-79.3" font-family="Times,serif" font-size="14.00">two &#160;</text>
+<polygon fill="none" stroke="orange" points="87,-72.5 87,-93.5 121,-93.5 121,-72.5 87,-72.5"/>
+<text text-anchor="start" x="90" y="-79.3" font-family="Times,serif" font-size="14.00">three</text>
+<polygon fill="none" stroke="orange" points="14,-51.5 14,-72.5 52,-72.5 52,-51.5 14,-51.5"/>
+<text text-anchor="start" x="19.5" y="-58.3" font-family="Times,serif" font-size="14.00">four </text>
+<polygon fill="none" stroke="orange" points="52,-51.5 52,-72.5 87,-72.5 87,-51.5 52,-51.5"/>
+<text text-anchor="start" x="57" y="-58.3" font-family="Times,serif" font-size="14.00">five </text>
+<polygon fill="none" stroke="orange" points="87,-51.5 87,-72.5 121,-72.5 121,-51.5 87,-51.5"/>
+<text text-anchor="start" x="92" y="-58.3" font-family="Times,serif" font-size="14.00">six &#160;</text>
+<polygon fill="none" stroke="orange" points="14,-30.5 14,-51.5 52,-51.5 52,-30.5 14,-30.5"/>
+<text text-anchor="start" x="17" y="-37.3" font-family="Times,serif" font-size="14.00">seven</text>
+<polygon fill="none" stroke="orange" points="52,-30.5 52,-51.5 87,-51.5 87,-30.5 52,-30.5"/>
+<text text-anchor="start" x="55.5" y="-37.3" font-family="Times,serif" font-size="14.00">eight</text>
+<polygon fill="none" stroke="orange" points="87,-30.5 87,-51.5 121,-51.5 121,-30.5 87,-30.5"/>
+<text text-anchor="start" x="90" y="-37.3" font-family="Times,serif" font-size="14.00">nine </text>
+<polygon fill="none" stroke="orange" points="13,-29.5 13,-94.5 122,-94.5 122,-29.5 13,-29.5"/>
+<polygon fill="none" stroke="blue" points="127,-3.5 127,-99.5 280,-99.5 280,-3.5 127,-3.5"/>
+<polygon fill="none" stroke="pink" points="130,-51.5 130,-96.5 175,-96.5 175,-51.5 130,-51.5"/>
+<text text-anchor="start" x="141" y="-70.3" font-family="Times,serif" font-size="14.00">eins</text>
+<polygon fill="none" stroke="pink" points="175,-51.5 175,-96.5 224,-96.5 224,-51.5 175,-51.5"/>
+<text text-anchor="start" x="186" y="-70.3" font-family="Times,serif" font-size="14.00">zwei</text>
+<polygon fill="none" stroke="pink" points="224,-6.5 224,-96.5 277,-96.5 277,-6.5 224,-6.5"/>
+<text text-anchor="start" x="239.5" y="-55.3" font-family="Times,serif" font-size="14.00">drei</text>
+<text text-anchor="start" x="235" y="-40.3" font-family="Times,serif" font-size="14.00">sechs</text>
+<polygon fill="none" stroke="pink" points="130,-6.5 130,-51.5 175,-51.5 175,-6.5 130,-6.5"/>
+<text text-anchor="start" x="141.5" y="-25.3" font-family="Times,serif" font-size="14.00">vier</text>
+<polygon fill="none" stroke="pink" points="175,-6.5 175,-51.5 224,-51.5 224,-6.5 175,-6.5"/>
+<text text-anchor="start" x="187.5" y="-25.3" font-family="Times,serif" font-size="14.00">fünf</text>
+<polygon fill="none" stroke="blue" points="8,-3.5 8,-24.5 127,-24.5 127,-3.5 8,-3.5"/>
+<text text-anchor="start" x="57.5" y="-10.3" font-family="Times,serif" font-size="14.00">abc</text>
+</g>
+</g>
+</svg>
diff --git a/other/ex12.svg b/other/ex12.svg
new file mode 100644
--- /dev/null
+++ b/other/ex12.svg
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 2.49.3 (20211023.0002)
+ -->
+<!-- Title: D Pages: 1 -->
+<svg width="62pt" height="79pt"
+ viewBox="0.00 0.00 62.00 79.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 75)">
+<title>D</title>
+<polygon fill="white" stroke="transparent" points="-4,4 -4,-75 58,-75 58,4 -4,4"/>
+<!-- some_node -->
+<g id="node1" class="node">
+<title>some_node</title>
+<polygon fill="yellow" stroke="transparent" points="13,-45.5 13,-66.5 41,-66.5 41,-45.5 13,-45.5"/>
+<polygon fill="none" stroke="black" points="13,-45.5 13,-66.5 41,-66.5 41,-45.5 13,-45.5"/>
+<text text-anchor="start" x="16" y="-52.3" font-family="Times,serif" font-size="14.00">Foo</text>
+<polygon fill="lightblue" stroke="transparent" points="13,-24.5 13,-45.5 41,-45.5 41,-24.5 13,-24.5"/>
+<polygon fill="none" stroke="black" points="13,-24.5 13,-45.5 41,-45.5 41,-24.5 13,-24.5"/>
+<text text-anchor="start" x="16.5" y="-31.3" font-family="Times,serif" font-size="14.00" fill="#0000ff">Bar</text>
+<polygon fill="#f0e3ff" stroke="transparent" points="13,-3.5 13,-24.5 41,-24.5 41,-3.5 13,-3.5"/>
+<polygon fill="none" stroke="black" points="13,-3.5 13,-24.5 41,-24.5 41,-3.5 13,-3.5"/>
+<text text-anchor="start" x="16" y="-10.3" font-family="Times,serif" font-size="14.00" fill="#ff1020">Baz</text>
+</g>
+</g>
+</svg>
diff --git a/other/ex13.svg b/other/ex13.svg
new file mode 100644
--- /dev/null
+++ b/other/ex13.svg
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 2.49.3 (20211023.0002)
+ -->
+<!-- Title: H Pages: 1 -->
+<svg width="97pt" height="62pt"
+ viewBox="0.00 0.00 97.00 62.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 58)">
+<title>H</title>
+<polygon fill="white" stroke="transparent" points="-4,4 -4,-58 93,-58 93,4 -4,4"/>
+<!-- aHtmlTable -->
+<g id="node1" class="node">
+<title>aHtmlTable</title>
+<text text-anchor="start" x="18.5" y="-33.8" font-family="Times,serif" font-size="14.00">col 1</text>
+<text text-anchor="start" x="57.5" y="-33.8" font-family="Times,serif" font-size="14.00">foo</text>
+<text text-anchor="start" x="13.5" y="-12.8" font-family="Times,serif" font-size="14.00">COL 2</text>
+<text text-anchor="start" x="58" y="-12.8" font-family="Times,serif" font-size="14.00">bar</text>
+<path fill="none" stroke="black" d="M20,-4C20,-4 69,-4 69,-4 75,-4 81,-10 81,-16 81,-16 81,-38 81,-38 81,-44 75,-50 69,-50 69,-50 20,-50 20,-50 14,-50 8,-44 8,-38 8,-38 8,-16 8,-16 8,-10 14,-4 20,-4"/>
+</g>
+</g>
+</svg>
diff --git a/other/ex14.svg b/other/ex14.svg
new file mode 100644
--- /dev/null
+++ b/other/ex14.svg
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 2.49.3 (20211023.0002)
+ -->
+<!-- Title: H Pages: 1 -->
+<svg width="230pt" height="138pt"
+ viewBox="0.00 0.00 230.00 138.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 134)">
+<title>H</title>
+<polygon fill="white" stroke="transparent" points="-4,4 -4,-134 226,-134 226,4 -4,4"/>
+<!-- parent -->
+<g id="node1" class="node">
+<title>parent</title>
+<polygon fill="none" stroke="black" points="11,-102 11,-123 211,-123 211,-102 11,-102"/>
+<text text-anchor="start" x="32.5" y="-108.8" font-family="Times,serif" font-size="14.00">The foo, the bar and the baz</text>
+<polygon fill="none" stroke="black" points="11,-79 11,-100 69,-100 69,-79 11,-79"/>
+<text text-anchor="start" x="14" y="-85.8" font-family="Times,serif" font-size="14.00">First port</text>
+<polygon fill="none" stroke="black" points="71,-79 71,-100 145,-100 145,-79 71,-79"/>
+<text text-anchor="start" x="74" y="-85.8" font-family="Times,serif" font-size="14.00">Second port</text>
+<polygon fill="none" stroke="black" points="147,-79 147,-100 211,-100 211,-79 147,-79"/>
+<text text-anchor="start" x="150" y="-85.8" font-family="Times,serif" font-size="14.00">Third port</text>
+<polygon fill="none" stroke="black" points="8,-76 8,-126 214,-126 214,-76 8,-76"/>
+</g>
+<!-- child_one -->
+<g id="node2" class="node">
+<title>child_one</title>
+<text text-anchor="start" x="32" y="-13.8" font-family="Times,serif" font-size="14.00">1</text>
+<polygon fill="none" stroke="black" points="27,-5.5 27,-30.5 45,-30.5 45,-5.5 27,-5.5"/>
+</g>
+<!-- parent&#45;&gt;child_one -->
+<g id="edge1" class="edge">
+<title>parent:port_one&#45;&gt;child_one</title>
+<path fill="none" stroke="black" d="M40,-78C40,-67.6 39.37,-56.18 38.62,-46.19"/>
+<polygon fill="black" stroke="black" points="42.09,-45.69 37.77,-36.02 35.11,-46.28 42.09,-45.69"/>
+</g>
+<!-- child_two -->
+<g id="node3" class="node">
+<title>child_two</title>
+<text text-anchor="start" x="104" y="-13.8" font-family="Times,serif" font-size="14.00">2</text>
+<polygon fill="none" stroke="black" points="99,-5.5 99,-30.5 117,-30.5 117,-5.5 99,-5.5"/>
+</g>
+<!-- parent&#45;&gt;child_two -->
+<g id="edge2" class="edge">
+<title>parent:port_two&#45;&gt;child_two</title>
+<path fill="none" stroke="black" d="M108,-78C108,-67.62 108,-56.21 108,-46.22"/>
+<polygon fill="black" stroke="black" points="111.5,-46.04 108,-36.04 104.5,-46.04 111.5,-46.04"/>
+</g>
+<!-- child_three -->
+<g id="node4" class="node">
+<title>child_three</title>
+<text text-anchor="start" x="176" y="-13.8" font-family="Times,serif" font-size="14.00">3</text>
+<polygon fill="none" stroke="black" points="171,-5.5 171,-30.5 189,-30.5 189,-5.5 171,-5.5"/>
+</g>
+<!-- parent&#45;&gt;child_three -->
+<g id="edge3" class="edge">
+<title>parent:port_three&#45;&gt;child_three</title>
+<path fill="none" stroke="black" d="M179,-78C179,-67.62 179.16,-56.21 179.35,-46.21"/>
+<polygon fill="black" stroke="black" points="182.85,-46.11 179.56,-36.04 175.85,-45.96 182.85,-46.11"/>
+</g>
+</g>
+</svg>
diff --git a/other/ex15.svg b/other/ex15.svg
new file mode 100644
--- /dev/null
+++ b/other/ex15.svg
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 2.49.3 (20211023.0002)
+ -->
+<!-- Title: D Pages: 1 -->
+<svg width="199pt" height="328pt"
+ viewBox="0.00 0.00 198.50 328.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 324)">
+<title>D</title>
+<polygon fill="white" stroke="transparent" points="-4,4 -4,-324 194.5,-324 194.5,4 -4,4"/>
+<!-- task_menu -->
+<g id="node1" class="node">
+<title>task_menu</title>
+<text text-anchor="start" x="15" y="-306.1" font-family="Sans serif" font-weight="bold" font-size="8.00">Task 1</text>
+<text text-anchor="start" x="15" y="-291.1" font-family="Sans serif" font-size="8.00">Choose Menu</text>
+<text text-anchor="start" x="15" y="-277.1" font-family="Sans serif" font-size="8.00" fill="darkgreen">done</text>
+<polygon fill="none" stroke="black" points="11,-271 11,-316 69,-316 69,-271 11,-271"/>
+</g>
+<!-- task_ingredients -->
+<g id="node2" class="node">
+<title>task_ingredients</title>
+<text text-anchor="start" x="12" y="-217.1" font-family="Sans serif" font-weight="bold" font-size="8.00">Task 2</text>
+<text text-anchor="start" x="12" y="-202.1" font-family="Sans serif" font-size="8.00">Buy ingredients</text>
+<text text-anchor="start" x="12" y="-188.1" font-family="Sans serif" font-size="8.00" fill="darkgreen">done</text>
+<polygon fill="none" stroke="black" points="8,-182 8,-227 72,-227 72,-182 8,-182"/>
+</g>
+<!-- task_menu&#45;&gt;task_ingredients -->
+<g id="edge1" class="edge">
+<title>task_menu&#45;&gt;task_ingredients</title>
+<path fill="none" stroke="black" d="M40,-266.87C40,-258.89 40,-249.93 40,-241.38"/>
+<polygon fill="black" stroke="black" points="43.5,-241.25 40,-231.25 36.5,-241.25 43.5,-241.25"/>
+</g>
+<!-- task_cook -->
+<g id="node4" class="node">
+<title>task_cook</title>
+<text text-anchor="start" x="76" y="-128.1" font-family="Sans serif" font-weight="bold" font-size="8.00">Task 5</text>
+<text text-anchor="start" x="76" y="-113.1" font-family="Sans serif" font-size="8.00">Cook</text>
+<text text-anchor="start" x="76" y="-99.1" font-family="Sans serif" font-size="8.00" fill="red">todo</text>
+<polygon fill="none" stroke="black" points="71.5,-93 71.5,-138 104.5,-138 104.5,-93 71.5,-93"/>
+</g>
+<!-- task_ingredients&#45;&gt;task_cook -->
+<g id="edge2" class="edge">
+<title>task_ingredients&#45;&gt;task_cook</title>
+<path fill="none" stroke="black" d="M54.14,-177.87C58.74,-169.53 63.92,-160.14 68.82,-151.26"/>
+<polygon fill="black" stroke="black" points="72.03,-152.7 73.79,-142.25 65.9,-149.31 72.03,-152.7"/>
+</g>
+<!-- task_invitation -->
+<g id="node3" class="node">
+<title>task_invitation</title>
+<text text-anchor="start" x="111" y="-217.1" font-family="Sans serif" font-weight="bold" font-size="8.00">Task 4</text>
+<text text-anchor="start" x="111" y="-202.1" font-family="Sans serif" font-size="8.00">Send invitation</text>
+<text text-anchor="start" x="111" y="-188.1" font-family="Sans serif" font-size="8.00" fill="darkgreen">done</text>
+<polygon fill="none" stroke="black" points="106.5,-182 106.5,-227 167.5,-227 167.5,-182 106.5,-182"/>
+</g>
+<!-- task_invitation&#45;&gt;task_cook -->
+<g id="edge3" class="edge">
+<title>task_invitation&#45;&gt;task_cook</title>
+<path fill="none" stroke="black" d="M122.56,-177.87C117.82,-169.45 112.47,-159.95 107.42,-150.98"/>
+<polygon fill="black" stroke="black" points="110.46,-149.25 102.5,-142.25 104.36,-152.68 110.46,-149.25"/>
+</g>
+<!-- task_eat -->
+<g id="node6" class="node">
+<title>task_eat</title>
+<text text-anchor="start" x="113" y="-39.1" font-family="Sans serif" font-weight="bold" font-size="8.00">Task 6</text>
+<text text-anchor="start" x="113" y="-24.1" font-family="Sans serif" font-size="8.00">Eat</text>
+<text text-anchor="start" x="113" y="-10.1" font-family="Sans serif" font-size="8.00" fill="red">todo</text>
+<polygon fill="none" stroke="black" points="108.5,-4 108.5,-49 141.5,-49 141.5,-4 108.5,-4"/>
+</g>
+<!-- task_cook&#45;&gt;task_eat -->
+<g id="edge5" class="edge">
+<title>task_cook&#45;&gt;task_eat</title>
+<path fill="none" stroke="black" d="M98.9,-88.87C102.41,-80.62 106.36,-71.34 110.1,-62.54"/>
+<polygon fill="black" stroke="black" points="113.36,-63.82 114.05,-53.25 106.91,-61.08 113.36,-63.82"/>
+</g>
+<!-- task_table -->
+<g id="node5" class="node">
+<title>task_table</title>
+<text text-anchor="start" x="146" y="-128.1" font-family="Sans serif" font-weight="bold" font-size="8.00">Task 3</text>
+<text text-anchor="start" x="146" y="-113.1" font-family="Sans serif" font-size="8.00">Lay table</text>
+<text text-anchor="start" x="146" y="-99.1" font-family="Sans serif" font-size="8.00" fill="red">todo</text>
+<polygon fill="none" stroke="black" points="141.5,-93 141.5,-138 182.5,-138 182.5,-93 141.5,-93"/>
+</g>
+<!-- task_table&#45;&gt;task_eat -->
+<g id="edge4" class="edge">
+<title>task_table&#45;&gt;task_eat</title>
+<path fill="none" stroke="black" d="M151.1,-88.87C147.59,-80.62 143.64,-71.34 139.9,-62.54"/>
+<polygon fill="black" stroke="black" points="143.09,-61.08 135.95,-53.25 136.64,-63.82 143.09,-61.08"/>
+</g>
+</g>
+</svg>
diff --git a/other/ex2.svg b/other/ex2.svg
new file mode 100644
--- /dev/null
+++ b/other/ex2.svg
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 2.49.3 (20211023.0002)
+ -->
+<!-- Title: D Pages: 1 -->
+<svg width="578pt" height="78pt"
+ viewBox="0.00 0.00 578.00 78.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 74)">
+<title>D</title>
+<polygon fill="white" stroke="transparent" points="-4,4 -4,-74 574,-74 574,4 -4,4"/>
+<!-- node_A -->
+<g id="node1" class="node">
+<title>node_A</title>
+<polygon fill="none" stroke="black" points="0,-0.5 0,-69.5 205,-69.5 205,-0.5 0,-0.5"/>
+<text text-anchor="middle" x="51.5" y="-31.3" font-family="Arial" font-size="14.00">shape=record</text>
+<polyline fill="none" stroke="black" points="103,-0.5 103,-69.5 "/>
+<text text-anchor="middle" x="132" y="-54.3" font-family="Arial" font-size="14.00">above</text>
+<polyline fill="none" stroke="black" points="103,-46.5 161,-46.5 "/>
+<text text-anchor="middle" x="132" y="-31.3" font-family="Arial" font-size="14.00">middle</text>
+<polyline fill="none" stroke="black" points="103,-23.5 161,-23.5 "/>
+<text text-anchor="middle" x="132" y="-8.3" font-family="Arial" font-size="14.00">below</text>
+<polyline fill="none" stroke="black" points="161,-0.5 161,-69.5 "/>
+<text text-anchor="middle" x="183" y="-31.3" font-family="Arial" font-size="14.00">right</text>
+</g>
+<!-- node_B -->
+<g id="node2" class="node">
+<title>node_B</title>
+<text text-anchor="middle" x="396.5" y="-31.3" font-family="Arial" font-size="14.00">shape=plaintext|{curly|braces and|bars without}|effect</text>
+</g>
+</g>
+</svg>
diff --git a/other/ex3.svg b/other/ex3.svg
new file mode 100644
--- /dev/null
+++ b/other/ex3.svg
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 2.49.3 (20211023.0002)
+ -->
+<!-- Title: D Pages: 1 -->
+<svg width="206pt" height="188pt"
+ viewBox="0.00 0.00 206.00 188.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 184)">
+<title>D</title>
+<polygon fill="white" stroke="transparent" points="-4,4 -4,-184 202,-184 202,4 -4,4"/>
+<!-- A -->
+<g id="node1" class="node">
+<title>A</title>
+<ellipse fill="none" stroke="black" cx="99" cy="-162" rx="27" ry="18"/>
+<text text-anchor="middle" x="99" y="-158.3" font-family="Times,serif" font-size="14.00">A</text>
+</g>
+<!-- B -->
+<g id="node2" class="node">
+<title>B</title>
+<ellipse fill="none" stroke="black" cx="27" cy="-90" rx="27" ry="18"/>
+<text text-anchor="middle" x="27" y="-86.3" font-family="Times,serif" font-size="14.00">B</text>
+</g>
+<!-- A&#45;&gt;B -->
+<g id="edge1" class="edge">
+<title>A&#45;&gt;B</title>
+<path fill="none" stroke="black" d="M84.43,-146.83C74.25,-136.94 60.48,-123.55 48.97,-112.36"/>
+<polygon fill="black" stroke="black" points="51.41,-109.85 41.8,-105.38 46.53,-114.87 51.41,-109.85"/>
+</g>
+<!-- C -->
+<g id="node3" class="node">
+<title>C</title>
+<ellipse fill="none" stroke="black" cx="99" cy="-90" rx="27" ry="18"/>
+<text text-anchor="middle" x="99" y="-86.3" font-family="Times,serif" font-size="14.00">C</text>
+</g>
+<!-- A&#45;&gt;C -->
+<g id="edge2" class="edge">
+<title>A&#45;&gt;C</title>
+<path fill="none" stroke="black" d="M99,-143.7C99,-135.98 99,-126.71 99,-118.11"/>
+<polygon fill="black" stroke="black" points="102.5,-118.1 99,-108.1 95.5,-118.1 102.5,-118.1"/>
+</g>
+<!-- D -->
+<g id="node4" class="node">
+<title>D</title>
+<ellipse fill="none" stroke="black" cx="171" cy="-90" rx="27" ry="18"/>
+<text text-anchor="middle" x="171" y="-86.3" font-family="Times,serif" font-size="14.00">D</text>
+</g>
+<!-- A&#45;&gt;D -->
+<g id="edge3" class="edge">
+<title>A&#45;&gt;D</title>
+<path fill="none" stroke="black" d="M113.57,-146.83C123.75,-136.94 137.52,-123.55 149.03,-112.36"/>
+<polygon fill="black" stroke="black" points="151.47,-114.87 156.2,-105.38 146.59,-109.85 151.47,-114.87"/>
+</g>
+<!-- F -->
+<g id="node5" class="node">
+<title>F</title>
+<ellipse fill="none" stroke="black" cx="99" cy="-18" rx="27" ry="18"/>
+<text text-anchor="middle" x="99" y="-14.3" font-family="Times,serif" font-size="14.00">F</text>
+</g>
+<!-- B&#45;&gt;F -->
+<g id="edge4" class="edge">
+<title>B&#45;&gt;F</title>
+<path fill="none" stroke="black" d="M41.57,-74.83C51.75,-64.94 65.52,-51.55 77.03,-40.36"/>
+<polygon fill="black" stroke="black" points="79.47,-42.87 84.2,-33.38 74.59,-37.85 79.47,-42.87"/>
+</g>
+<!-- C&#45;&gt;F -->
+<g id="edge5" class="edge">
+<title>C&#45;&gt;F</title>
+<path fill="none" stroke="black" d="M99,-71.7C99,-63.98 99,-54.71 99,-46.11"/>
+<polygon fill="black" stroke="black" points="102.5,-46.1 99,-36.1 95.5,-46.1 102.5,-46.1"/>
+</g>
+<!-- D&#45;&gt;F -->
+<g id="edge6" class="edge">
+<title>D&#45;&gt;F</title>
+<path fill="none" stroke="black" d="M156.43,-74.83C146.25,-64.94 132.48,-51.55 120.97,-40.36"/>
+<polygon fill="black" stroke="black" points="123.41,-37.85 113.8,-33.38 118.53,-42.87 123.41,-37.85"/>
+</g>
+</g>
+</svg>
diff --git a/other/ex4.svg b/other/ex4.svg
new file mode 100644
--- /dev/null
+++ b/other/ex4.svg
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 2.49.3 (20211023.0002)
+ -->
+<!-- Title: L Pages: 1 -->
+<svg width="137pt" height="242pt"
+ viewBox="0.00 0.00 137.00 242.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 238)">
+<title>L</title>
+<polygon fill="white" stroke="transparent" points="-4,4 -4,-238 133,-238 133,4 -4,4"/>
+<!-- a -->
+<g id="node1" class="node">
+<title>a</title>
+<polygon fill="none" stroke="black" points="0,-180.5 0,-233.5 129,-233.5 129,-180.5 0,-180.5"/>
+<text text-anchor="start" x="8" y="-218.3" font-family="Arial" font-size="14.00">one</text>
+<text text-anchor="start" x="8" y="-203.3" font-family="Arial" font-size="14.00">two three</text>
+<text text-anchor="start" x="8" y="-188.3" font-family="Arial" font-size="14.00">four five six seven</text>
+</g>
+<!-- b -->
+<g id="node2" class="node">
+<title>b</title>
+<polygon fill="none" stroke="black" points="0,-90.5 0,-143.5 129,-143.5 129,-90.5 0,-90.5"/>
+<text text-anchor="middle" x="64.5" y="-128.3" font-family="Arial" font-size="14.00">one</text>
+<text text-anchor="middle" x="64.5" y="-113.3" font-family="Arial" font-size="14.00">two three</text>
+<text text-anchor="middle" x="64.5" y="-98.3" font-family="Arial" font-size="14.00">four five six seven</text>
+</g>
+<!-- a&#45;&gt;b -->
+<g id="edge1" class="edge">
+<title>a&#45;&gt;b</title>
+<path fill="none" stroke="black" d="M64.5,-180.07C64.5,-171.84 64.5,-162.58 64.5,-153.78"/>
+<polygon fill="black" stroke="black" points="68,-153.74 64.5,-143.74 61,-153.74 68,-153.74"/>
+</g>
+<!-- c -->
+<g id="node3" class="node">
+<title>c</title>
+<polygon fill="none" stroke="black" points="0,-0.5 0,-53.5 129,-53.5 129,-0.5 0,-0.5"/>
+<text text-anchor="end" x="121" y="-38.3" font-family="Arial" font-size="14.00">one</text>
+<text text-anchor="end" x="121" y="-23.3" font-family="Arial" font-size="14.00">two three</text>
+<text text-anchor="end" x="121" y="-8.3" font-family="Arial" font-size="14.00">four five six seven</text>
+</g>
+<!-- b&#45;&gt;c -->
+<g id="edge2" class="edge">
+<title>b&#45;&gt;c</title>
+<path fill="none" stroke="black" d="M64.5,-90.07C64.5,-81.84 64.5,-72.58 64.5,-63.78"/>
+<polygon fill="black" stroke="black" points="68,-63.74 64.5,-53.74 61,-63.74 68,-63.74"/>
+</g>
+</g>
+</svg>
diff --git a/other/ex5.svg b/other/ex5.svg
new file mode 100644
--- /dev/null
+++ b/other/ex5.svg
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 2.49.3 (20211023.0002)
+ -->
+<!-- Title: D Pages: 1 -->
+<svg width="181pt" height="139pt"
+ viewBox="0.00 0.00 181.00 139.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 135)">
+<title>D</title>
+<polygon fill="white" stroke="transparent" points="-4,4 -4,-135 177,-135 177,4 -4,4"/>
+<text text-anchor="middle" x="86.5" y="-115.8" font-family="Times,serif" font-size="14.00">The foo, the bar and the baz</text>
+<!-- FOO -->
+<g id="node1" class="node">
+<title>FOO</title>
+<text text-anchor="middle" x="86.5" y="-86.3" font-family="Times,serif" font-size="14.00">FOO</text>
+</g>
+<!-- BAR -->
+<g id="node2" class="node">
+<title>BAR</title>
+<text text-anchor="middle" x="50.5" y="-14.3" font-family="Times,serif" font-size="14.00">BAR</text>
+</g>
+<!-- FOO&#45;&gt;BAR -->
+<g id="edge1" class="edge">
+<title>FOO&#45;&gt;BAR</title>
+<path fill="none" stroke="black" d="M77.6,-71.7C73.5,-63.73 68.55,-54.1 64.01,-45.26"/>
+<polygon fill="black" stroke="black" points="66.98,-43.4 59.3,-36.1 60.76,-46.6 66.98,-43.4"/>
+</g>
+<!-- BAZ -->
+<g id="node3" class="node">
+<title>BAZ</title>
+<text text-anchor="middle" x="122.5" y="-14.3" font-family="Times,serif" font-size="14.00">BAZ</text>
+</g>
+<!-- FOO&#45;&gt;BAZ -->
+<g id="edge2" class="edge">
+<title>FOO&#45;&gt;BAZ</title>
+<path fill="none" stroke="black" d="M95.4,-71.7C99.5,-63.73 104.45,-54.1 108.99,-45.26"/>
+<polygon fill="black" stroke="black" points="112.24,-46.6 113.7,-36.1 106.02,-43.4 112.24,-46.6"/>
+</g>
+</g>
+</svg>
diff --git a/other/ex6.svg b/other/ex6.svg
new file mode 100644
--- /dev/null
+++ b/other/ex6.svg
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 2.49.3 (20211023.0002)
+ -->
+<!-- Title: D Pages: 1 -->
+<svg width="134pt" height="172pt"
+ viewBox="0.00 0.00 134.00 172.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 168)">
+<title>D</title>
+<polygon fill="white" stroke="transparent" points="-4,4 -4,-168 130,-168 130,4 -4,4"/>
+<text text-anchor="start" x="38.5" y="-149.8" font-family="Times,serif" font-size="14.00">The </text>
+<text text-anchor="start" x="64.5" y="-149.8" font-family="Times,serif" font-weight="bold" font-size="14.00" fill="red">foo</text>
+<text text-anchor="start" x="83.5" y="-149.8" font-family="Times,serif" font-size="14.00">,</text>
+<text text-anchor="start" x="25.5" y="-131" font-family="Times,serif" font-size="14.00"> the </text>
+<text text-anchor="start" x="50.5" y="-131" font-family="Times,serif" font-size="20.00">bar</text>
+<text text-anchor="start" x="76.5" y="-131" font-family="Times,serif" font-size="14.00"> and</text>
+<text text-anchor="start" x="40.5" y="-115.8" font-family="Times,serif" font-size="14.00"> the </text>
+<text text-anchor="start" x="65.5" y="-115.8" font-family="Times,serif" font-style="italic" font-size="14.00">baz</text>
+<!-- FOO -->
+<g id="node1" class="node">
+<title>FOO</title>
+<text text-anchor="middle" x="63" y="-86.3" font-family="Times,serif" font-size="14.00">FOO</text>
+</g>
+<!-- BAR -->
+<g id="node2" class="node">
+<title>BAR</title>
+<text text-anchor="middle" x="27" y="-14.3" font-family="Times,serif" font-size="14.00">BAR</text>
+</g>
+<!-- FOO&#45;&gt;BAR -->
+<g id="edge1" class="edge">
+<title>FOO&#45;&gt;BAR</title>
+<path fill="none" stroke="black" d="M54.1,-71.7C50,-63.73 45.05,-54.1 40.51,-45.26"/>
+<polygon fill="black" stroke="black" points="43.48,-43.4 35.8,-36.1 37.26,-46.6 43.48,-43.4"/>
+</g>
+<!-- BAZ -->
+<g id="node3" class="node">
+<title>BAZ</title>
+<text text-anchor="middle" x="99" y="-14.3" font-family="Times,serif" font-size="14.00">BAZ</text>
+</g>
+<!-- FOO&#45;&gt;BAZ -->
+<g id="edge2" class="edge">
+<title>FOO&#45;&gt;BAZ</title>
+<path fill="none" stroke="black" d="M71.9,-71.7C76,-63.73 80.95,-54.1 85.49,-45.26"/>
+<polygon fill="black" stroke="black" points="88.74,-46.6 90.2,-36.1 82.52,-43.4 88.74,-46.6"/>
+</g>
+</g>
+</svg>
diff --git a/other/ex7.svg b/other/ex7.svg
new file mode 100644
--- /dev/null
+++ b/other/ex7.svg
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 2.49.3 (20211023.0002)
+ -->
+<!-- Title: R Pages: 1 -->
+<svg width="206pt" height="260pt"
+ viewBox="0.00 0.00 206.00 260.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 256)">
+<title>R</title>
+<polygon fill="white" stroke="transparent" points="-4,4 -4,-256 202,-256 202,4 -4,4"/>
+<!-- rA -->
+<g id="node1" class="node">
+<title>rA</title>
+<ellipse fill="none" stroke="black" cx="27" cy="-162" rx="27" ry="18"/>
+<text text-anchor="middle" x="27" y="-158.3" font-family="Times,serif" font-size="14.00">rA</text>
+</g>
+<!-- sA -->
+<g id="node2" class="node">
+<title>sA</title>
+<ellipse fill="none" stroke="black" cx="99" cy="-162" rx="27" ry="18"/>
+<text text-anchor="middle" x="99" y="-158.3" font-family="Times,serif" font-size="14.00">sA</text>
+</g>
+<!-- rA&#45;&gt;sA -->
+<g id="edge1" class="edge">
+<title>rA&#45;&gt;sA</title>
+<path fill="none" stroke="black" d="M54,-162C56.61,-162 59.23,-162 61.84,-162"/>
+<polygon fill="black" stroke="black" points="61.93,-165.5 71.93,-162 61.93,-158.5 61.93,-165.5"/>
+</g>
+<!-- vB -->
+<g id="node5" class="node">
+<title>vB</title>
+<ellipse fill="none" stroke="black" cx="99" cy="-90" rx="27" ry="18"/>
+<text text-anchor="middle" x="99" y="-86.3" font-family="Times,serif" font-size="14.00">vB</text>
+</g>
+<!-- sA&#45;&gt;vB -->
+<g id="edge2" class="edge">
+<title>sA&#45;&gt;vB</title>
+<path fill="none" stroke="black" d="M99,-143.7C99,-135.98 99,-126.71 99,-118.11"/>
+<polygon fill="black" stroke="black" points="102.5,-118.1 99,-108.1 95.5,-118.1 102.5,-118.1"/>
+</g>
+<!-- tA -->
+<g id="node3" class="node">
+<title>tA</title>
+<ellipse fill="none" stroke="black" cx="171" cy="-162" rx="27" ry="18"/>
+<text text-anchor="middle" x="171" y="-158.3" font-family="Times,serif" font-size="14.00">tA</text>
+</g>
+<!-- uB -->
+<g id="node4" class="node">
+<title>uB</title>
+<ellipse fill="none" stroke="black" cx="27" cy="-90" rx="27" ry="18"/>
+<text text-anchor="middle" x="27" y="-86.3" font-family="Times,serif" font-size="14.00">uB</text>
+</g>
+<!-- uB&#45;&gt;vB -->
+<g id="edge4" class="edge">
+<title>uB&#45;&gt;vB</title>
+<path fill="none" stroke="black" d="M54,-90C56.61,-90 59.23,-90 61.84,-90"/>
+<polygon fill="black" stroke="black" points="61.93,-93.5 71.93,-90 61.93,-86.5 61.93,-93.5"/>
+</g>
+<!-- wB -->
+<g id="node6" class="node">
+<title>wB</title>
+<ellipse fill="none" stroke="black" cx="171" cy="-90" rx="27" ry="18"/>
+<text text-anchor="middle" x="171" y="-86.3" font-family="Times,serif" font-size="14.00">wB</text>
+</g>
+<!-- wB&#45;&gt;tA -->
+<g id="edge6" class="edge">
+<title>wB&#45;&gt;tA</title>
+<path fill="none" stroke="black" d="M171,-108.1C171,-115.79 171,-125.05 171,-133.67"/>
+<polygon fill="black" stroke="black" points="167.5,-133.7 171,-143.7 174.5,-133.7 167.5,-133.7"/>
+</g>
+<!-- u -->
+<g id="node8" class="node">
+<title>u</title>
+<ellipse fill="none" stroke="black" cx="171" cy="-18" rx="27" ry="18"/>
+<text text-anchor="middle" x="171" y="-14.3" font-family="Times,serif" font-size="14.00">u</text>
+</g>
+<!-- wB&#45;&gt;u -->
+<g id="edge5" class="edge">
+<title>wB&#45;&gt;u</title>
+<path fill="none" stroke="black" d="M171,-71.7C171,-63.98 171,-54.71 171,-46.11"/>
+<polygon fill="black" stroke="black" points="174.5,-46.1 171,-36.1 167.5,-46.1 174.5,-46.1"/>
+</g>
+<!-- t -->
+<g id="node7" class="node">
+<title>t</title>
+<ellipse fill="none" stroke="black" cx="27" cy="-234" rx="27" ry="18"/>
+<text text-anchor="middle" x="27" y="-230.3" font-family="Times,serif" font-size="14.00">t</text>
+</g>
+<!-- t&#45;&gt;rA -->
+<g id="edge3" class="edge">
+<title>t&#45;&gt;rA</title>
+<path fill="none" stroke="black" d="M27,-215.7C27,-207.98 27,-198.71 27,-190.11"/>
+<polygon fill="black" stroke="black" points="30.5,-190.1 27,-180.1 23.5,-190.1 30.5,-190.1"/>
+</g>
+</g>
+</svg>
diff --git a/other/ex8.svg b/other/ex8.svg
new file mode 100644
--- /dev/null
+++ b/other/ex8.svg
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 2.49.3 (20211023.0002)
+ -->
+<!-- Title: Q Pages: 1 -->
+<svg width="426pt" height="260pt"
+ viewBox="0.00 0.00 426.44 260.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 256)">
+<title>Q</title>
+<polygon fill="white" stroke="transparent" points="-4,4 -4,-256 422.44,-256 422.44,4 -4,4"/>
+<g id="clust1" class="cluster">
+<title>cluster_R</title>
+<polygon fill="none" stroke="black" points="8,-64 8,-116 402,-116 402,-64 8,-64"/>
+</g>
+<!-- nd_1 -->
+<g id="node1" class="node">
+<title>nd_1</title>
+<ellipse fill="none" stroke="black" cx="191" cy="-234" rx="37.09" ry="18"/>
+<text text-anchor="middle" x="191" y="-230.3" font-family="Times,serif" font-size="14.00">Node 1</text>
+</g>
+<!-- nd_2 -->
+<g id="node2" class="node">
+<title>nd_2</title>
+<ellipse fill="none" stroke="black" cx="191" cy="-162" rx="37.09" ry="18"/>
+<text text-anchor="middle" x="191" y="-158.3" font-family="Times,serif" font-size="14.00">Node 2</text>
+</g>
+<!-- nd_1&#45;&gt;nd_2 -->
+<g id="edge2" class="edge">
+<title>nd_1&#45;&gt;nd_2</title>
+<path fill="none" stroke="black" d="M191,-215.7C191,-207.98 191,-198.71 191,-190.11"/>
+<polygon fill="black" stroke="black" points="194.5,-190.1 191,-180.1 187.5,-190.1 194.5,-190.1"/>
+</g>
+<!-- nd_3 -->
+<g id="node5" class="node">
+<title>nd_3</title>
+<ellipse fill="none" stroke="black" cx="199" cy="-90" rx="37.09" ry="18"/>
+<text text-anchor="middle" x="199" y="-86.3" font-family="Times,serif" font-size="14.00">Node 3</text>
+</g>
+<!-- nd_2&#45;&gt;nd_3 -->
+<g id="edge3" class="edge">
+<title>nd_2&#45;&gt;nd_3</title>
+<path fill="none" stroke="black" d="M192.98,-143.7C193.86,-135.98 194.92,-126.71 195.9,-118.11"/>
+<polygon fill="black" stroke="black" points="199.39,-118.44 197.05,-108.1 192.43,-117.64 199.39,-118.44"/>
+</g>
+<!-- nd_3_a -->
+<g id="node3" class="node">
+<title>nd_3_a</title>
+<ellipse fill="none" stroke="black" cx="332" cy="-162" rx="86.38" ry="18"/>
+<text text-anchor="middle" x="332" y="-158.3" font-family="Times,serif" font-size="14.00">Above Right Node 3</text>
+</g>
+<!-- nd_3_r -->
+<g id="node6" class="node">
+<title>nd_3_r</title>
+<ellipse fill="none" stroke="black" cx="324" cy="-90" rx="69.59" ry="18"/>
+<text text-anchor="middle" x="324" y="-86.3" font-family="Times,serif" font-size="14.00">Right of Node 3</text>
+</g>
+<!-- nd_3_a&#45;&gt;nd_3_r -->
+<g id="edge1" class="edge">
+<title>nd_3_a&#45;&gt;nd_3_r</title>
+<path fill="none" stroke="black" d="M330.02,-143.7C329.14,-135.98 328.08,-126.71 327.1,-118.11"/>
+<polygon fill="black" stroke="black" points="330.57,-117.64 325.95,-108.1 323.61,-118.44 330.57,-117.64"/>
+</g>
+<!-- nd_3_l -->
+<g id="node4" class="node">
+<title>nd_3_l</title>
+<ellipse fill="none" stroke="black" cx="80" cy="-90" rx="64.19" ry="18"/>
+<text text-anchor="middle" x="80" y="-86.3" font-family="Times,serif" font-size="14.00">Left of Node 3</text>
+</g>
+<!-- nd_3_l&#45;&gt;nd_3 -->
+<g id="edge5" class="edge">
+<title>nd_3_l&#45;&gt;nd_3</title>
+<path fill="none" stroke="grey" d="M144.61,-90C150.28,-90 155.94,-90 161.61,-90"/>
+</g>
+<!-- nd_3&#45;&gt;nd_3_r -->
+<g id="edge6" class="edge">
+<title>nd_3&#45;&gt;nd_3_r</title>
+<path fill="none" stroke="grey" d="M236.11,-90C242.17,-90 248.24,-90 254.31,-90"/>
+</g>
+<!-- nd_4 -->
+<g id="node7" class="node">
+<title>nd_4</title>
+<ellipse fill="none" stroke="black" cx="199" cy="-18" rx="37.09" ry="18"/>
+<text text-anchor="middle" x="199" y="-14.3" font-family="Times,serif" font-size="14.00">Node 4</text>
+</g>
+<!-- nd_3&#45;&gt;nd_4 -->
+<g id="edge4" class="edge">
+<title>nd_3&#45;&gt;nd_4</title>
+<path fill="none" stroke="black" d="M199,-71.7C199,-63.98 199,-54.71 199,-46.11"/>
+<polygon fill="black" stroke="black" points="202.5,-46.1 199,-36.1 195.5,-46.1 202.5,-46.1"/>
+</g>
+</g>
+</svg>
diff --git a/other/ex9.svg b/other/ex9.svg
new file mode 100644
--- /dev/null
+++ b/other/ex9.svg
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 2.49.3 (20211023.0002)
+ -->
+<!-- Title: D Pages: 1 -->
+<svg width="461pt" height="177pt"
+ viewBox="0.00 0.00 461.00 177.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 173)">
+<title>D</title>
+<polygon fill="white" stroke="transparent" points="-4,4 -4,-173 457,-173 457,4 -4,4"/>
+<g id="clust1" class="cluster">
+<title>cluster_p</title>
+<polygon fill="none" stroke="black" points="8,-8 8,-161 445,-161 445,-8 8,-8"/>
+<text text-anchor="middle" x="226.5" y="-145.8" font-family="Times,serif" font-size="14.00">Parent</text>
+</g>
+<g id="clust2" class="cluster">
+<title>cluster_c1</title>
+<polygon fill="none" stroke="black" points="96,-16 96,-130 437,-130 437,-16 96,-16"/>
+<text text-anchor="middle" x="266.5" y="-114.8" font-family="Times,serif" font-size="14.00">Child one</text>
+</g>
+<g id="clust3" class="cluster">
+<title>cluster_gc_1</title>
+<polygon fill="none" stroke="black" points="318,-24 318,-99 429,-99 429,-24 318,-24"/>
+<text text-anchor="middle" x="373.5" y="-83.8" font-family="Times,serif" font-size="14.00">Grand&#45;Child one</text>
+</g>
+<g id="clust4" class="cluster">
+<title>cluster_gc_2</title>
+<polygon fill="none" stroke="black" points="168,-24 168,-99 310,-99 310,-24 168,-24"/>
+<text text-anchor="middle" x="239" y="-83.8" font-family="Times,serif" font-size="14.00">Grand&#45;Child two</text>
+</g>
+<g id="clust5" class="cluster">
+<title>cluster_c2</title>
+<polygon fill="none" stroke="black" points="16,-24 16,-99 88,-99 88,-24 16,-24"/>
+<text text-anchor="middle" x="52" y="-83.8" font-family="Times,serif" font-size="14.00">Child two</text>
+</g>
+<!-- a -->
+<g id="node1" class="node">
+<title>a</title>
+<ellipse fill="none" stroke="black" cx="131" cy="-50" rx="27" ry="18"/>
+<text text-anchor="middle" x="131" y="-46.3" font-family="Times,serif" font-size="14.00">a</text>
+</g>
+<!-- b -->
+<g id="node2" class="node">
+<title>b</title>
+<ellipse fill="none" stroke="black" cx="373" cy="-50" rx="27" ry="18"/>
+<text text-anchor="middle" x="373" y="-46.3" font-family="Times,serif" font-size="14.00">b</text>
+</g>
+<!-- c -->
+<g id="node3" class="node">
+<title>c</title>
+<ellipse fill="none" stroke="black" cx="275" cy="-50" rx="27" ry="18"/>
+<text text-anchor="middle" x="275" y="-46.3" font-family="Times,serif" font-size="14.00">c</text>
+</g>
+<!-- d -->
+<g id="node4" class="node">
+<title>d</title>
+<ellipse fill="none" stroke="black" cx="203" cy="-50" rx="27" ry="18"/>
+<text text-anchor="middle" x="203" y="-46.3" font-family="Times,serif" font-size="14.00">d</text>
+</g>
+<!-- e -->
+<g id="node5" class="node">
+<title>e</title>
+<ellipse fill="none" stroke="black" cx="52" cy="-50" rx="27" ry="18"/>
+<text text-anchor="middle" x="52" y="-46.3" font-family="Times,serif" font-size="14.00">e</text>
+</g>
+</g>
+</svg>
diff --git a/other/nh.svg b/other/nh.svg
new file mode 100644
--- /dev/null
+++ b/other/nh.svg
@@ -0,0 +1,25 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="1044.6129846153847" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-0.5000399999999985 -324.5 678.99844 325.0" height="500.0"><style>svg {
+  color-scheme: light dark;
+}
+{
+  .canvas g, .title g, .axisbar g, .ticktext g, .tickglyph g, .ticklines g, .legendContent g text {
+    fill: #0d0d0d;
+  }
+  .ticklines g, .tickglyph g, .legendBorder g {
+    stroke: #0d0d0d;
+  }
+  .legendBorder g {
+    fill: #f0f0f0;
+  }
+}
+@media (prefers-color-scheme:dark) {
+  .canvas g, .title g, .axisbar g, .ticktext g, .tickglyph g, .ticklines g, .legendContent g text {
+    fill: #f0f0f0;
+  }
+  .ticklines g, .tickglyph g, .legendBorder g {
+    stroke: #f0f0f0;
+  }
+  .legendBorder g {
+    fill: #0d0d0d;
+  }
+}</style><g class="chart"><g class="edges"><g fill="rgb(0%, 0%, 0%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.0" stroke-opacity="1.0"><path d="M 261.71,-107.24 C 234.9900,-119.71 198.5900,-136.69 173.91,-148.21 L 173.62,-148.34"/></g><g fill="rgb(0%, 0%, 0%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.0" stroke-opacity="1.0"><path d="M 142.14,-252.3000 C 134.45,-263.07 124.6,-276.87 116.9000,-287.64 L 116.72,-287.9"/></g><g fill="rgb(0%, 0%, 0%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.0" stroke-opacity="1.0"><path d="M 185.57,-252.12 C 205.09,-262.8900 230.21,-276.73 249.85,-287.56 L 250.31,-287.81"/></g><g fill="rgb(0%, 0%, 0%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.0" stroke-opacity="1.0"><path d="M 376.48,-180.1600 C 375.6900,-199.21 372.0100,-230.15 357.5,-252.0000 C 347.61,-266.8900 331.99,-278.84 317.47,-287.6 L 317.13,-287.8000"/></g><g fill="rgb(0%, 0%, 0%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.0" stroke-opacity="1.0"><path d="M 465.63,-36.124 C 446.95,-47.208 422.75,-61.5610 404.29,-72.516 L 403.8500,-72.774"/></g><g fill="rgb(0%, 0%, 0%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.0" stroke-opacity="1.0"><path d="M 376.5,-108.3 C 376.5,-119.07 376.5,-132.87 376.5,-143.64 L 376.5,-143.9"/></g><g fill="rgb(0%, 0%, 0%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.0" stroke-opacity="1.0"><path d="M 349.23,-104.13 C 345.98,-105.5100 342.69,-106.8400 339.5,-108.0000 C 308.87,-119.16 219.46,-142.43 173.89,-154.07 L 173.62,-154.13"/></g><g fill="rgb(0%, 0%, 0%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.0" stroke-opacity="1.0"><path d="M 196.94,-108.3 C 186.63,-119.07 173.43,-132.87 163.11,-143.64 L 162.87,-143.9"/></g><g fill="rgb(0%, 0%, 0%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.0" stroke-opacity="1.0"><path d="M 376.5,-36.303 C 376.5,-47.069 376.5,-60.866 376.5,-71.642 L 376.5,-71.896"/></g><g fill="rgb(0%, 0%, 0%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.0" stroke-opacity="1.0"><path d="M 230.79,-180.12 C 215.26,-190.8900 195.29,-204.7300 179.67,-215.56 L 179.3000,-215.81"/></g><g fill="rgb(0%, 0%, 0%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.0" stroke-opacity="1.0"><path d="M 261.93,-180.3000 C 265.93,-191.07 271.05,-204.8700 275.05,-215.64 L 275.15,-215.9000"/></g><g fill="rgb(0%, 0%, 0%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.0" stroke-opacity="1.0"><path d="M 281.5,-252.3000 C 281.5,-263.07 281.5,-276.87 281.5,-287.64 L 281.5,-287.9"/></g><g fill="rgb(0%, 0%, 0%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.0" stroke-opacity="1.0"><path d="M 140.48,-108.3 C 141.71,-119.07 143.2800,-132.87 144.52,-143.64 L 144.55,-143.9"/></g><g fill="rgb(0%, 0%, 0%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.0" stroke-opacity="1.0"><path d="M 131.67,-33.503 C 134.99,-34.3890 138.29,-35.2310 141.5,-36.0 C 228.49,-56.813 254.19,-45.118 339.5,-72.0 C 342.64,-72.988 345.85,-74.178 349.02,-75.464 L 349.32,-75.589"/></g><g fill="rgb(0%, 0%, 0%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.0" stroke-opacity="1.0"><path d="M 85.48,-36.185 C 87.094,-54.712 91.3430,-84.678 102.5,-108.0000 C 108.8,-121.1700 119.1,-133.83 128.14,-143.4700 L 128.57,-143.93"/></g><g fill="rgb(0%, 0%, 0%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.0" stroke-opacity="1.0"><path d="M 292.65,-36.124 C 309.72,-46.887 331.67,-60.732 348.84,-71.555 L 349.24,-71.81"/></g><g fill="rgb(0%, 0%, 0%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.0" stroke-opacity="1.0"><path d="M 148.48,-180.3000 C 149.71,-191.07 151.28,-204.8700 152.52,-215.64 L 152.55,-215.9000"/></g><g fill="rgb(0%, 0%, 0%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.0" stroke-opacity="1.0"><path d="M 123.76,-180.3000 C 109.61,-191.07 91.476,-204.8700 77.314,-215.64 L 76.98,-215.9000"/></g><g fill="rgb(0%, 0%, 0%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.0" stroke-opacity="1.0"><path d="M 55.1,-107.24 C 74.311,-118.5400 99.821,-133.54 119.01,-144.83 L 119.46,-145.09"/></g><g fill="rgb(0%, 0%, 0%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.0" stroke-opacity="1.0"><path d="M 66.86,-252.3000 C 74.549,-263.07 84.404,-276.87 92.101,-287.64 L 92.283,-287.9"/></g><g fill="rgb(0%, 0%, 0%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.0" stroke-opacity="1.0"><path d="M 220.02,-32.535 C 223.22,-33.729 226.41,-34.9 229.5,-36.0 C 277.9500,-53.2750 291.61,-53.2310 339.5,-72.0 C 342.56,-73.199 345.73,-74.524 348.87,-75.889 L 349.17,-76.021"/></g><g fill="rgb(0%, 0%, 0%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.0" stroke-opacity="1.0"><path d="M 555.07,-36.0340 C 507.09,-50.2640 440.79,-69.93 403.97,-80.854 L 403.53,-80.982"/></g></g><g class="shapes"><g fill="rgb(2%, 73%, 80%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.2" stroke-opacity="1.0"><rect x="71.99992" width="65.00016" y="-324.0" height="36.0"/></g><g fill="rgb(2%, 73%, 80%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.2" stroke-opacity="1.0"><rect x="262.00012" width="68.99976000000004" y="-108.0" height="36.0"/></g><g fill="rgb(2%, 73%, 80%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.2" stroke-opacity="1.0"><rect x="112.9992" width="83.0016" y="-252.0" height="36.0"/></g><g fill="rgb(2%, 73%, 80%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.2" stroke-opacity="1.0"><rect x="344.49996" width="64.00008000000003" y="-180.0" height="36.0"/></g><g fill="rgb(2%, 73%, 80%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.2" stroke-opacity="1.0"><rect x="460.50016000000005" width="67.99968000000001" y="-36.0" height="36.0"/></g><g fill="rgb(2%, 73%, 80%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.2" stroke-opacity="1.0"><rect x="349.5" width="54.0" y="-108.0" height="36.0"/></g><g fill="rgb(2%, 73%, 80%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.2" stroke-opacity="1.0"><rect x="183.50012" width="59.99975999999998" y="-108.0" height="36.0"/></g><g fill="rgb(2%, 73%, 80%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.2" stroke-opacity="1.0"><rect x="310.5012" width="131.99760000000003" y="-36.0" height="36.0"/></g><g fill="rgb(2%, 73%, 80%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.2" stroke-opacity="1.0"><rect x="225.50012" width="59.99976000000001" y="-180.0" height="36.0"/></g><g fill="rgb(2%, 73%, 80%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.2" stroke-opacity="1.0"><rect x="233.5012" width="95.9976" y="-324.0" height="36.0"/></g><g fill="rgb(2%, 73%, 80%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.2" stroke-opacity="1.0"><rect x="214.5004" width="133.99919999999997" y="-252.0" height="36.0"/></g><g fill="rgb(2%, 73%, 80%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.2" stroke-opacity="1.0"><rect x="111.5" width="54.0" y="-108.0" height="36.0"/></g><g fill="rgb(2%, 73%, 80%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.2" stroke-opacity="1.0"><rect x="37.4984" width="94.00319999999999" y="-36.0" height="36.0"/></g><g fill="rgb(2%, 73%, 80%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.2" stroke-opacity="1.0"><rect x="238.5" width="54.0" y="-36.0" height="36.0"/></g><g fill="rgb(2%, 73%, 80%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.2" stroke-opacity="1.0"><rect x="119.5" width="54.0" y="-180.0" height="36.0"/></g><g fill="rgb(2%, 73%, 80%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.2" stroke-opacity="1.0"><rect x="-3.999999999848569e-5" width="55.00008" y="-108.0" height="36.0"/></g><g fill="rgb(2%, 73%, 80%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.2" stroke-opacity="1.0"><rect x="14.500399999999999" width="79.9992" y="-252.0" height="36.0"/></g><g fill="rgb(2%, 73%, 80%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.2" stroke-opacity="1.0"><rect x="151.00012" width="68.99975999999998" y="-36.0" height="36.0"/></g><g fill="rgb(2%, 73%, 80%)" stroke="rgb(-51%, 30%, 37%)" stroke-width="1.0" fill-opacity="0.2" stroke-opacity="1.0"><rect x="547.0016" width="130.9967999999999" y="-36.0" height="36.0"/></g></g><g class="labels"><g fill="rgb(-51%, 30%, 37%)" stroke="none" stroke-width="0.0" font-size="14.0" fill-opacity="1.0" text-anchor="middle"><text x="104.5" y="-302.3"><a href="https://hackage.haskell.org/package/numhask/docs/NumHask-Algebra-Additive.html#t:Additive">Additive</a></text><text x="296.5" y="-86.3"><a href="https://hackage.haskell.org/package/numhask/docs/NumHask-Algebra-Metric.html#t:Direction">Direction</a></text><text x="154.5" y="-230.3"><a href="https://hackage.haskell.org/package/numhask/docs/NumHask-Algebra-Distributive.html#t:Distributive">Distributive</a></text><text x="376.5" y="-158.3"><a href="https://hackage.haskell.org/package/numhask/docs/NumHask-Algebra-Multiplicative.html#t:Divisive">Divisive</a></text><text x="494.50000000000006" y="-14.3"><a href="https://hackage.haskell.org/package/numhask/docs/NumHask-Algebra-Field.html#t:ExpField">ExpField</a></text><text x="376.5" y="-86.3"><a href="https://hackage.haskell.org/package/numhask/docs/NumHask-Algebra-Field.html#t:Field">Field</a></text><text x="213.5" y="-86.3"><a href="https://hackage.haskell.org/package/numhask/docs/NumHask-Data-Integral.html#t:Integral">Integral</a></text><text x="376.5" y="-14.3"><a href="https://hackage.haskell.org/package/numhask/docs/NumHask-Algebra-Field.html#t:LowerBoundedField">LowerBoundedField</a></text><text x="255.5" y="-158.3"><a href="https://hackage.haskell.org/package/numhask/docs/NumHask-Algebra-Module.html#t:Module">Module</a></text><text x="281.5" y="-302.3"><a href="https://hackage.haskell.org/package/numhask/docs/NumHask-Algebra-Multiplicative.html#t:Multiplicative">Multiplicative</a></text><text x="281.5" y="-230.3"><a href="https://hackage.haskell.org/package/numhask/docs/NumHask-Algebra-Module.html#t:MultiplicativeAction">MultiplicativeAction</a></text><text x="138.5" y="-86.3"><a href="https://hackage.haskell.org/package/numhask/docs/NumHask-Algebra-Metric.html#t:Norm">Norm</a></text><text x="84.5" y="-14.3"><a href="https://hackage.haskell.org/package/numhask/docs/NumHask-Algebra-Field.html#t:QuotientField">QuotientField</a></text><text x="265.5" y="-14.3"><a href="https://hackage.haskell.org/package/numhask/docs/NumHask-Data-Rational.html#t:Ratio">Ratio</a></text><text x="146.5" y="-158.3"><a href="https://hackage.haskell.org/package/numhask/docs/NumHask-Algebra-Ring.html#t:Ring">Ring</a></text><text x="27.5" y="-86.3"><a href="https://hackage.haskell.org/package/numhask/docs/NumHask-Algebra-Metric.html#t:Signed">Signed</a></text><text x="54.5" y="-230.3"><a href="https://hackage.haskell.org/package/numhask/docs/NumHask-Algebra-Additive.html#t:Subtractive">Subtractive</a></text><text x="185.5" y="-14.3"><a href="https://hackage.haskell.org/package/numhask/docs/NumHask-Algebra-Field.html#t:TrigField">TrigField</a></text><text x="612.5" y="-14.3"><a href="https://hackage.haskell.org/package/numhask/docs/NumHask-Algebra-Field.html#t:UpperBoundedField">UpperBoundedField</a></text></g></g></g><g class="hud"></g></svg>
diff --git a/src/DotParse.hs b/src/DotParse.hs
new file mode 100644
--- /dev/null
+++ b/src/DotParse.hs
@@ -0,0 +1,23 @@
+{-# OPTIONS_HADDOCK prune #-}
+{-# LANGUAGE AllowAmbiguousTypes #-}
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+-- | Parser & Printer for the dot language of <http://www.graphviz.org/doc/info/lang.html graphviz>
+--
+-- See "DotParse.Examples" for usage.
+--
+module DotParse
+  ( module DotParse.Types,
+    module DotParse.FlatParse,
+    module FlatParse.Basic,
+    module NeatInterpolation,
+  )
+where
+
+import DotParse.FlatParse
+import DotParse.Types
+import FlatParse.Basic hiding (cut, lines)
+import NeatInterpolation
+import Prelude hiding (replicate)
diff --git a/src/DotParse/Examples.hs b/src/DotParse/Examples.hs
new file mode 100644
--- /dev/null
+++ b/src/DotParse/Examples.hs
@@ -0,0 +1,610 @@
+{-# LANGUAGE AllowAmbiguousTypes #-}
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedLabels #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE QuasiQuotes #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TupleSections #-}
+{-# LANGUAGE TypeApplications #-}
+{-# OPTIONS_GHC -Wno-name-shadowing #-}
+{-# OPTIONS_GHC -Wno-unused-local-binds #-}
+
+-- | Examples of conversion from dot ByteStrings
+module DotParse.Examples where
+
+import qualified Algebra.Graph as G
+import Control.Monad
+import Data.ByteString hiding (empty, head, length, map, zip, zipWith)
+import Data.Proxy
+import Data.Text.Encoding (encodeUtf8)
+import DotParse
+import Optics.Core
+import Prelude hiding (replicate)
+
+-- $setup
+-- >>> import DotParse
+-- >>> import Data.Proxy
+-- >>> :set -XOverloadedStrings
+
+-- * examples
+
+-- | minimal definition
+--
+-- >>> runDotParser ex0 :: Graph
+-- Graph {strict = Last {getLast = Just NoMergeEdges}, directed = Last {getLast = Just UnDirected}, graphid = Last {getLast = Nothing}, nodeAttributes = fromList [], graphAttributes = fromList [], edgeAttributes = fromList [], globalAttributes = fromList [], nodes = [], edges = [], subgraphs = []}
+--
+-- >>> testDotParser (Proxy :: Proxy Graph) defaultDotConfig ex0
+ex0 :: ByteString
+ex0 =
+  encodeUtf8
+    [trimming|
+graph {}
+|]
+
+-- | Examples from https://renenyffenegger.ch/notes/tools/Graphviz/examples/index
+--
+-- >>> testDotParser (Proxy :: Proxy Graph) defaultDotConfig ex1
+--
+-- ![Example](other/ex1.svg)
+ex1 :: ByteString
+ex1 =
+  encodeUtf8
+    [trimming|
+digraph D {
+    A [shape=diamond]
+    B [shape=box]
+    C [shape=circle]
+    A -> B [style=dashed, color=grey]
+    A -> C [color="black:invis:black"]
+    A -> D [penwidth=5, arrowhead=none]
+    }
+|]
+
+-- |
+-- >>> testDotParser (Proxy :: Proxy Graph) defaultDotConfig ex2
+--
+-- ![Example](other/ex2.svg)
+ex2 :: ByteString
+ex2 =
+  encodeUtf8
+    [trimming|
+digraph D {
+
+    node [fontname="Arial"];
+
+    node_A [shape=record    label="shape=record|{above|middle|below}|right"];
+    node_B [shape=plaintext label="shape=plaintext|{curly|braces and|bars without}|effect"];
+
+}
+|]
+
+-- |
+-- >>> testDotParser (Proxy :: Proxy Graph) defaultDotConfig ex3
+--
+-- ![Example](other/ex3.svg)
+ex3 :: ByteString
+ex3 =
+  encodeUtf8
+    [trimming|
+digraph D {
+  A -> {B, C, D} -> {F}
+}
+|]
+
+-- |
+--
+-- ![Example](other/ex4.svg)
+ex4 :: ByteString
+ex4 =
+  encodeUtf8
+    [trimming|
+digraph L {
+
+  node [shape=record fontname=Arial];
+
+  a  [label="one\ltwo three\lfour five six seven\l"]
+  b  [label="one\ntwo three\nfour five six seven"]
+  c  [label="one\rtwo three\rfour five six seven\r"]
+
+  a -> b -> c
+
+}
+|]
+
+-- |
+--
+-- ![Example](other/ex5.svg)
+ex5 :: ByteString
+ex5 =
+  encodeUtf8
+    [trimming|
+digraph D {
+
+  label = "The foo, the bar and the baz";
+  labelloc = "t"; // place the label at the top (b seems to be default)
+
+  node [shape=plaintext]
+
+  FOO -> {BAR, BAZ};
+
+
+}
+|]
+
+-- |
+--
+-- ![Example](other/ex6.svg)
+ex6 :: ByteString
+ex6 =
+  encodeUtf8
+    [trimming|
+digraph D {
+
+  label = <The <font color='red'><b>foo</b></font>,<br/> the <font point-size='20'>bar</font> and<br/> the <i>baz</i>>;
+  labelloc = "t"; // place the label at the top (b seems to be default)
+
+  node [shape=plaintext]
+
+  FOO -> {BAR, BAZ};
+
+}
+|]
+
+-- |
+--
+-- ![Example](other/ex7.svg)
+ex7 :: ByteString
+ex7 =
+  encodeUtf8
+    [trimming|
+digraph R {
+
+  { rank=same rA sA tA }
+  { rank=same uB vB wB }
+
+
+   rA -> sA;
+   sA -> vB;
+   t  -> rA;
+   uB -> vB;
+   wB -> u;
+   wB -> tA;
+
+}
+|]
+
+-- |
+--
+-- ![Example](other/ex8.svg)
+ex8 :: ByteString
+ex8 =
+  encodeUtf8
+    [trimming|
+digraph Q {
+
+  nd_1   [label = "Node 1"];
+  nd_2   [label = "Node 2"];
+  nd_3_a [label = "Above Right Node 3"];
+  nd_3_l [label = "Left of Node 3"];
+  nd_3   [label = "Node 3"];
+  nd_3_r [label = "Right of Node 3"];
+  nd_4   [label = "Node 4"];
+
+
+  nd_3_a -> nd_3_r;
+  nd_1 -> nd_2 -> nd_3 -> nd_4;
+
+  subgraph cluster_R {
+
+    {rank=same nd_3_l nd_3 nd_3_r}
+
+    nd_3_l -> nd_3 -> nd_3_r [color=grey arrowhead=none];
+
+  }
+
+}
+|]
+
+-- |
+--
+-- ![Example](other/ex9.svg)
+ex9 :: ByteString
+ex9 =
+  encodeUtf8
+    [trimming|
+digraph D {
+
+  subgraph cluster_p {
+    label = "Parent";
+
+    subgraph cluster_c1 {
+      label = "Child one";
+      a;
+
+      subgraph cluster_gc_1 {
+        label = "Grand-Child one";
+         b;
+      }
+      subgraph cluster_gc_2 {
+        label = "Grand-Child two";
+          c;
+          d;
+      }
+
+    }
+
+    subgraph cluster_c2 {
+      label = "Child two";
+      e;
+    }
+  }
+}
+|]
+
+-- |
+--
+-- ![Example](other/ex10.svg)
+ex10 :: ByteString
+ex10 =
+  encodeUtf8
+    [trimming|
+digraph H {
+
+  aHtmlTable [
+   shape=plaintext
+   color=blue      // The color of the border of the table
+   label=<
+
+     <table border='1' cellborder='0'>
+       <tr><td>col 1</td><td>foo</td></tr>
+       <tr><td>COL 2</td><td>bar</td></tr>
+     </table>
+
+  >];
+
+}|]
+
+-- |
+--
+-- ![Example](other/ex11.svg)
+ex11 :: ByteString
+ex11 =
+  encodeUtf8
+    [trimming|
+digraph {
+
+  tbl [
+
+    shape=plaintext
+    label=<
+
+      <table border='0' cellborder='1' color='blue' cellspacing='0'>
+        <tr><td>foo</td><td>bar</td><td>baz</td></tr>
+        <tr><td cellpadding='4'>
+          <table color='orange' cellspacing='0'>
+            <tr><td>one  </td><td>two  </td><td>three</td></tr>
+            <tr><td>four </td><td>five </td><td>six  </td></tr>
+            <tr><td>seven</td><td>eight</td><td>nine </td></tr>
+          </table>
+        </td>
+        <td colspan='2' rowspan='2'>
+          <table color='pink' border='0' cellborder='1' cellpadding='10' cellspacing='0'>
+            <tr><td>eins</td><td>zwei</td><td rowspan='2'>drei<br/>sechs</td></tr>
+            <tr><td>vier</td><td>fünf</td>                             </tr>
+          </table>
+        </td>
+        </tr>
+
+        <tr><td>abc</td></tr>
+
+      </table>
+
+    >];
+
+}
+|]
+
+-- |
+--
+-- ![Example](other/ex12.svg)
+ex12 :: ByteString
+ex12 =
+  encodeUtf8
+    [trimming|
+digraph D {
+
+  node [shape=plaintext]
+
+  some_node [
+   label=<
+     <table border="0" cellborder="1" cellspacing="0">
+       <tr><td bgcolor="yellow">Foo</td></tr>
+       <tr><td bgcolor="lightblue"><font color="#0000ff">Bar</font></td></tr>
+       <tr><td bgcolor="#f0e3ff"><font color="#ff1020">Baz</font></td></tr>
+     </table>>
+  ];
+}
+|]
+
+-- |
+--
+-- ![Example](other/ex13.svg)
+ex13 :: ByteString
+ex13 =
+  encodeUtf8
+    [trimming|
+digraph H {
+
+  aHtmlTable [
+   shape=plaintext
+   label=<
+
+     <table border='1' cellborder='0' style='rounded'>
+       <tr><td>col 1</td><td>foo</td></tr>
+       <tr><td>COL 2</td><td>bar</td></tr>
+     </table>
+
+  >];
+
+}
+|]
+
+-- |
+--
+-- ![Example](other/ex14.svg)
+ex14 :: ByteString
+ex14 =
+  encodeUtf8
+    [trimming|
+digraph H {
+
+  parent [
+   shape=plaintext
+   label=<
+     <table border='1' cellborder='1'>
+       <tr><td colspan="3">The foo, the bar and the baz</td></tr>
+       <tr><td port='port_one'>First port</td><td port='port_two'>Second port</td><td port='port_three'>Third port</td></tr>
+     </table>
+  >];
+
+  child_one [
+   shape=plaintext
+   label=<
+     <table border='1' cellborder='0'>
+       <tr><td>1</td></tr>
+     </table>
+  >];
+
+  child_two [
+   shape=plaintext
+   label=<
+     <table border='1' cellborder='0'>
+       <tr><td>2</td></tr>
+     </table>
+  >];
+
+  child_three [
+   shape=plaintext
+   label=<
+     <table border='1' cellborder='0'>
+       <tr><td>3</td></tr>
+     </table>
+  >];
+
+  parent:port_one   -> child_one;
+  parent:port_two   -> child_two;
+  parent:port_three -> child_three;
+
+}
+|]
+
+-- |
+--
+-- ![Example](other/ex15.svg)
+ex15 :: ByteString
+ex15 =
+  encodeUtf8
+    [trimming|
+digraph D {
+
+  node [shape=plaintext fontname="Sans serif" fontsize="8"];
+
+  task_menu [ label=<
+   <table border="1" cellborder="0" cellspacing="1">
+     <tr><td align="left"><b>Task 1</b></td></tr>
+     <tr><td align="left">Choose Menu</td></tr>
+     <tr><td align="left"><font color="darkgreen">done</font></td></tr>
+   </table>>];
+
+  task_ingredients [ label=<
+   <table border="1" cellborder="0" cellspacing="1">
+     <tr><td align="left"><b>Task 2</b></td></tr>
+     <tr><td align="left">Buy ingredients</td></tr>
+     <tr><td align="left"><font color="darkgreen">done</font></td></tr>
+   </table>>];
+
+  task_invitation [ label=<
+   <table border="1" cellborder="0" cellspacing="1">
+     <tr><td align="left"><b>Task 4</b></td></tr>
+     <tr><td align="left">Send invitation</td></tr>
+     <tr><td align="left"><font color="darkgreen">done</font></td></tr>
+   </table>>];
+
+  task_cook [ label=<
+   <table border="1" cellborder="0" cellspacing="1">
+     <tr><td align="left"><b>Task 5</b></td></tr>
+     <tr><td align="left">Cook</td></tr>
+     <tr><td align="left"><font color="red">todo</font></td></tr>
+   </table>>];
+
+  task_table[ label=<
+   <table border="1" cellborder="0" cellspacing="1">
+     <tr><td align="left"><b>Task 3</b></td></tr>
+     <tr><td align="left">Lay table</td></tr>
+     <tr><td align="left"><font color="red">todo</font></td></tr>
+   </table>>];
+
+  task_eat[ label=<
+   <table border="1" cellborder="0" cellspacing="1">
+     <tr><td align="left"><b>Task 6</b></td></tr>
+     <tr><td align="left">Eat</td></tr>
+     <tr><td align="left"><font color="red">todo</font></td></tr>
+   </table>>];
+
+
+  task_menu        -> task_ingredients;
+  task_ingredients -> task_cook;
+  task_invitation  -> task_cook;
+  task_table       -> task_eat;
+  task_cook        -> task_eat;
+
+}
+|]
+
+-- | Test all the examples
+testAll :: IO ()
+testAll =
+  zipWithM_
+    (>>)
+    ( putStrLn
+        <$> [ "ex0",
+              "ex1",
+              "ex2",
+              "ex3",
+              "ex4",
+              "ex5",
+              "ex6",
+              "ex7",
+              "ex8",
+              "ex9",
+              "ex10",
+              "ex11",
+              "ex12",
+              "ex13",
+              "ex14",
+              "ex15"
+            ]
+    )
+    ( testDotParser (Proxy :: Proxy Graph) defaultDotConfig
+        <$> [ex0, ex1, ex2, ex3, ex4, ex5, ex6, ex7, ex8, ex9, ex10, ex11, ex12, ex13, ex14, ex15]
+    )
+
+-- | Render all the examples
+svgAll :: IO ()
+svgAll =
+  zipWithM_
+    (>>)
+    ( putStrLn
+        <$> [ "ex0",
+              "ex1",
+              "ex2",
+              "ex3",
+              "ex4",
+              "ex5",
+              "ex6",
+              "ex7",
+              "ex8",
+              "ex9",
+              "ex10",
+              "ex11",
+              "ex12",
+              "ex13",
+              "ex14",
+              "ex15"
+            ]
+    )
+    ( zipWith
+        (\b f -> processDotWith Directed ["-Tsvg", "-oother/" <> f <> ".svg"] b)
+        [ex0, ex1, ex2, ex3, ex4, ex5, ex6, ex7, ex8, ex9, ex10, ex11, ex12, ex13, ex14, ex15]
+        [ "ex0",
+          "ex1",
+          "ex2",
+          "ex3",
+          "ex4",
+          "ex5",
+          "ex6",
+          "ex7",
+          "ex8",
+          "ex9",
+          "ex10",
+          "ex11",
+          "ex12",
+          "ex13",
+          "ex14",
+          "ex15"
+        ]
+    )
+
+-- | algebraic graph example
+exGInt :: G.Graph Int
+exGInt =
+  G.edges $
+    [(v, (v + 1) `mod` 6) | v <- [0 .. 5]]
+      ++ [(v, v + k) | v <- [0 .. 5], k <- [6, 12]]
+      ++ [(2, 18), (2, 19), (15, 18), (15, 19), (18, 3), (19, 3)]
+
+-- |
+--
+-- > exInt = defaultGraph & addStatements (toStatements Directed (packUTF8 . show <$> exGInt))
+-- > import qualified Data.ByteString.Char8 as B
+-- > g <- processGraph exInt
+-- > B.putStrLn $ dotPrint g
+-- digraph {
+--     graph [bb="0,0,495.65,493.78";overlap=false;size="1!";splines=spline]
+--     node [height=0.5;label="\N";shape=circle]
+--     edge [arrowsize=0]
+--     0 [pos="384.5,475.78";width=0.5]
+--     1 [pos="357.5,401.63";width=0.5]
+--     0 -> 1 [pos="e,363.57,418.85 378.51,458.77 374.1,446.99 368.12,431.02 363.67,419.13"]
+--     6 [pos="411.5,401.63";width=0.5]
+--     0 -> 6 [pos="e,405.43,418.85 390.49,458.77 394.9,446.99 400.87,431.02 405.32,419.13"]
+--     12 [height=0.55967;pos="467.5,401.63";width=0.55967]
+--     0 -> 12 [pos="e,452.8,415.41 397.83,463.19 412.75,450.22 436.85,429.27 452.43,415.73"]
+--     2 [pos="330.5,325.33";width=0.5]
+--     1 -> 2 [pos="e,336.35,342.42 351.64,384.51 347.15,372.14 340.97,355.15 336.45,342.72"]
+--     7 [pos="384.5,325.33";width=0.5]
+--     1 -> 7 [pos="e,378.65,342.42 363.36,384.51 367.85,372.14 374.03,355.15 378.54,342.72"]
+--     13 [height=0.55967;pos="440.5,325.33";width=0.55967]
+--     1 -> 13 [pos="e,425.95,339.36 370.47,389.02 385.4,375.66 409.87,353.75 425.58,339.69"]
+--     3 [pos="263.5,249.04";width=0.5]
+--     2 -> 3 [pos="e,275.26,263.08 318.83,311.39 306.7,297.94 287.81,277 275.55,263.4"]
+--     8 [pos="419.5,249.04";width=0.5]
+--     2 -> 8 [pos="e,406.15,261.18 344.02,313.05 360.71,299.11 388.95,275.54 405.75,261.51"]
+--     14 [height=0.55967;pos="475.5,249.04";width=0.55967]
+--     2 -> 14 [pos="e,459.28,261.56 344.39,313.55 348.48,310.63 353.06,307.61 357.5,305.19 394.93,284.71 408.73,289.04 446.5,269.19 450.64,267.01 454.93,\
+-- 264.4 458.9,261.81"]
+--     18 [height=0.55967;pos="239.5,96.445";width=0.55967]
+--     2 -> 18 [pos="e,221.18,105.3 313.92,317.87 277.75,302.66 192.9,260.73 166.5,192.89 160,176.2 158.52,168.63 166.5,152.59 177.74,130.02 203.11,114.24 \
+-- 220.76,105.5"]
+--     19 [height=0.55967;pos="335.5,96.445";width=0.55967]
+--     2 -> 19 [pos="e,335.94,116.84 331.52,307.33 332.98,282.28 335.56,234 336.5,192.89 336.91,174.98 336.66,170.5 336.5,152.59 336.39,140.81 336.16,\
+-- 127.62 335.94,117.09"]
+--     4 [pos="101.5,172.74";width=0.5]
+--     3 -> 4 [pos="e,117.56,181.11 247.37,240.64 216.44,226.46 149.09,195.57 117.92,181.27"]
+--     9 [pos="193.5,172.74";width=0.5]
+--     3 -> 9 [pos="e,205.67,186.66 251.62,235.43 238.93,221.96 218.89,200.69 205.97,186.98"]
+--     15 [height=0.55967;pos="287.5,172.74";width=0.55967]
+--     3 -> 15 [pos="e,281.6,192.01 268.82,231.55 272.58,219.93 277.61,204.35 281.51,192.29"]
+--     5 [pos="48.498,96.445";width=0.5]
+--     4 -> 5 [pos="e,58.506,111.47 91.279,157.42 81.908,144.28 68.101,124.92 58.727,111.78"]
+--     10 [height=0.55967;pos="104.5,96.445";width=0.55967]
+--     4 -> 10 [pos="e,103.72,116.67 102.19,154.51 102.65,143.28 103.24,128.61 103.71,116.95"]
+--     16 [height=0.55967;pos="162.5,96.445";width=0.55967]
+--     4 -> 16 [pos="e,150.12,112.52 112.69,158.11 123.2,145.31 138.91,126.18 149.86,112.83"]
+--     5 -> 0 [pos="e,366.23,475.02 49.439,114.6 50.887,142.55 53.498,199.64 53.498,248.04 53.498,326.33 53.498,326.33 53.498,326.33 53.498,464.3 295.89,\
+-- 474.85 365.82,475.02"]
+--     11 [height=0.54162;pos="19.498,20.148";width=0.54162]
+--     5 -> 11 [pos="e,26.284,38.534 42.206,79.323 37.545,67.382 31.197,51.119 26.397,38.823"]
+--     17 [height=0.55967;pos="77.498,20.148";width=0.55967]
+--     5 -> 17 [pos="e,70.506,39.061 54.791,79.323 59.386,67.552 65.62,51.579 70.394,39.349"]
+--     15 -> 18 [pos="e,250.12,113.89 276.85,155.25 268.95,143.04 258.24,126.45 250.31,114.18"]
+--     15 -> 19 [pos="e,324.88,113.89 298.15,155.25 306.04,143.04 316.76,126.45 324.69,114.18"]
+--     18 -> 3 [pos="e,260.79,231.06 242.53,116.49 247.24,145.99 256.21,202.31 260.74,230.72"]
+--     19 -> 3 [pos="e,277.53,237.38 334.82,116.79 333.41,136.85 329.16,168.62 316.5,192.89 307.11,210.88 290.05,227.04 277.82,237.15"]
+--     }
+exInt :: Graph
+exInt = defaultGraph & addStatements (toStatements Directed (packUTF8 . show <$> exGInt))
diff --git a/src/DotParse/Examples/NumHask.hs b/src/DotParse/Examples/NumHask.hs
new file mode 100644
--- /dev/null
+++ b/src/DotParse/Examples/NumHask.hs
@@ -0,0 +1,246 @@
+{-# LANGUAGE AllowAmbiguousTypes #-}
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE OverloadedLabels #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE QuasiQuotes #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
+
+{-# HLINT ignore "Use ?~" #-}
+
+-- | Example of Dot graph construction for the NumHask class heirarchy.
+module DotParse.Examples.NumHask where
+
+import qualified Algebra.Graph as G
+import Chart
+import Data.Bifunctor
+import qualified Data.Map.Strict as Map
+import Data.Monoid
+import Data.Text (Text, pack)
+import DotParse
+import GHC.IO.Unsafe
+import Optics.Core
+import Prelude hiding (replicate)
+
+-- $setup
+-- >>> import DotParse
+-- >>> :set -XOverloadedStrings
+
+data Class
+  = Magma
+  | Unital
+  | Associative
+  | Commutative
+  | Invertible
+  | Idempotent
+  | Absorbing
+  | Group
+  | AbelianGroup
+  | Additive
+  | Subtractive
+  | Multiplicative
+  | Divisive
+  | Distributive
+  | Semiring
+  | Ring
+  | IntegralDomain
+  | Field
+  | ExpField
+  | QuotientField
+  | UpperBoundedField
+  | LowerBoundedField
+  | TrigField
+  | -- Higher-kinded numbers
+    AdditiveAction
+  | SubtractiveAction
+  | MultiplicativeAction
+  | DivisiveAction
+  | Module
+  | -- Lattice
+    JoinSemiLattice
+  | MeetSemiLattice
+  | Lattice
+  | BoundedJoinSemiLattice
+  | BoundedMeetSemiLattice
+  | BoundedLattice
+  | -- Number Types
+    Integral
+  | Ratio
+  | -- Measure
+    Signed
+  | Norm
+  | Basis
+  | Direction
+  | Epsilon
+  deriving (Show, Eq, Ord)
+
+data Family
+  = Addition
+  | Multiplication
+  | Actor
+  deriving (Show, Eq, Ord)
+
+data Dependency = Dependency
+  { _class :: Class,
+    _dep :: Class,
+    _op :: Maybe Family
+  }
+  deriving (Show, Eq, Ord)
+
+dependencies :: [Dependency]
+dependencies =
+  [ Dependency Unital Magma Nothing,
+    Dependency Associative Magma Nothing,
+    Dependency Commutative Magma Nothing,
+    Dependency Invertible Magma Nothing,
+    Dependency Idempotent Magma Nothing,
+    Dependency Absorbing Magma Nothing,
+    Dependency Group Unital Nothing,
+    Dependency Group Invertible Nothing,
+    Dependency Group Associative Nothing,
+    Dependency AbelianGroup Unital Nothing,
+    Dependency AbelianGroup Invertible Nothing,
+    Dependency AbelianGroup Associative Nothing,
+    Dependency AbelianGroup Commutative Nothing,
+    Dependency Additive Commutative (Just Addition),
+    Dependency Additive Unital (Just Addition),
+    Dependency Additive Associative (Just Addition),
+    Dependency Subtractive Invertible (Just Addition),
+    Dependency Subtractive Additive (Just Addition),
+    Dependency Multiplicative Unital (Just Multiplication),
+    Dependency Multiplicative Associative (Just Multiplication),
+    Dependency Multiplicative Commutative (Just Multiplication),
+    Dependency Divisive Invertible (Just Multiplication),
+    Dependency Divisive Multiplicative (Just Multiplication),
+    Dependency Distributive Additive (Just Addition),
+    Dependency Distributive Multiplicative (Just Multiplication),
+    Dependency Distributive Absorbing Nothing,
+    Dependency Ring Distributive Nothing,
+    Dependency Ring Subtractive (Just Addition),
+    Dependency IntegralDomain Ring Nothing,
+    Dependency Field Ring Nothing,
+    Dependency Field Divisive (Just Multiplication),
+    Dependency ExpField Field Nothing,
+    Dependency QuotientField Field Nothing,
+    Dependency QuotientField Ring Nothing,
+    Dependency TrigField Field Nothing,
+    Dependency UpperBoundedField Field Nothing,
+    Dependency LowerBoundedField Field Nothing,
+    -- higher-kinded relationships
+    Dependency AdditiveAction Additive (Just Actor),
+    Dependency SubtractiveAction Subtractive (Just Actor),
+    Dependency MultiplicativeAction Multiplicative (Just Actor),
+    Dependency DivisiveAction Divisive (Just Actor),
+    Dependency Module Distributive (Just Actor),
+    Dependency Module MultiplicativeAction Nothing,
+    -- Lattice
+    Dependency JoinSemiLattice Associative Nothing,
+    Dependency JoinSemiLattice Commutative Nothing,
+    Dependency JoinSemiLattice Idempotent Nothing,
+    Dependency MeetSemiLattice Associative Nothing,
+    Dependency MeetSemiLattice Commutative Nothing,
+    Dependency MeetSemiLattice Idempotent Nothing,
+    Dependency Lattice JoinSemiLattice Nothing,
+    Dependency Lattice MeetSemiLattice Nothing,
+    Dependency BoundedJoinSemiLattice JoinSemiLattice Nothing,
+    Dependency BoundedJoinSemiLattice Unital Nothing,
+    Dependency BoundedMeetSemiLattice MeetSemiLattice Nothing,
+    Dependency BoundedMeetSemiLattice Unital Nothing,
+    Dependency BoundedLattice BoundedJoinSemiLattice Nothing,
+    Dependency BoundedLattice BoundedMeetSemiLattice Nothing,
+    Dependency Signed Ring Nothing,
+    Dependency Norm Ring Nothing,
+    Dependency Basis Ring Nothing,
+    Dependency Direction Ring Nothing,
+    Dependency Epsilon Subtractive Nothing,
+    Dependency Epsilon MeetSemiLattice Nothing,
+    Dependency Integral Ring Nothing,
+    Dependency Ratio Field Nothing
+  ]
+
+classesNH :: [Class]
+classesNH =
+  [ Additive,
+    Subtractive,
+    Multiplicative,
+    Divisive,
+    Distributive,
+    Ring,
+    Field,
+    ExpField,
+    QuotientField,
+    TrigField,
+    Signed,
+    Norm,
+    Direction,
+    MultiplicativeAction,
+    Module,
+    UpperBoundedField,
+    LowerBoundedField,
+    Integral,
+    Ratio
+  ]
+
+classesModule :: [(Class, Text)]
+classesModule =
+  [ (Additive, "NumHask-Algebra-Additive"),
+    (Subtractive, "NumHask-Algebra-Additive"),
+    (Multiplicative, "NumHask-Algebra-Multiplicative"),
+    (Divisive, "NumHask-Algebra-Multiplicative"),
+    (Distributive, "NumHask-Algebra-Distributive"),
+    (Ring, "NumHask-Algebra-Ring"),
+    (Field, "NumHask-Algebra-Field"),
+    (ExpField, "NumHask-Algebra-Field"),
+    (QuotientField, "NumHask-Algebra-Field"),
+    (TrigField, "NumHask-Algebra-Field"),
+    (Signed, "NumHask-Algebra-Metric"),
+    (Norm, "NumHask-Algebra-Metric"),
+    (Direction, "NumHask-Algebra-Metric"),
+    (MultiplicativeAction, "NumHask-Algebra-Module"),
+    (Module, "NumHask-Algebra-Module"),
+    (UpperBoundedField, "NumHask-Algebra-Field"),
+    (LowerBoundedField, "NumHask-Algebra-Field"),
+    (Integral, "NumHask-Data-Integral"),
+    (Ratio, "NumHask-Data-Rational")
+  ]
+
+dependenciesNH :: [Dependency] -> [Dependency]
+dependenciesNH = filter (\(Dependency x0 x1 _) -> x0 `elem` classesNH && x1 `elem` classesNH)
+
+-- | NumHask Classes as an algebraic graph
+graphNHG :: G.Graph Class
+graphNHG =
+  G.edges ((\(Dependency x y _) -> (x, y)) <$> dependenciesNH dependencies)
+    <> G.vertices classesNH
+
+-- | NumHask statements in a dot Graph with box shapes for the nodes.
+dotGraphNH :: Directed -> Graph
+dotGraphNH d =
+  defaultGraph
+    & #directed .~ Last (Just d)
+    & addStatements (toStatements d (packUTF8 . show <$> graphNHG))
+    & attL NodeType (ID "shape") .~ Just (ID "box")
+    & gattL (ID "rankdir") .~ Just (IDQuoted "BT")
+
+-- | 'dotGraphNH' after being positionally processed via 'processGraph'
+dotGraphNH' :: Directed -> Graph
+dotGraphNH' d = unsafePerformIO $ processGraph (dotGraphNH d)
+{-# NOINLINE dotGraphNH' #-}
+
+-- | Convert a node ID to a label for chart-svg charts
+--
+-- Doing this directly in dot doesn't quite work because the engines get the width of the link wrong.
+toLink :: ID -> Text
+toLink i = [trimming|<a href="https://hackage.haskell.org/package/numhask/docs/$m.html#t:$t">$t</a>|]
+  where
+    t = pack (label i)
+    m = Map.fromList (first (pack . show) <$> classesModule) Map.! t
+
+-- | A chart-svg chart with label links
+--
+-- > writeChartSvg "other/nh.svg" (graphToChart toLink (dotGraphNH' Directed))
+--
+-- ![NumHask Example](other/nh.svg)
+writeNHChart :: IO ()
+writeNHChart = writeChartSvg "other/nh.svg" (graphToChartWith (defaultChartConfig & #labelf .~ toLink) (dotGraphNH' Directed))
diff --git a/src/DotParse/FlatParse.hs b/src/DotParse/FlatParse.hs
new file mode 100644
--- /dev/null
+++ b/src/DotParse/FlatParse.hs
@@ -0,0 +1,226 @@
+{-# LANGUAGE AllowAmbiguousTypes #-}
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedLabels #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE QuasiQuotes #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE TupleSections #-}
+{-# LANGUAGE TypeApplications #-}
+{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
+
+{-# HLINT ignore "Use <$>" #-}
+
+-- | Lower-level flatparse parsers
+module DotParse.FlatParse
+  ( Error (..),
+    prettyError,
+    keyword,
+    keyword',
+    symbol,
+    symbol',
+    ws,
+    token,
+    ident,
+    cut,
+    cut',
+    testParser,
+    runParser_,
+    int,
+    double,
+    signed,
+    quoted,
+    htmlLike,
+    sepP,
+    wrapSquareP,
+    wrapSquarePrint,
+    wrapCurlyP,
+    wrapCurlyPrint,
+    wrapQuotePrint,
+    pointP,
+    Spline (..),
+    splineP,
+    rectP,
+    boolP,
+    nonEmptyP,
+  )
+where
+
+import Data.Bool
+import Data.ByteString hiding (empty, head, length, map, zip, zipWith)
+import qualified Data.ByteString.Char8 as B
+import Data.Char hiding (isDigit)
+import Data.List.NonEmpty
+import DotParse.FlatParse.TH hiding (merge)
+import FlatParse.Basic hiding (cut, lines)
+import GHC.Generics
+import NumHask.Space
+import Prelude hiding (replicate)
+
+-- $setup
+-- >>> import DotParse
+-- >>> import FlatParse.Basic
+
+-- | Run parser, print pretty error on failure.
+testParser :: Show a => Parser Error a -> ByteString -> IO ()
+testParser p b =
+  case runParser p b of
+    Err e -> B.putStrLn $ prettyError b e
+    OK a _ -> print a
+    Fail -> B.putStrLn "uncaught parse error"
+
+-- | run a Parser, erroring on leftovers, Fail or Err
+runParser_ :: Parser Error a -> ByteString -> a
+runParser_ p b = case runParser p b of
+  OK r "" -> r
+  OK _ x -> error $ unpackUTF8 $ "leftovers: " <> x
+  Fail -> error "Fail"
+  Err e -> error $ unpackUTF8 $ prettyError b e
+
+-- * parsing
+
+digit :: Parser Error Int
+digit = (\c -> ord c - ord '0') <$> satisfyASCII isDigit
+
+-- | (unsigned) Int parser
+int :: Parser Error Int
+int = token do
+  (place, n) <- chainr (\n (!place, !acc) -> (place * 10, acc + place * n)) digit (pure (1, 0))
+  case place of
+    1 -> empty
+    _ -> pure n
+
+digits :: Parser Error (Int, Int)
+digits = chainr (\n (!place, !acc) -> (place * 10, acc + place * n)) digit (pure (1, 0))
+
+-- |
+-- >>> runParser double "1.234x"
+-- OK 1.234 "x"
+--
+-- >>> runParser double "."
+-- Fail
+--
+-- >>> runParser double "123"
+-- OK 123.0 ""
+--
+-- >>> runParser double ".123"
+-- OK 0.123 ""
+--
+-- >>> runParser double "123."
+-- OK 123.0 ""
+double :: Parser Error Double
+double = token do
+  (placel, nl) <- digits
+  optioned
+    ($(char '.') *> digits)
+    ( \(placer, nr) ->
+        case (placel, placer) of
+          (1, 1) -> empty
+          _ -> pure $ fromIntegral nl + fromIntegral nr / fromIntegral placer
+    )
+    ( case placel of
+        1 -> empty
+        _ -> pure $ fromIntegral nl
+    )
+
+-- |
+-- >>> runParser (signed double) "-1.234x"
+-- OK (-1.234) "x"
+signed :: Num b => Parser e b -> Parser e b
+signed p = optioned $(char '-') (const (((-1) *) <$> p)) p
+
+-- | Looks ahead for a "/"" that may be in the quoted string.
+-- >>> runParser quoted (packUTF8 "\"hello\"")
+-- OK "hello" ""
+--
+-- >>> runParser quoted (packUTF8 "\"hello/\"\"")
+-- OK "hello\"" ""
+quoted :: Parser Error String
+quoted =
+  $(symbol "\"") *> many unquoteQuote <* $(symbol' "\"")
+
+unquoteQuote :: Parser Error Char
+unquoteQuote = do
+  next <- satisfy (/= '"')
+  case next of
+    '/' -> branch (lookahead $(char '"')) ('"' <$ $(char '"')) (pure '/')
+    x -> pure x
+
+-- | optional separators
+sepP :: Parser e ()
+sepP =
+  token
+    $( switch
+         [|
+           case _ of
+             ";" -> pure ()
+             "," -> pure ()
+           |]
+     )
+
+-- | parse wrapping square brackets
+wrapSquareP :: Parser Error a -> Parser Error a
+wrapSquareP p =
+  $(symbol "[") *> p <* $(symbol' "]")
+
+-- | print wrapping square brackets
+wrapSquarePrint :: ByteString -> ByteString
+wrapSquarePrint b = "[" <> b <> "]"
+
+-- | print wrapping quotes
+wrapQuotePrint :: ByteString -> ByteString
+wrapQuotePrint b = "\"" <> b <> "\""
+
+-- | parse wrapping square brackets
+wrapCurlyP :: Parser Error a -> Parser Error a
+wrapCurlyP p = $(symbol "{") *> p <* $(symbol' "}")
+
+-- | print wrapping curly brackets
+wrapCurlyPrint :: ByteString -> ByteString
+wrapCurlyPrint b = "{" <> b <> "}"
+
+-- | comma separated Point
+pointP :: Parser Error (Point Double)
+pointP = token $ Point <$> double <*> ($(symbol ",") *> double)
+
+-- | dot specification of a cubic spline (and an arrow head which is ignored here)
+data Spline = Spline {splineEnd :: Maybe (Point Double), splineStart :: Maybe (Point Double), splineP1 :: Point Double, splineTriples :: [(Point Double, Point Double, Point Double)]} deriving (Eq, Show, Generic)
+
+-- |
+-- http://www.graphviz.org/docs/attr-types/splineType/
+splineP :: Parser Error Spline
+splineP =
+  Spline
+    <$> optional ($(symbol "e,") *> pointP)
+    <*> optional ($(symbol "s") *> pointP)
+    <*> pointP
+    <*> some ((,,) <$> pointP <*> pointP <*> pointP)
+
+-- | comma separated rectangle or bounding box
+rectP :: Parser Error (Rect Double)
+rectP = token $ do
+  x <- double
+  _ <- $(symbol ",")
+  y <- double
+  _ <- $(symbol ",")
+  z <- double
+  _ <- $(symbol ",")
+  w <- double
+  pure $ Rect x z y w
+
+-- | true | false
+boolP :: Parser Error Bool
+boolP =
+  (True <$ $(symbol "true"))
+    <|> (False <$ $(symbol "false"))
+
+-- | NonEmpty version of many
+nonEmptyP :: Parser e a -> Parser e () -> Parser e (NonEmpty a)
+nonEmptyP p sep = token $ do
+  s <- p
+  xs <- many (optional sep *> p)
+  pure (s :| xs)
diff --git a/src/DotParse/FlatParse/TH.hs b/src/DotParse/FlatParse/TH.hs
new file mode 100644
--- /dev/null
+++ b/src/DotParse/FlatParse/TH.hs
@@ -0,0 +1,206 @@
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedLabels #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE QuasiQuotes #-}
+{-# LANGUAGE TemplateHaskell #-}
+{-# OPTIONS_GHC -Wno-name-shadowing #-}
+{-# OPTIONS_GHC -Wno-unused-local-binds #-}
+
+-- | TH stage restriction guff for flatparsing
+module DotParse.FlatParse.TH where
+
+import Data.ByteString hiding (head, length, reverse)
+import qualified Data.ByteString.Char8 as B
+import Data.Functor
+import FlatParse.Basic
+import Language.Haskell.TH
+
+-- | Consume whitespace.
+ws :: Parser e ()
+ws =
+  $( switch
+       [|
+         case _ of
+           -- order matters
+           " " -> ws
+           "\\\n" -> ws
+           "\\\\\\n" -> ws
+           "\n" -> ws
+           "\t" -> ws
+           "\r" -> ws
+           ";" -> ws
+           "//" -> lineComment
+           "/*" -> multilineComment
+           _ -> pure ()
+         |]
+   )
+
+-- | Consume whitespace after running a parser.
+token :: Parser e a -> Parser e a
+token p = p <* ws
+{-# INLINE token #-}
+
+-- | Parse a line comment.
+lineComment :: Parser e ()
+lineComment =
+  optioned
+    anyWord8
+    ( \case
+        10 -> ws
+        _ -> lineComment
+    )
+    (pure ())
+
+-- | Parse a potentially nested multiline comment.
+multilineComment :: Parser e ()
+multilineComment = go (1 :: Int)
+  where
+    go 0 = ws
+    go n =
+      $( switch
+           [|
+             case _ of
+               "*/" -> go (n - 1)
+               "/*" -> go (n + 1)
+               _ -> branch anyWord8 (go n) (pure ())
+             |]
+       )
+
+-- | Parse a HTML-Like string by counting the angle brackets
+htmlLike :: Parser e String
+htmlLike = ws *> $(char '<') *> go (1 :: Int) "<"
+  where
+    go 0 acc = ws $> reverse acc
+    go n acc =
+      ($(char '>') *> go (n - 1) ('>' : acc))
+        <|> ($(char '<') *> go (n + 1) ('<' : acc))
+        <|> (anyChar >>= (\c -> go n (c : acc)))
+
+-- | First character of a dot identifier.
+isValidStartChar :: Char -> Bool
+isValidStartChar c =
+  ('A' <= c && c <= 'Z')
+    || ('a' <= c && c <= 'z')
+    || ('\200' <= c && c <= '\377')
+    || (c == '_')
+
+-- | character of a dot identifier.
+isValidChar :: Char -> Bool
+isValidChar c = isValidStartChar c || isDigit c
+
+-- | Read a starting character of an identifier.
+identStartChar :: Parser e Char
+identStartChar = satisfy isValidStartChar
+
+-- | Read a non-starting character of an identifier.
+identChar :: Parser e Char
+identChar = satisfy isValidChar
+
+-- | Parse a non-keyword string.
+symbol :: String -> Q Exp
+symbol str = [|token $(string str)|]
+
+-- | Parse a keyword string.
+keyword :: String -> Q Exp
+keyword str = [|token ($(string str) `notFollowedBy` identChar)|]
+
+-- | Parser a non-keyword string, throw precise error on failure.
+symbol' :: String -> Q Exp
+symbol' str = [|$(symbol str) `cut'` packUTF8 str|]
+
+-- | Parse a keyword string, throw precise error on failure.
+keyword' :: String -> Q Exp
+keyword' str = [|$(keyword str) `cut'` packUTF8 str|]
+
+-- | Parse an identifier.
+ident :: Parser e ByteString
+ident =
+  token $
+    byteStringOf $
+      identStartChar *> many_ identChar
+
+-- | Parse an identifier, throw a precise error on failure.
+ident' :: Parser Error ByteString
+ident' = ident `cut'` "identifier"
+
+-- | A parsing error.
+data Error
+  = -- | A precisely known error, like leaving out "in" from "let".
+    Precise Pos ByteString
+  | -- | An imprecise error, when we expect a number of different things,
+    --   but parse something else.
+    Imprecise Pos [ByteString]
+  deriving (Eq, Show)
+
+-- | position of error
+errorPos :: Error -> Pos
+errorPos (Precise p _) = p
+errorPos (Imprecise p _) = p
+
+-- | Merge two errors. Inner errors (which were thrown at points with more consumed inputs)
+--   are preferred. If errors are thrown at identical input positions, we prefer precise errors
+--   to imprecise ones.
+--
+--   The point of prioritizing inner and precise errors is to suppress the deluge of "expected"
+--   items, and instead try to point to a concrete issue to fix.
+merge :: Error -> Error -> Error
+merge e e' = case (errorPos e, errorPos e') of
+  (p, p') | p < p' -> e'
+  (p, p') | p > p' -> e
+  (p, _) -> case (e, e') of
+    (Precise {}, _) -> e
+    (_, Precise {}) -> e'
+    (Imprecise _ es, Imprecise _ es') -> Imprecise p (es ++ es')
+{-# NOINLINE merge #-} -- merge is "cold" code, so we shouldn't inline it.
+
+-- | Pretty print an error. The `ByteString` input is the source file. The offending line from the
+--   source is displayed in the output.
+prettyError :: ByteString -> Error -> ByteString
+prettyError b e =
+  let pos :: Pos
+      pos = case e of
+        Imprecise pos _ -> pos
+        Precise pos _ -> pos
+      ls = B.lines b
+      (l, c) = head $ posLineCols b [pos]
+      line = if l < length ls then ls !! l else ""
+      linum = packUTF8 $ show l
+      lpad = B.replicate (B.length linum) ' '
+
+      err (Precise _ e) = e
+      err (Imprecise _ es) = imprec es
+
+      imprec :: [ByteString] -> ByteString
+      imprec [] = error "impossible"
+      imprec [e] = e
+      imprec (e : es) = e <> go es
+        where
+          go [] = ""
+          go [e] = " or " <> e
+          go (e : es) = ", " <> e <> go es
+   in packUTF8 (show l) <> ":" <> packUTF8 (show c) <> ":\n"
+        <> lpad
+        <> "|\n"
+        <> linum
+        <> "| "
+        <> line
+        <> "\n"
+        <> lpad
+        <> "| "
+        <> B.replicate c ' '
+        <> "^\n"
+        <> "parse error: expected "
+        <> err e
+
+-- | Imprecise cut: we slap a list of items on inner errors.
+cut :: Parser Error a -> [ByteString] -> Parser Error a
+cut p es = do
+  pos <- getPos
+  cutting p (Imprecise pos es) merge
+
+-- | Precise cut: we propagate at most a single error.
+cut' :: Parser Error a -> ByteString -> Parser Error a
+cut' p e = do
+  pos <- getPos
+  cutting p (Precise pos e) merge
diff --git a/src/DotParse/Types.hs b/src/DotParse/Types.hs
new file mode 100644
--- /dev/null
+++ b/src/DotParse/Types.hs
@@ -0,0 +1,881 @@
+{-# LANGUAGE AllowAmbiguousTypes #-}
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingVia #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# HLINT ignore "Use unwords" #-}
+{-# HLINT ignore "Use <$>" #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedLabels #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE QuasiQuotes #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE TupleSections #-}
+{-# LANGUAGE TypeApplications #-}
+{-# OPTIONS_GHC -Wno-name-shadowing #-}
+{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
+{-# OPTIONS_HADDOCK not-home #-}
+
+-- | Abstract Grammar for the dot language.
+-- http://www.graphviz.org/doc/info/lang.html
+module DotParse.Types
+  ( DotConfig (..),
+    defaultDotConfig,
+    DotParse (..),
+    testDotParser,
+    runDotParser,
+    Error (..),
+    prettyError,
+    Graph (..),
+    gattL,
+    attL,
+    defaultGraph,
+    processDotWith,
+    processDot,
+    processGraph,
+    processGraphWith,
+
+    -- * components
+    Strict (..),
+    defStrict,
+    Directed (..),
+    defDirected,
+    ID (..),
+    label,
+    Compass (..),
+    Port (..),
+    AttributeType (..),
+    AttributeStatement (..),
+    NodeStatement (..),
+    EdgeID (..),
+    EdgeOp (..),
+    fromDirected,
+    EdgeStatement (..),
+    edgeID,
+    edgeIDs,
+    edgeIDsNamed,
+    Statement (..),
+    addStatement,
+    addStatements,
+    SubGraphStatement (..),
+
+    -- * Graph Extraction
+    bbL,
+    nodesPortL,
+    nodesL,
+    edgesL,
+    nodesA,
+    edgesA,
+    nodePos,
+    nodeWidth,
+    edgeSpline,
+    edgeWidth,
+    NodeInfo (..),
+    nodeInfo,
+    EdgeInfo (..),
+    edgeInfo,
+    splinePath,
+
+    -- * Conversion
+    graphToChartWith,
+    graphToChart,
+    ChartConfig (..),
+    defaultChartConfig,
+    toStatements,
+    toDotGraph,
+    toDotGraphWith,
+  )
+where
+
+import qualified Algebra.Graph as G
+import Chart
+import Control.Monad
+import Data.Bool
+import Data.ByteString hiding (any, empty, filter, head, length, map, zip, zipWith)
+import qualified Data.ByteString.Char8 as B
+import Data.List.NonEmpty hiding (filter, head, length, map, zip, zipWith, (!!))
+import Data.Map.Merge.Strict
+import qualified Data.Map.Strict as Map
+import Data.Maybe
+import Data.Monoid
+import Data.Proxy
+import Data.Text (Text)
+import qualified Data.Text as Text
+import Data.Text.Encoding (encodeUtf8)
+import Data.These
+import DotParse.FlatParse
+import FlatParse.Basic hiding (cut, lines)
+import GHC.Generics
+import NeatInterpolation
+import Optics.Core
+import System.Exit
+import System.Process.ByteString
+import Prelude hiding (replicate)
+
+-- $setup
+-- >>> import DotParse
+-- >>> import qualified Data.Map as Map
+-- >>> :set -XOverloadedStrings
+
+-- | printing options, for separators.
+data DotConfig = DotConfig
+  { topLevelSep :: ByteString,
+    statementSep :: ByteString,
+    attSep :: ByteString,
+    subGraphSep :: ByteString
+  }
+  deriving (Eq, Show, Generic)
+
+-- | default separators
+defaultDotConfig :: DotConfig
+defaultDotConfig = DotConfig " " "\n    " ";" ";"
+
+-- | A parser & printer class for a graphviz graph and components of its dot language
+class DotParse a where
+  dotPrint :: DotConfig -> a -> ByteString
+  dotParse :: Parser Error a
+
+-- | dotParse and then dotPrint:
+--
+-- - pretty printing error on failure.
+--
+-- - This is not an exact parser/printer, so the test re-parses the dotPrint, which should be idempotent
+testDotParser :: forall a. (DotParse a) => Proxy a -> DotConfig -> ByteString -> IO ()
+testDotParser _ cfg b =
+  case runParser dotParse b :: Result Error a of
+    Err e -> B.putStrLn $ prettyError b e
+    OK a left -> do
+      when (left /= "") (B.putStrLn $ "parsed with leftovers: " <> left)
+      case runParser dotParse (dotPrint cfg a) :: Result Error a of
+        Err e -> B.putStrLn $ "round trip error: " <> prettyError (dotPrint cfg a) e
+        Fail -> B.putStrLn "uncaught round trip parse error"
+        OK _ left' -> do
+          when (left' /= "") (B.putStrLn $ "round trip parse with left overs" <> left)
+    Fail -> B.putStrLn "uncaught parse error"
+
+-- | run a dotParse erroring on leftovers, Fail or Err
+runDotParser :: (DotParse a) => ByteString -> a
+runDotParser = runParser_ dotParse
+
+-- | Representation of a full graphviz graph, as per the dot language specification
+data Graph = Graph
+  { strict :: Last Strict,
+    directed :: Last Directed,
+    graphid :: Last ID,
+    nodeAttributes :: Map.Map ID ID,
+    graphAttributes :: Map.Map ID ID,
+    edgeAttributes :: Map.Map ID ID,
+    globalAttributes :: Map.Map ID ID,
+    nodes :: [NodeStatement],
+    edges :: [EdgeStatement],
+    subgraphs :: [SubGraphStatement]
+  }
+  deriving (Eq, Show, Generic)
+
+instance Semigroup Graph where
+  (Graph m d i na ga ea gs ns es ss) <> (Graph m' d' i' na' ga' ea' gs' ns' es' ss') =
+    Graph (m <> m') (d <> d') (i <> i') (na <> na') (ga <> ga') (ea <> ea') (gs <> gs') (ns <> ns') (es <> es') (ss <> ss')
+
+instance Monoid Graph where
+  mempty = Graph mempty mempty mempty mempty mempty mempty mempty mempty mempty mempty
+
+-- | global attributes lens
+gattL :: ID -> Lens' Graph (Maybe ID)
+gattL k = #globalAttributes % at k
+
+-- | attributes lens
+attL :: AttributeType -> ID -> Lens' Graph (Maybe ID)
+attL GraphType k = #graphAttributes % at k
+attL NodeType k = #nodeAttributes % at k
+attL EdgeType k = #edgeAttributes % at k
+
+outercalate :: ByteString -> [ByteString] -> ByteString
+outercalate _ [] = mempty
+outercalate a xs = a <> intercalate a xs <> a
+
+instance DotParse Graph where
+  dotPrint cfg (Graph me d i na ga ea gs ns es ss) =
+    intercalate (cfg ^. #topLevelSep) $
+      bool [] ["strict"] (me == Last (Just MergeEdges))
+        <> bool ["digraph"] ["graph"] (d == Last (Just UnDirected))
+        <> maybe [] ((: []) . dotPrint cfg) (getLast i)
+        <> [ wrapCurlyPrint $
+               outercalate
+                 (cfg ^. #statementSep)
+                 ( [dotPrint cfg (AttributeStatement NodeType na)]
+                     <> [dotPrint cfg (AttributeStatement GraphType ga)]
+                     <> [dotPrint cfg (AttributeStatement EdgeType ea)]
+                     <> (dotPrint cfg . GlobalAttributeStatement <$> Map.toList gs)
+                     <> (dotPrint cfg <$> ns)
+                     <> (dotPrint cfg <$> es)
+                     <> (dotPrint cfg <$> ss)
+                 )
+           ]
+
+  dotParse = token $ do
+    me <- dotParse
+    d <- dotParse
+    i <- optional dotParse
+    ss <- wrapCurlyP (many dotParse)
+    let g =
+          (mempty :: Graph)
+            & #strict .~ Last (Just me)
+            & #directed .~ Last (Just d)
+            & #graphid .~ Last i
+    pure $ addStatements ss g
+
+-- * Dot Grammar
+
+-- | MergeEdges (strict)
+data Strict = MergeEdges | NoMergeEdges deriving (Eq, Show, Generic)
+
+instance DotParse Strict where
+  dotPrint _ MergeEdges = "strict"
+  dotPrint _ NoMergeEdges = ""
+
+  dotParse = token $ optioned $(keyword "strict") (const $ pure MergeEdges) (pure NoMergeEdges)
+
+-- | Default Strict is NoMergeEdges
+defStrict :: Last Strict -> Strict
+defStrict (Last Nothing) = NoMergeEdges
+defStrict (Last (Just x)) = x
+
+-- | Directed (digraph | graph)
+data Directed = Directed | UnDirected deriving (Eq, Show, Generic)
+
+instance DotParse Directed where
+  dotPrint _ Directed = "digraph"
+  dotPrint _ UnDirected = "graph"
+
+  dotParse =
+    token $
+      (Directed <$ $(keyword "digraph"))
+        <|> (UnDirected <$ $(keyword "graph"))
+
+-- | Default Directed is Directed
+defDirected :: Last Directed -> Directed
+defDirected (Last Nothing) = Directed
+defDirected (Last (Just x)) = x
+
+-- | A dot statement as per the dot language specification.
+data Statement = StatementNode NodeStatement | StatementEdge EdgeStatement | StatementGlobalAttribute GlobalAttributeStatement | StatementAttribute AttributeStatement | StatementSubGraph SubGraphStatement deriving (Eq, Show, Generic)
+
+instance DotParse Statement where
+  dotPrint cfg (StatementNode x) = dotPrint cfg x
+  dotPrint cfg (StatementEdge x) = dotPrint cfg x
+  dotPrint cfg (StatementAttribute x) = dotPrint cfg x
+  dotPrint cfg (StatementGlobalAttribute x) = dotPrint cfg x
+  dotPrint cfg (StatementSubGraph x) = dotPrint cfg x
+
+  dotParse =
+    token $
+      -- Order is important
+      (StatementEdge <$> dotParse)
+        <|> (StatementAttribute <$> dotParse)
+        <|> (StatementGlobalAttribute <$> dotParse)
+        <|> (StatementSubGraph <$> dotParse)
+        <|> (StatementNode <$> dotParse)
+
+-- | Identifier as per the dot language specifications.
+--
+-- >>> runDotParser "0" :: ID
+-- IDInt 0
+--
+-- >>> runDotParser "-.123" :: ID
+-- IDDouble (-0.123)
+--
+-- >>> runParser dotParse "apple_1'" :: Result Error ID
+-- OK (ID "apple_1") "'"
+--
+-- >>> :set -XQuasiQuotes
+-- >>> runParser dotParse "\"hello\"" :: Result Error ID
+-- OK (IDQuoted "hello") ""
+--
+-- >>> runDotParser "<The <font color='red'><b>foo</b></font>,<br/> the <font point-size='20'>bar</font> and<br/> the <i>baz</i>>" :: ID
+-- IDHtml "<The <font color='red'><b>foo</b></font>,<br/> the <font point-size='20'>bar</font> and<br/> the <i>baz</i>>"
+data ID = ID ByteString | IDInt Int | IDDouble Double | IDQuoted ByteString | IDHtml ByteString deriving (Eq, Show, Generic, Ord)
+
+instance DotParse ID where
+  dotPrint _ (ID s) = s
+  dotPrint _ (IDInt i) = packUTF8 (show i)
+  dotPrint _ (IDDouble x) = packUTF8 (show x)
+  dotPrint _ (IDQuoted x) =
+    wrapQuotePrint x
+  dotPrint _ (IDHtml s) = s
+
+  -- order matters
+  dotParse =
+    (ID <$> ident)
+      <|> (IDInt <$> (signed int `notFollowedBy` $(char '.')))
+      <|> (IDDouble <$> signed double)
+      <|> (IDQuoted . packUTF8 <$> quoted)
+      <|> (IDHtml . packUTF8 <$> htmlLike)
+
+-- | ID as the equivalent plain String
+--
+-- note that the dot language identifier equivalence law is:
+--
+-- > x == y if label x == label y
+label :: ID -> String
+label (ID s) = unpackUTF8 s
+label (IDInt i) = show i
+label (IDDouble d) = show d
+label (IDQuoted q) = unpackUTF8 q
+label (IDHtml h) = unpackUTF8 h
+
+-- | Attribute key-value pair of identifiers
+--
+-- >>> runDotParser "shape=diamond" :: (ID,ID)
+-- (ID "shape",ID "diamond")
+--
+-- >>> runDotParser "fontname=\"Arial\"" :: (ID,ID)
+-- (ID "fontname",IDQuoted "Arial")
+instance DotParse (ID, ID) where
+  dotPrint cfg (x0, x1) = dotPrint cfg x0 <> "=" <> dotPrint cfg x1
+
+  dotParse = token $
+    do
+      x0 <- token dotParse
+      _ <- token $(symbol "=")
+      x1 <- dotParse
+      pure (x0, x1)
+
+-- | Attribute collections
+--
+-- >>> runDotParser "[shape=diamond; color=blue] [label=label]" :: Map.Map ID ID
+-- fromList [(ID "color",ID "blue"),(ID "label",ID "label"),(ID "shape",ID "diamond")]
+--
+-- A given entity can have multiple attribute lists. For simplicity, these are mconcat'ed on parsing.
+instance DotParse (Map.Map ID ID) where
+  dotPrint cfg as =
+    bool
+      (wrapSquarePrint (intercalate (cfg ^. #attSep) $ dotPrint cfg <$> Map.toList as))
+      mempty
+      (as == Map.empty)
+
+  dotParse =
+    Map.fromList . mconcat . fmap toList
+      <$> token (many (wrapSquareP (nonEmptyP dotParse sepP)) <|> ([] <$ wrapSquareP ws))
+
+-- | Compass instructions which are optionally associated with an identifier
+data Compass = CompassN | CompassNE | CompassE | CompassSE | CompassS | CompassSW | CompassW | CompassNW | CompassC | Compass_ deriving (Eq, Show, Generic)
+
+instance DotParse Compass where
+  dotPrint _ CompassN = "n"
+  dotPrint _ CompassNE = "ne"
+  dotPrint _ CompassE = "e"
+  dotPrint _ CompassSE = "se"
+  dotPrint _ CompassS = "s"
+  dotPrint _ CompassSW = "sw"
+  dotPrint _ CompassW = "w"
+  dotPrint _ CompassNW = "nw"
+  dotPrint _ CompassC = "c"
+  dotPrint _ Compass_ = "_"
+
+  dotParse =
+    token
+      $( switch
+           [|
+             case _ of
+               "n" -> pure CompassN
+               "ne" -> pure CompassNE
+               "e" -> pure CompassE
+               "se" -> pure CompassSE
+               "s" -> pure CompassS
+               "sw" -> pure CompassSW
+               "w" -> pure CompassW
+               "nw" -> pure CompassNW
+               "c" -> pure CompassC
+               "_" -> pure Compass_
+             |]
+       )
+
+-- | Port instructions which are optionally associated with an identifier
+newtype Port = Port {portID :: These ID Compass} deriving (Eq, Show, Generic)
+
+instance DotParse Port where
+  dotPrint cfg (Port (This i)) = ": " <> dotPrint cfg i
+  dotPrint cfg (Port (That c)) = ": " <> dotPrint cfg c
+  dotPrint cfg (Port (These i c)) = ": " <> dotPrint cfg i <> " : " <> dotPrint cfg c
+
+  dotParse =
+    token $
+      ((\x0 x1 -> Port (These x0 x1)) <$> ($(symbol ":") *> dotParse) <*> ($(symbol ":") *> dotParse))
+        <|> (Port . This <$> ($(symbol ":") *> dotParse))
+        <|> (Port . That <$> ($(symbol ":") *> dotParse))
+
+-- | A top-level attribute
+--
+-- >>> runDotParser "rankdir=\"BT\"" :: Statement
+-- StatementGlobalAttribute (GlobalAttributeStatement {globalAttributeStatement = (ID "rankdir",IDQuoted "BT")})
+newtype GlobalAttributeStatement = GlobalAttributeStatement {globalAttributeStatement :: (ID, ID)} deriving (Eq, Show, Generic)
+
+instance DotParse GlobalAttributeStatement where
+  dotPrint cfg (GlobalAttributeStatement s) = dotPrint cfg s
+  dotParse = GlobalAttributeStatement <$> dotParse
+
+-- | Category of attribute
+data AttributeType = GraphType | NodeType | EdgeType deriving (Eq, Show, Generic)
+
+instance DotParse AttributeType where
+  dotPrint _ GraphType = "graph"
+  dotPrint _ NodeType = "node"
+  dotPrint _ EdgeType = "edge"
+
+  dotParse =
+    token
+      (GraphType <$ $(keyword "graph"))
+      <|> (NodeType <$ $(keyword "node"))
+      <|> (EdgeType <$ $(keyword "edge"))
+
+-- | Top-level attribute statement
+--
+-- >>> runDotParser "graph [overlap=false, splines=spline, size=\"1!\"];" :: Statement
+-- StatementAttribute (AttributeStatement {attributeType = GraphType, attributes = fromList [(ID "overlap",ID "false"),(ID "size",IDQuoted "1!"),(ID "splines",ID "spline")]})
+data AttributeStatement = AttributeStatement {attributeType :: AttributeType, attributes :: Map.Map ID ID} deriving (Eq, Show, Generic)
+
+instance DotParse AttributeStatement where
+  dotPrint cfg (AttributeStatement t as) =
+    intercalate
+      " "
+      [dotPrint cfg t, dotPrint cfg as]
+
+  dotParse = AttributeStatement <$> dotParse <*> dotParse
+
+-- | Node statement
+--
+-- >>> runDotParser "A [shape=diamond; color=blue]" :: Statement
+-- StatementNode (NodeStatement {nodeID = ID "A", port = Nothing, nodeAttrs = fromList [(ID "color",ID "blue"),(ID "shape",ID "diamond")]})
+data NodeStatement = NodeStatement {nodeID :: ID, port :: Maybe Port, nodeAttrs :: Map.Map ID ID} deriving (Eq, Show, Generic)
+
+instance DotParse NodeStatement where
+  dotPrint cfg (NodeStatement i p as) =
+    intercalate " " $
+      [dotPrint cfg i]
+        <> (dotPrint cfg <$> maybeToList p)
+        <> [dotPrint cfg as]
+
+  dotParse = NodeStatement <$> dotParse <*> optional dotParse <*> dotParse
+
+-- | An edge can be specified in as a NodeID or as a SubGraph
+data EdgeID
+  = EdgeID ID (Maybe Port)
+  | EdgeSubGraph SubGraphStatement
+  deriving (Eq, Show, Generic)
+
+instance DotParse EdgeID where
+  dotPrint cfg (EdgeID e p) =
+    mconcat $ [dotPrint cfg e] <> (dotPrint cfg <$> maybeToList p)
+  dotPrint cfg (EdgeSubGraph s) = dotPrint cfg s
+
+  dotParse =
+    (EdgeID <$> dotParse <*> optional dotParse)
+      <|> (EdgeSubGraph <$> dotParse)
+
+-- | An edgeop is -> in directed graphs and -- in undirected graphs.
+data EdgeOp = EdgeDirected | EdgeUndirected deriving (Eq, Show, Generic)
+
+instance DotParse EdgeOp where
+  dotPrint _ EdgeDirected = "->"
+  dotPrint _ EdgeUndirected = "--"
+
+  dotParse =
+    token
+      $( switch
+           [|
+             case _ of
+               "->" -> pure EdgeDirected
+               "--" -> pure EdgeUndirected
+             |]
+       )
+
+-- | generate an EdgeOp given the type of graph.
+fromDirected :: Directed -> EdgeOp
+fromDirected Directed = EdgeDirected
+fromDirected UnDirected = EdgeUndirected
+
+-- | Edge statement
+--
+-- >>> runDotParser "A -> B [style=dashed, color=grey]" :: Statement
+-- StatementEdge (EdgeStatement {edgeOp = EdgeDirected, leftEdge = EdgeID (ID "A") Nothing, rightEdges = EdgeID (ID "B") Nothing :| [], edgeAttrs = fromList [(ID "color",ID "grey"),(ID "style",ID "dashed")]})
+data EdgeStatement = EdgeStatement {edgeOp :: EdgeOp, leftEdge :: EdgeID, rightEdges :: NonEmpty EdgeID, edgeAttrs :: Map.Map ID ID} deriving (Eq, Show, Generic)
+
+instance DotParse EdgeStatement where
+  dotPrint cfg (EdgeStatement l rs xs as) =
+    intercalate
+      " "
+      ( [intercalate (" " <> dotPrint cfg l <> " ") (dotPrint cfg <$> (rs : toList xs))]
+          <> [dotPrint cfg as]
+      )
+
+  dotParse = token $ do
+    l <- dotParse
+    o0 <- dotParse
+    r0 <- dotParse
+    ors <- many ((,) <$> dotParse <*> dotParse)
+    as <- dotParse
+    bool
+      (pure (EdgeStatement o0 l (r0 :| (snd <$> ors)) as))
+      empty
+      (any ((/= o0) . fst) ors)
+
+-- | The edge ID or subgraph ID (if any)
+edgeID :: EdgeID -> Maybe ID
+edgeID (EdgeID i _) = Just i
+edgeID (EdgeSubGraph (SubGraphStatement i _)) = i
+
+-- | edge IDs
+edgeIDsNamed :: EdgeStatement -> [(ID, ID)]
+edgeIDsNamed e = [(x, y) | (Just x, Just y) <- edgeIDs e]
+
+-- | list of edges in a given EdgeStatement, including anonymous SugGraphs
+edgeIDs :: EdgeStatement -> [(Maybe ID, Maybe ID)]
+edgeIDs e = zip (id0 : id1) id1
+  where
+    id0 = edgeID (view #leftEdge e)
+    id1 = edgeID <$> toList (view #rightEdges e)
+
+-- | A subgraph statement.
+--
+-- Note: each subgraph must have a unique name
+--
+-- >>> runDotParser "subgraph A {A, B, C}" :: Statement
+-- StatementSubGraph (SubGraphStatement {subgraphID = Just (ID "A"), subgraphStatements = [StatementNode (NodeStatement {nodeID = ID "A", port = Nothing, nodeAttrs = fromList []}),StatementNode (NodeStatement {nodeID = ID "B", port = Nothing, nodeAttrs = fromList []}),StatementNode (NodeStatement {nodeID = ID "C", port = Nothing, nodeAttrs = fromList []})]})
+data SubGraphStatement = SubGraphStatement {subgraphID :: Maybe ID, subgraphStatements :: [Statement]} deriving (Eq, Show, Generic)
+
+instance DotParse SubGraphStatement where
+  dotPrint cfg (SubGraphStatement x xs) =
+    intercalate " " $
+      maybe
+        []
+        (\x' -> [intercalate " " ["subgraph", dotPrint cfg x']])
+        x
+        <> (: []) (wrapCurlyPrint (intercalate (cfg ^. #subGraphSep) $ dotPrint cfg <$> xs))
+
+  dotParse = token $ do
+    x <- optional ($(keyword "subgraph") *> dotParse)
+    pure (SubGraphStatement x) <*> wrapCurlyP (many (optional sepP *> dotParse))
+
+-- | add a graphviz statement to a 'Graph'
+addStatement :: Statement -> Graph -> Graph
+addStatement (StatementNode n) g = g & #nodes %~ (<> [n])
+addStatement (StatementEdge e) g = g & #edges %~ (<> [e])
+addStatement (StatementSubGraph s) g = g & #subgraphs %~ (<> [s])
+addStatement (StatementAttribute (AttributeStatement GraphType as)) g = g & #graphAttributes %~ (<> as)
+addStatement (StatementAttribute (AttributeStatement NodeType as)) g = g & #nodeAttributes %~ (<> as)
+addStatement (StatementAttribute (AttributeStatement EdgeType as)) g = g & #edgeAttributes %~ (<> as)
+addStatement (StatementGlobalAttribute (GlobalAttributeStatement s)) g = g & #globalAttributes %~ (<> Map.fromList [s])
+
+-- | add a list of graphviz statements to a 'Graph'
+addStatements :: [Statement] -> Graph -> Graph
+addStatements ss g = Prelude.foldr addStatement g ss
+
+-- | default dot graph as a ByteString
+defaultBS :: ByteString
+defaultBS =
+  encodeUtf8
+    [trimming|
+digraph {
+    node [shape=circle
+         ,height=0.5];
+    graph [overlap=false
+          ,splines=spline
+          ,size="1!"];
+    edge [arrowsize=0];
+  }
+|]
+
+-- | A default dot graph
+--
+-- >>> import qualified Data.ByteString.Char8 as B
+-- >>> B.putStrLn $ dotPrint defaultDotConfig defaultGraph
+-- digraph {
+--     node [height=0.5;shape=circle]
+--     graph [overlap=false;size="1!";splines=spline]
+--     edge [arrowsize=0]
+--     }
+defaultGraph :: Graph
+defaultGraph = runDotParser defaultBS
+
+-- | run a dot string through graphviz, supplying arguments and collecting stdout
+processDotWith :: Directed -> [String] -> ByteString -> IO ByteString
+processDotWith d args i = do
+  let cmd = case d of
+        Directed -> "dot"
+        UnDirected -> "neato"
+  (r, input, e) <- readProcessWithExitCode cmd args i
+  bool
+    (error $ unpackUTF8 e)
+    (pure input)
+    (r == ExitSuccess)
+
+-- | run a dot string through graphviz, collecting the augmented dot string output
+processDot :: Directed -> ByteString -> IO ByteString
+processDot d = processDotWith d ["-Tdot"]
+
+-- | Augment a Graph via the graphviz process
+processGraphWith :: DotConfig -> Graph -> IO Graph
+processGraphWith cfg g =
+  runDotParser <$> processDot (defDirected $ view #directed g) (dotPrint cfg g)
+
+-- | Augment a Graph via the graphviz process
+processGraph :: Graph -> IO Graph
+processGraph g =
+  runDotParser <$> processDot (defDirected $ view #directed g) (dotPrint defaultDotConfig g)
+
+instance DotParse (Point Double) where
+  dotPrint _ (Point x y) =
+    intercalate "," $
+      packUTF8 . show <$> [x, y]
+
+  dotParse = token pointP
+
+pointI :: Iso' ID (Point Double)
+pointI =
+  iso
+    (runParser_ pointP . packUTF8 . label)
+    (IDQuoted . dotPrint defaultDotConfig)
+
+instance DotParse (Rect Double) where
+  dotPrint _ (Rect x z y w) =
+    intercalate "," $
+      packUTF8 . show <$> [x, y, z, w]
+
+  dotParse = token rectP
+
+rectI :: Iso' ID (Rect Double)
+rectI =
+  iso
+    (runParser_ rectP . packUTF8 . label)
+    (IDQuoted . dotPrint defaultDotConfig)
+
+-- | Bounding box ID lens
+bb_ :: Lens' Graph (Maybe ID)
+bb_ = #graphAttributes % at (ID "bb")
+
+-- | Bounding Box lens as a 'Rect'
+bbL :: Lens' Graph (Maybe (Rect Double))
+bbL = lens (preview (bb_ % _Just % rectI)) (\g r -> set bb_ (review rectI <$> r) g)
+
+-- | nodes lens
+nodesPortL :: Lens' Graph (Map.Map ID (Maybe Port, Map.Map ID ID))
+nodesPortL =
+  lens
+    ( \g ->
+        g & view #nodes
+          & fmap (\x -> (view #nodeID x, (view #port x, view #nodeAttrs x)))
+          & Map.fromList
+    )
+    (\g m -> g & #nodes .~ ((\(n, (p, a)) -> NodeStatement n p a) <$> Map.toList m))
+
+-- | nodes lens ignoring/forgetting port information
+nodesL :: Lens' Graph (Map.Map ID (Map.Map ID ID))
+nodesL =
+  lens
+    ( \g ->
+        g & view #nodes
+          & fmap (\x -> (view #nodeID x, view #nodeAttrs x))
+          & Map.fromList
+    )
+    (\g m -> g & #nodes .~ ((\(n, a) -> NodeStatement n Nothing a) <$> Map.toList m))
+
+-- | edges lens ignoring/forgetting port information
+edgesL :: Lens' Graph (Map.Map (ID, ID) (Map.Map ID ID))
+edgesL =
+  lens getEdges_ setEdges_
+
+-- | edge & attribute map
+-- ignores anonymous subgraphs
+getEdges_ :: Graph -> Map.Map (ID, ID) (Map.Map ID ID)
+getEdges_ g =
+  Map.fromList $
+    mconcat $
+      fmap
+        (\(xs, a) -> (,a) <$> xs)
+        [(edgeIDsNamed e, view #edgeAttrs e) | e <- view #edges g]
+
+setEdges_ :: Graph -> Map.Map (ID, ID) (Map.Map ID ID) -> Graph
+setEdges_ g m =
+  g
+    & #edges
+    .~ ( ( \((x0, x1), as) ->
+             EdgeStatement
+               (fromDirected (defDirected $ view #directed g))
+               (EdgeID x0 Nothing)
+               (EdgeID x1 Nothing :| [])
+               as
+         )
+           <$> Map.toList m
+       )
+
+-- | A specific attribute for all nodes in a graph
+nodesA :: ID -> Graph -> Map.Map ID (Maybe ID)
+nodesA a g = fmap (Map.lookup a) (view nodesL g)
+
+-- | node position (as a Point)
+nodePos :: Graph -> Map.Map ID (Maybe (Point Double))
+nodePos = fmap (fmap (view pointI)) . nodesA (ID "pos")
+
+--
+
+-- | Specific attribute for all edges
+edgesA :: Graph -> ID -> Map.Map (ID, ID) (Maybe ID)
+edgesA g a = fmap (Map.lookup a) (view edgesL g)
+
+-- | node width attributes
+nodeWidth :: Graph -> Map.Map ID (Maybe Double)
+nodeWidth g =
+  fmap
+    ( \case
+        Just (IDDouble x') -> Just x'
+        _ -> Nothing
+    )
+    $ nodesA (ID "width") g
+
+-- | edge width attributes
+edgeWidth :: Graph -> Map.Map (ID, ID) (Maybe Double)
+edgeWidth g =
+  fmap
+    ( \case
+        Just (IDDouble x') -> Just x'
+        _ -> Nothing
+    )
+    $ edgesA g (ID "width")
+
+-- | edge path attributes
+edgeSpline :: Graph -> Map.Map (ID, ID) (Maybe Spline)
+edgeSpline g =
+  fmap
+    ( \case
+        Just (IDQuoted x') -> Just (runParser_ splineP x')
+        _ -> Nothing
+    )
+    $ edgesA g (ID "pos")
+
+-- | typical node information after processing a dot bytestring.
+data NodeInfo = NodeInfo {nlabel :: ID, nwidth :: Double, pos :: Point Double} deriving (Eq, Show, Generic)
+
+-- | Create a list of NodeInfo from a graph.
+nodeInfo :: Graph -> Double -> [NodeInfo]
+nodeInfo g w = [NodeInfo x (fromMaybe w (join w')) p | (x, (Just p, w')) <- xs]
+  where
+    xs =
+      Map.toList $
+        merge
+          (mapMissing (\_ v -> (v, Nothing)))
+          dropMissing
+          (zipWithMatched (\_ x y -> (x, Just y)))
+          (nodePos g)
+          (nodeWidth g)
+
+-- | typical edge information after processing a dot bytestring.
+data EdgeInfo = EdgeInfo {elabel :: (ID, ID), ewidth :: Double, curve :: [PathData Double]} deriving (Eq, Show, Generic)
+
+-- | Create a list of EdgeInfo from a graph
+edgeInfo :: Graph -> Double -> [EdgeInfo]
+edgeInfo g w = [EdgeInfo (x, y) (fromMaybe w (join w')) (splinePath p) | ((x, y), (Just p, w')) <- xs]
+  where
+    xs =
+      Map.toList $
+        merge
+          (mapMissing (\_ v -> (v, Nothing)))
+          dropMissing
+          (zipWithMatched (\_ x y -> (x, Just y)))
+          (edgeSpline g)
+          (edgeWidth g)
+
+-- |
+--
+-- https://graphviz.org/docs/attr-types/splineType/
+-- format of the example is end point point and then triples (5,8,11 lengths are 1, 2 and 3 cubics)
+splinePath :: Spline -> [PathData Double]
+splinePath sp = s' <> p1' <> cs <> e'
+  where
+    s' = maybe [] (\s -> [StartP s, LineP $ view #splineP1 sp]) (view #splineStart sp)
+    e' = maybe [] (\e -> [LineP e]) (view #splineEnd sp)
+    p1' = [StartP (view #splineP1 sp)]
+    cs = (\(x, y, z) -> CubicP x y z) <$> view #splineTriples sp
+
+-- | create Statements from a (no edge label) algebraic graph
+toStatements :: Directed -> G.Graph ByteString -> [Statement]
+toStatements d g =
+  ((\x -> StatementNode $ NodeStatement (IDQuoted x) Nothing Map.empty) <$> G.vertexList g)
+    <> ( ( \(x, y) ->
+             StatementEdge $
+               EdgeStatement
+                 (fromDirected d)
+                 (EdgeID (IDQuoted x) Nothing)
+                 (fromList [EdgeID (IDQuoted y) Nothing])
+                 Map.empty
+         )
+           <$> G.edgeList g
+       )
+
+-- | Various configutaion parameters for the chart-svg Chart
+data ChartConfig = ChartConfig
+  { chartHeight :: Double,
+    chartScale :: Double,
+    edgeSize :: Double,
+    chartColor :: Colour,
+    chartBackgroundColor :: Colour,
+    nodeHeight :: Double,
+    nodeSize :: Double,
+    vshift :: Double,
+    textSize :: Double,
+    labelf :: ID -> Text
+  }
+  deriving (Generic)
+
+-- | default parameters
+defaultChartConfig :: ChartConfig
+defaultChartConfig = ChartConfig 500 72 0.5 (over lightness' (* 0.5) (palette1 0)) (set opac' 0.2 (palette1 0)) 0.5 0.5 (-3.7) 14 (Text.pack . label)
+
+-- | convert a 'Graph' processed via the graphviz commands to a 'ChartSvg'
+--
+-- >>> import Chart
+-- >>> import DotParse.Examples (exInt)
+-- >>> ex <- processGraph exInt
+-- >>> writeChartSvg "other/ex.svg" (graphToChartWith defaultChartConfig ex)
+--
+-- ![Example](other/ex.svg)
+graphToChartWith :: ChartConfig -> Graph -> ChartSvg
+graphToChartWith cfg g =
+  mempty
+    & #charts .~ named "edges" ps <> named "shapes" c0 <> named "labels" [ts]
+    & #svgOptions % #svgHeight .~ (cfg ^. #chartHeight)
+    & #hudOptions .~ (mempty & #chartAspect .~ ChartAspect)
+  where
+    glyphs w = case view (attL NodeType (ID "shape")) g of
+      Just (ID "circle") -> defaultGlyphStyle & #shape .~ CircleGlyph & #size .~ (cfg ^. #chartScale) * w & #borderSize .~ (cfg ^. #edgeSize) & #borderColor .~ (cfg ^. #chartColor) & #color .~ (cfg ^. #chartBackgroundColor)
+      Just (ID "box") -> defaultGlyphStyle & #shape .~ RectSharpGlyph (h / w) & #size .~ 72 * w & #borderSize .~ 1 & #borderColor .~ (cfg ^. #chartColor) & #color .~ (cfg ^. #chartBackgroundColor)
+      -- defaults to circle
+      _ -> defaultGlyphStyle & #shape .~ CircleGlyph & #size .~ 72 * w & #borderSize .~ 1 & #borderColor .~ (cfg ^. #chartColor) & #color .~ (cfg ^. #chartBackgroundColor)
+    h = maybe (cfg ^. #nodeHeight) (runParser_ double . packUTF8 . label) (view (attL NodeType (ID "height")) g)
+    vshift' = cfg ^. #vshift
+    -- node information
+    ns = nodeInfo g (cfg ^. #nodeSize)
+    -- edge information
+    es = edgeInfo g (cfg ^. #edgeSize)
+    -- paths
+    ps = fmap (\(EdgeInfo _ w p) -> PathChart (defaultPathStyle & #borderSize .~ (2 * w) & #borderColor .~ (cfg ^. #chartColor) & #color .~ transparent) p) es
+    -- circles
+    c0 = fmap (\(NodeInfo _ w p) -> GlyphChart (glyphs w) [p]) ns
+    -- labels
+    ts =
+      TextChart (defaultTextStyle & #size .~ (cfg ^. #textSize) & #color .~ (cfg ^. #chartColor)) ((\(NodeInfo l _ (Point x y)) -> ((cfg ^. #labelf) l, Point x (vshift' + y))) <$> ns)
+
+-- | convert a 'Graph' processed via the graphviz commands to a 'ChartSvg' using the default ChartConfig.
+graphToChart :: Graph -> ChartSvg
+graphToChart = graphToChartWith defaultChartConfig
+
+-- | Convert an algebraic graph to a dotparse graph.
+toDotGraphWith :: Directed -> Graph -> G.Graph ByteString -> Graph
+toDotGraphWith d g gg = g & #directed .~ Last (Just d) & addStatements (toStatements d gg)
+
+-- | Convert an algebraic graph to a dotparse graph, starting with the 'defaultGraph'.
+toDotGraph :: G.Graph ByteString -> Graph
+toDotGraph = toDotGraphWith Directed defaultGraph
