diff --git a/404.html b/404.html index 5ca5737..a763dd6 100644 --- a/404.html +++ b/404.html @@ -4,7 +4,7 @@
reftree
",id:"introducing-reftree",level:2},{value:"Inside reftree
",id:"inside-reftree",level:2},{value:"Functional animation",id:"functional-animation",level:2},{value:"Zipping it up",id:"zipping-it-up",level:2}];function h(e){const n={a:"a",code:"code",em:"em",h1:"h1",h2:"h2",img:"img",li:"li",p:"p",pre:"pre",ul:"ul",...(0,s.R)(),...e.components},{Details:a}=n;return a||function(e,n){throw new Error("Expected "+(n?"component":"object")+" `"+e+"` to be defined: you likely forgot to import, pass, or provide it.")}("Details",!0),(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(n.h1,{id:"visualize-your-data-structures",children:"Visualize your data structures!"}),"\n",(0,t.jsx)(n.p,{children:"This page contains the materials for my talk \u201cVisualize your data structures!\u201d."}),"\n","\n",(0,t.jsx)(r(),{controls:!0,style:{marginBottom:"1em"},url:"https://www.youtube.com/watch?v=yoayLNPTESk"}),"\n",(0,t.jsxs)(a,{children:[(0,t.jsx)("summary",{children:"Older videos"}),(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["ScalaDays Chicago, April 2017: ",(0,t.jsx)(n.a,{href:"https://www.youtube.com/watch?v=6mWaqGHeg3g",children:"https://www.youtube.com/watch?v=6mWaqGHeg3g"})]}),"\n"]})]}),"\n",(0,t.jsx)(n.p,{children:"You can use this page in two ways:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"as a reference/refresher on the material covered in the talk;"}),"\n",(0,t.jsx)(n.li,{children:"as an interactive playground where you can try the same commands I presented."}),"\n"]}),"\n",(0,t.jsx)(n.p,{children:"Throughout this page we will assume the following\ndeclarations (each section might add its own):"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:"import reftree.core._\nimport reftree.diagram._\nimport reftree.render._\nimport reftree.geometry._\nimport reftree.svg.animation.Frame\nimport reftree.svg.XmlSvgApi\nimport reftree.svg.XmlSvgApi.svgUnzip\nimport reftree.contrib.XmlInstances._\nimport reftree.contrib.OpticInstances._\nimport reftree.contrib.ZipperInstances._\nimport reftree.contrib.ShapelessInstances._\nimport reftree.util.Optics\nimport reftree.demo.Data\nimport reftree.demo.Shortcuts\nimport scala.collection.immutable._\nimport java.nio.file.Paths\nimport Diagram.{sourceCodeCaption => diagram}\n"})}),"\n",(0,t.jsx)(n.p,{children:"To start an interactive session, just run"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{children:"$ sbt demo\n@ render(List(1, 2, 3))\n"})}),"\n",(0,t.jsxs)(n.p,{children:["and open ",(0,t.jsx)(n.code,{children:"diagram.png"})," in your favorite image viewer (hopefully one that\nreloads images automatically on file change). You will also need to have\n",(0,t.jsx)(n.a,{href:"http://www.graphviz.org/",children:"GraphViz"})," installed. ",(0,t.jsx)(n.em,{children:"The interactive session\nalready has all the necessary imports in scope."})]}),"\n",(0,t.jsxs)(n.h2,{id:"introducing-reftree",children:["Introducing ",(0,t.jsx)(n.code,{children:"reftree"})]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'// extra declarations for this section\nval renderer = Renderer(\n renderingOptions = RenderingOptions(density = 100),\n directory = Paths.get(ImagePath, "visualize")\n)\nimport renderer._\n'})}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.a,{href:"https://stanch.github.io/reftree",children:"reftree"})," is a library for visualizing Scala data structures."]}),"\n",(0,t.jsx)(n.p,{children:"Let\u2019s look at a quick usage example:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'case class Person(firstName: String, age: Int)\n\nval bob = Person("Bob", 42)\n// bob: Person = Person(firstName = "Bob", age = 42)\n\ndiagram(bob).render("bob")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"bob",src:i(108).A+"",width:"313",height:"363"})}),"\n",(0,t.jsx)(n.p,{children:"That\u2019s it! You can configure the visualization as you like:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:"// render strings as a single box\nimport reftree.contrib.SimplifiedInstances.string\n"})}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'// rename the firstName field (pun not intended)\nimplicit val personConfig: ToRefTree.DerivationConfig[Person] =\n ToRefTree.DerivationConfig[Person]\n .tweakField("firstName", _.withName("name"))\n\ndiagram(bob).render("bob-simplified")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"bob-simplified",src:i(9341).A+"",width:"238",height:"364"})}),"\n",(0,t.jsxs)(n.p,{children:["There are various ways you can use ",(0,t.jsx)(n.code,{children:"reftree"}),":"]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"improving the documentation of your projects;"}),"\n",(0,t.jsx)(n.li,{children:"live-coding demos;"}),"\n",(0,t.jsx)(n.li,{children:"exploring how things work;"}),"\n",(0,t.jsx)(n.li,{children:"anywhere you need diagrams of your Scala data structures."}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:["(",(0,t.jsx)(n.em,{children:"Incidentally, this talk is an example of all of the above."}),")"]}),"\n",(0,t.jsxs)(n.p,{children:["My previous ",(0,t.jsx)(n.code,{children:"reftree"}),"-powered ",(0,t.jsx)(n.a,{href:"/reftree/docs/talks/Immutability",children:"talk"})," focused on\nimmutable data and various ways it can be manipulated (I do recommend it)."]}),"\n",(0,t.jsxs)(n.p,{children:["Today I would like to take you on a journey deep inside ",(0,t.jsx)(n.code,{children:"reftree"})," itself,\nso that we can see how some of these techniques and concepts can be applied...\nto produce visualizations of themselves \u2014 using one of my favorite ",(0,t.jsx)(n.code,{children:"reftree"}),"\nfeatures: animations."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'Animation\n .startWith(Queue(1, 2, 3))\n .repeat(3)(_.iterate(2)(q => q :+ (q.max + 1)).iterate(2)(_.tail))\n .build(Diagram.toStringCaption(_).withAnchor("queue"))\n .render("queue")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"queue",src:i(9696).A+"",width:"540",height:"922"})}),"\n",(0,t.jsxs)(n.h2,{id:"inside-reftree",children:["Inside ",(0,t.jsx)(n.code,{children:"reftree"})]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:"// extra declarations for this section\nimport reftree.contrib.SimplifiedInstances.{option, seq, list}\n"})}),"\n",(0,t.jsxs)(n.p,{children:["First, we need to grasp the basics of ",(0,t.jsx)(n.code,{children:"reftree"}),"."]}),"\n",(0,t.jsxs)(n.p,{children:["To visualize a value of some type ",(0,t.jsx)(n.code,{children:"A"}),", ",(0,t.jsx)(n.code,{children:"reftree"})," converts it into a data structure\ncalled ",(0,t.jsx)(n.code,{children:"RefTree"})," (surprise!), using a typeclass ",(0,t.jsx)(n.code,{children:"ToRefTree[A]"}),"."]}),"\n",(0,t.jsxs)(n.p,{children:["For case classes this is done automagically, using\n",(0,t.jsx)(n.a,{href:"https://github.com/milessabin/shapeless/wiki/Feature-overview:-shapeless-2.0.0#generic-representation-of-sealed-families-of-case-classes",children:(0,t.jsx)(n.em,{children:"shapeless"})}),".\n(",(0,t.jsxs)(n.em,{children:["If you are curious about the magic, take a look at ",(0,t.jsx)(n.a,{href:"https://github.com/stanch/reftree/blob/master/core/shared/src/main/scala/reftree/core/GenericInstances.scala",children:"this file"}),"."]}),")\nGiven our friend ",(0,t.jsx)(n.code,{children:"bob"}),", ",(0,t.jsx)(n.em,{children:"shapeless"})," would provide a generic representation,\nwhich includes the field names (at the type level!) and the values (as a heterogeneous list):"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'Shortcuts.generic(bob)\n// res2: shapeless.::[String with shapeless.labelled.KeyTag[Symbol with shapeless.tag.Tagged["firstName"], String], shapeless.::[Int with shapeless.labelled.KeyTag[Symbol with shapeless.tag.Tagged["age"], Int], shapeless.HNil]] = "Bob" :: 42 :: HNil\n\ndiagram(Shortcuts.generic(bob)).render("generic")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"generic",src:i(2634).A+"",width:"417",height:"460"})}),"\n",(0,t.jsxs)(n.p,{children:["This information is enough to auto-generate a ",(0,t.jsx)(n.code,{children:"RefTree"}),".\nNow, what does it look like? The best way to find out is to visualize a ",(0,t.jsx)(n.code,{children:"RefTree"}),"\nof a ",(0,t.jsx)(n.code,{children:"RefTree"}),"!"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'diagram(Shortcuts.refTree(bob)).render("reftree")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"reftree",src:i(3342).A+"",width:"1581",height:"836"})}),"\n",(0,t.jsxs)(n.p,{children:["As you can see, it contains values (",(0,t.jsx)(n.code,{children:"Val"}),") and references (",(0,t.jsx)(n.code,{children:"Ref"}),")."]}),"\n",(0,t.jsxs)(n.p,{children:["How do we get from ",(0,t.jsx)(n.code,{children:"RefTree"})," to an image though?\nThis is where ",(0,t.jsx)(n.a,{href:"http://www.graphviz.org/",children:"GraphViz"})," comes in.\nFrom a ",(0,t.jsx)(n.code,{children:"RefTree"})," we can obtain a graph definition that can be rendered by GraphViz:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'Shortcuts.graph(bob).encode\n// res5: String = """digraph "Diagram" {\n// graph [ ranksep=0.8 bgcolor="#ffffff00" ]\n// node [ shape="plaintext" fontname="Source Code Pro" fontcolor="#000000ff" ]\n// edge [ arrowsize=0.7 color="#000000ff" ]\n// "-repl.MdocSession$MdocApp$Person1385763504" [ id="-repl.MdocSession$MdocApp$Person1385763504" label=<Person | name | age |
· | 42 |
"Bob" |
reftree
",id:"introducing-reftree",level:2},{value:"Inside reftree
",id:"inside-reftree",level:2},{value:"Functional animation",id:"functional-animation",level:2},{value:"Zipping it up",id:"zipping-it-up",level:2}];function h(e){const n={a:"a",code:"code",em:"em",h1:"h1",h2:"h2",img:"img",li:"li",p:"p",pre:"pre",ul:"ul",...(0,s.R)(),...e.components},{Details:a}=n;return a||function(e,n){throw new Error("Expected "+(n?"component":"object")+" `"+e+"` to be defined: you likely forgot to import, pass, or provide it.")}("Details",!0),(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(n.h1,{id:"visualize-your-data-structures",children:"Visualize your data structures!"}),"\n",(0,t.jsx)(n.p,{children:"This page contains the materials for my talk \u201cVisualize your data structures!\u201d."}),"\n","\n",(0,t.jsx)(r(),{controls:!0,style:{marginBottom:"1em"},url:"https://www.youtube.com/watch?v=yoayLNPTESk"}),"\n",(0,t.jsxs)(a,{children:[(0,t.jsx)("summary",{children:"Older videos"}),(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["ScalaDays Chicago, April 2017: ",(0,t.jsx)(n.a,{href:"https://www.youtube.com/watch?v=6mWaqGHeg3g",children:"https://www.youtube.com/watch?v=6mWaqGHeg3g"})]}),"\n"]})]}),"\n",(0,t.jsx)(n.p,{children:"You can use this page in two ways:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"as a reference/refresher on the material covered in the talk;"}),"\n",(0,t.jsx)(n.li,{children:"as an interactive playground where you can try the same commands I presented."}),"\n"]}),"\n",(0,t.jsx)(n.p,{children:"Throughout this page we will assume the following\ndeclarations (each section might add its own):"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:"import reftree.core._\nimport reftree.diagram._\nimport reftree.render._\nimport reftree.geometry._\nimport reftree.svg.animation.Frame\nimport reftree.svg.XmlSvgApi\nimport reftree.svg.XmlSvgApi.svgUnzip\nimport reftree.contrib.XmlInstances._\nimport reftree.contrib.OpticInstances._\nimport reftree.contrib.ZipperInstances._\nimport reftree.contrib.ShapelessInstances._\nimport reftree.util.Optics\nimport reftree.demo.Data\nimport reftree.demo.Shortcuts\nimport scala.collection.immutable._\nimport java.nio.file.Paths\nimport Diagram.{sourceCodeCaption => diagram}\n"})}),"\n",(0,t.jsx)(n.p,{children:"To start an interactive session, just run"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{children:"$ sbt demo\n@ render(List(1, 2, 3))\n"})}),"\n",(0,t.jsxs)(n.p,{children:["and open ",(0,t.jsx)(n.code,{children:"diagram.png"})," in your favorite image viewer (hopefully one that\nreloads images automatically on file change). You will also need to have\n",(0,t.jsx)(n.a,{href:"http://www.graphviz.org/",children:"GraphViz"})," installed. ",(0,t.jsx)(n.em,{children:"The interactive session\nalready has all the necessary imports in scope."})]}),"\n",(0,t.jsxs)(n.h2,{id:"introducing-reftree",children:["Introducing ",(0,t.jsx)(n.code,{children:"reftree"})]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'// extra declarations for this section\nval renderer = Renderer(\n renderingOptions = RenderingOptions(density = 100),\n directory = Paths.get(ImagePath, "visualize")\n)\nimport renderer._\n'})}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.a,{href:"https://stanch.github.io/reftree",children:"reftree"})," is a library for visualizing Scala data structures."]}),"\n",(0,t.jsx)(n.p,{children:"Let\u2019s look at a quick usage example:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'case class Person(firstName: String, age: Int)\n\nval bob = Person("Bob", 42)\n// bob: Person = Person(firstName = "Bob", age = 42)\n\ndiagram(bob).render("bob")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"bob",src:i(108).A+"",width:"313",height:"363"})}),"\n",(0,t.jsx)(n.p,{children:"That\u2019s it! You can configure the visualization as you like:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:"// render strings as a single box\nimport reftree.contrib.SimplifiedInstances.string\n"})}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'// rename the firstName field (pun not intended)\nimplicit val personConfig: ToRefTree.DerivationConfig[Person] =\n ToRefTree.DerivationConfig[Person]\n .tweakField("firstName", _.withName("name"))\n\ndiagram(bob).render("bob-simplified")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"bob-simplified",src:i(9341).A+"",width:"238",height:"364"})}),"\n",(0,t.jsxs)(n.p,{children:["There are various ways you can use ",(0,t.jsx)(n.code,{children:"reftree"}),":"]}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"improving the documentation of your projects;"}),"\n",(0,t.jsx)(n.li,{children:"live-coding demos;"}),"\n",(0,t.jsx)(n.li,{children:"exploring how things work;"}),"\n",(0,t.jsx)(n.li,{children:"anywhere you need diagrams of your Scala data structures."}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:["(",(0,t.jsx)(n.em,{children:"Incidentally, this talk is an example of all of the above."}),")"]}),"\n",(0,t.jsxs)(n.p,{children:["My previous ",(0,t.jsx)(n.code,{children:"reftree"}),"-powered ",(0,t.jsx)(n.a,{href:"/reftree/docs/talks/Immutability",children:"talk"})," focused on\nimmutable data and various ways it can be manipulated (I do recommend it)."]}),"\n",(0,t.jsxs)(n.p,{children:["Today I would like to take you on a journey deep inside ",(0,t.jsx)(n.code,{children:"reftree"})," itself,\nso that we can see how some of these techniques and concepts can be applied...\nto produce visualizations of themselves \u2014 using one of my favorite ",(0,t.jsx)(n.code,{children:"reftree"}),"\nfeatures: animations."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'Animation\n .startWith(Queue(1, 2, 3))\n .repeat(3)(_.iterate(2)(q => q :+ (q.max + 1)).iterate(2)(_.tail))\n .build(Diagram.toStringCaption(_).withAnchor("queue"))\n .render("queue")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"queue",src:i(9696).A+"",width:"540",height:"922"})}),"\n",(0,t.jsxs)(n.h2,{id:"inside-reftree",children:["Inside ",(0,t.jsx)(n.code,{children:"reftree"})]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:"// extra declarations for this section\nimport reftree.contrib.SimplifiedInstances.{option, seq, list}\n"})}),"\n",(0,t.jsxs)(n.p,{children:["First, we need to grasp the basics of ",(0,t.jsx)(n.code,{children:"reftree"}),"."]}),"\n",(0,t.jsxs)(n.p,{children:["To visualize a value of some type ",(0,t.jsx)(n.code,{children:"A"}),", ",(0,t.jsx)(n.code,{children:"reftree"})," converts it into a data structure\ncalled ",(0,t.jsx)(n.code,{children:"RefTree"})," (surprise!), using a typeclass ",(0,t.jsx)(n.code,{children:"ToRefTree[A]"}),"."]}),"\n",(0,t.jsxs)(n.p,{children:["For case classes this is done automagically, using\n",(0,t.jsx)(n.a,{href:"https://github.com/milessabin/shapeless/wiki/Feature-overview:-shapeless-2.0.0#generic-representation-of-sealed-families-of-case-classes",children:(0,t.jsx)(n.em,{children:"shapeless"})}),".\n(",(0,t.jsxs)(n.em,{children:["If you are curious about the magic, take a look at ",(0,t.jsx)(n.a,{href:"https://github.com/stanch/reftree/blob/master/core/shared/src/main/scala/reftree/core/GenericInstances.scala",children:"this file"}),"."]}),")\nGiven our friend ",(0,t.jsx)(n.code,{children:"bob"}),", ",(0,t.jsx)(n.em,{children:"shapeless"})," would provide a generic representation,\nwhich includes the field names (at the type level!) and the values (as a heterogeneous list):"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'Shortcuts.generic(bob)\n// res2: shapeless.::[String with shapeless.labelled.KeyTag[Symbol with shapeless.tag.Tagged["firstName"], String], shapeless.::[Int with shapeless.labelled.KeyTag[Symbol with shapeless.tag.Tagged["age"], Int], shapeless.HNil]] = "Bob" :: 42 :: HNil\n\ndiagram(Shortcuts.generic(bob)).render("generic")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"generic",src:i(2634).A+"",width:"417",height:"460"})}),"\n",(0,t.jsxs)(n.p,{children:["This information is enough to auto-generate a ",(0,t.jsx)(n.code,{children:"RefTree"}),".\nNow, what does it look like? The best way to find out is to visualize a ",(0,t.jsx)(n.code,{children:"RefTree"}),"\nof a ",(0,t.jsx)(n.code,{children:"RefTree"}),"!"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'diagram(Shortcuts.refTree(bob)).render("reftree")\n'})}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsx)(n.img,{alt:"reftree",src:i(3342).A+"",width:"1520",height:"836"})}),"\n",(0,t.jsxs)(n.p,{children:["As you can see, it contains values (",(0,t.jsx)(n.code,{children:"Val"}),") and references (",(0,t.jsx)(n.code,{children:"Ref"}),")."]}),"\n",(0,t.jsxs)(n.p,{children:["How do we get from ",(0,t.jsx)(n.code,{children:"RefTree"})," to an image though?\nThis is where ",(0,t.jsx)(n.a,{href:"http://www.graphviz.org/",children:"GraphViz"})," comes in.\nFrom a ",(0,t.jsx)(n.code,{children:"RefTree"})," we can obtain a graph definition that can be rendered by GraphViz:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-scala",children:'Shortcuts.graph(bob).encode\n// res5: String = """digraph "Diagram" {\n// graph [ ranksep=0.8 bgcolor="#ffffff00" ]\n// node [ shape="plaintext" fontname="Source Code Pro" fontcolor="#000000ff" ]\n// edge [ arrowsize=0.7 color="#000000ff" ]\n// "-repl.MdocSession$MdocApp$Person560852288" [ id="-repl.MdocSession$MdocApp$Person560852288" label=<Person | name | age |
· | 42 |
"Bob" |
To jump into an interactive session:
$ git clone https://github.com/stanch/reftree
$ cd reftree
$ sbt demo
@ render(List(1, 2, 3))
// display diagram.png with your favorite image viewer
You can depend on the library by adding these lines to your build.sbt
:
reftree
is available for Scala 2.12 and 2.13. You can depend on the library by adding these lines to your build.sbt
:
libraryDependencies += "io.github.stanch" %% "reftree" % "1.5.0"
libraryDependencies += "io.github.stanch" %%% "reftree" % "1.5.0"
libraryDependencies += "io.github.stanch" %% "reftree" % "1.5.0"
libraryDependencies += "io.github.stanch" %%% "reftree" % "1.5.0"
import reftree.render.{Renderer, RenderingOptions}
import reftree.diagram.Diagram
import java.nio.file.Paths
val ImagePath = "images"
import monocle.macros.GenLens
val salaryLens = GenLens[Employee](_.salary)
// salaryLens: monocle.package.Lens[Employee, Long] = repl.MdocSession$MdocApp$$anon$1@3c477363
salaryLens.get(startup.founder)
// res10: Long = 4000L
salaryLens.modify(s => s + 10)(startup.founder)
// res11: Employee = Employee(name = "Michael", salary = 4010L)
import monocle.macros.GenLens
val salaryLens = GenLens[Employee](_.salary)
// salaryLens: monocle.package.Lens[Employee, Long] = repl.MdocSession$MdocApp$$anon$1@5eedf892
salaryLens.get(startup.founder)
// res10: Long = 4000L
salaryLens.modify(s => s + 10)(startup.founder)
// res11: Employee = Employee(name = "Michael", salary = 4010L)
diagram(OpticFocus(salaryLens, startup.founder)).render("salaryLens")
We can also define a lens that focuses on the startup’s founder:
-val founderLens = GenLens[Startup](_.founder)
// founderLens: monocle.package.Lens[Startup, Employee] = repl.MdocSession$MdocApp$$anon$2@389ba703
founderLens.get(startup)
// res13: Employee = Employee(name = "Michael", salary = 4000L)
val founderLens = GenLens[Startup](_.founder)
// founderLens: monocle.package.Lens[Startup, Employee] = repl.MdocSession$MdocApp$$anon$2@56f23749
founderLens.get(startup)
// res13: Employee = Employee(name = "Michael", salary = 4000L)
diagram(OpticFocus(founderLens, startup)).render("founderLens")
It’s not apparent yet how this would help, but the trick is that lenses can be composed:
-val founderSalaryLens = founderLens composeLens salaryLens
// founderSalaryLens: monocle.PLens[Startup, Startup, Long, Long] = monocle.PLens$$anon$1@21c54dde
founderSalaryLens.get(startup)
// res15: Long = 4000L
founderSalaryLens.modify(s => s + 10)(startup)
// res16: Startup = Startup(
// name = "Acme",
// founder = Employee(name = "Michael", salary = 4010L),
// team = List(
// Employee(name = "Adam", salary = 2100L),
// Employee(name = "Bella", salary = 2100L),
// Employee(name = "Chad", salary = 1980L),
// Employee(name = "Delia", salary = 1850L)
// )
// )
val founderSalaryLens = founderLens composeLens salaryLens
// founderSalaryLens: monocle.PLens[Startup, Startup, Long, Long] = monocle.PLens$$anon$1@3b84f3b2
founderSalaryLens.get(startup)
// res15: Long = 4000L
founderSalaryLens.modify(s => s + 10)(startup)
// res16: Startup = Startup(
// name = "Acme",
// founder = Employee(name = "Michael", salary = 4010L),
// team = List(
// Employee(name = "Adam", salary = 2100L),
// Employee(name = "Bella", salary = 2100L),
// Employee(name = "Chad", salary = 1980L),
// Employee(name = "Delia", salary = 1850L)
// )
// )
diagram(OpticFocus(founderSalaryLens, startup)).render("founderSalaryLens")
One interesting thing is that lenses can focus on anything, not just direct attributes of the data. @@ -129,7 +129,7 @@
diagram(OpticFocus(vowelTraversal, "example")).render("vowelTraversal")
We can use it to give our founder a funny name:
-val employeeNameLens = GenLens[Employee](_.name)
// employeeNameLens: monocle.package.Lens[Employee, String] = repl.MdocSession$MdocApp$$anon$3@8eeb1fa
val founderVowelTraversal = founderLens composeLens employeeNameLens composeTraversal vowelTraversal
// founderVowelTraversal: monocle.PTraversal[Startup, Startup, Char, Char] = monocle.PTraversal$$anon$2@37aed08b
founderVowelTraversal.modify(v => v.toUpper)(startup)
// res19: Startup = Startup(
// name = "Acme",
// founder = Employee(name = "MIchAEl", salary = 4000L),
// team = List(
// Employee(name = "Adam", salary = 2100L),
// Employee(name = "Bella", salary = 2100L),
// Employee(name = "Chad", salary = 1980L),
// Employee(name = "Delia", salary = 1850L)
// )
// )
val employeeNameLens = GenLens[Employee](_.name)
// employeeNameLens: monocle.package.Lens[Employee, String] = repl.MdocSession$MdocApp$$anon$3@47f92c7a
val founderVowelTraversal = founderLens composeLens employeeNameLens composeTraversal vowelTraversal
// founderVowelTraversal: monocle.PTraversal[Startup, Startup, Char, Char] = monocle.PTraversal$$anon$2@73f506c1
founderVowelTraversal.modify(v => v.toUpper)(startup)
// res19: Startup = Startup(
// name = "Acme",
// founder = Employee(name = "MIchAEl", salary = 4000L),
// team = List(
// Employee(name = "Adam", salary = 2100L),
// Employee(name = "Bella", salary = 2100L),
// Employee(name = "Chad", salary = 1980L),
// Employee(name = "Delia", salary = 1850L)
// )
// )
diagram(OpticFocus(founderVowelTraversal, startup)).render("founderVowelTraversal")
So far we have replaced the copy
boilerplate with a number of lens declarations.
diff --git a/docs/talks/Visualize/index.html b/docs/talks/Visualize/index.html
index 4b93088..f22eeed 100644
--- a/docs/talks/Visualize/index.html
+++ b/docs/talks/Visualize/index.html
@@ -4,7 +4,7 @@
Now, what does it look like? The best way to find out is to visualize a RefTree
of a RefTree
!
diagram(Shortcuts.refTree(bob)).render("reftree")
-
+
As you can see, it contains values (Val
) and references (Ref
).
How do we get from RefTree
to an image though?
This is where GraphViz comes in.
From a RefTree
we can obtain a graph definition that can be rendered by GraphViz:
-Shortcuts.graph(bob).encode
// res5: String = """digraph "Diagram" {
// graph [ ranksep=0.8 bgcolor="#ffffff00" ]
// node [ shape="plaintext" fontname="Source Code Pro" fontcolor="#000000ff" ]
// edge [ arrowsize=0.7 color="#000000ff" ]
// "-repl.MdocSession$MdocApp$Person560852288" [ id="-repl.MdocSession$MdocApp$Person560852288" label=<<table cellspacing="0" cellpadding="6" cellborder="0" columns="*" bgcolor="#ffffff00" style="rounded"><tr><td port="n" rowspan="2">Person</td><td bgcolor="#ffffff00"><i>name</i></td><td bgcolor="#ffffff00"><i>age</i></td></tr><hr/><tr><td port="0" bgcolor="#ffffff00">·</td><td bgcolor="#ffffff00">42</td></tr></table>> ] [ color="#104e8bff" fontcolor="#104e8bff" ]
// "-java.lang.String1983877187" [ id="-java.lang.String1983877187" label=<<table cellspacing="0" cellpadding="6" cellborder="0" columns="*" bgcolor="#ffffff00" style="rounded"><tr><td port="n" rowspan="2">"Bob"</td></tr></table>> ] [ color="#104e8bff" fontcolor="#104e8bff" ]
// "-repl.MdocSession$MdocApp$Person560852288":"0":"s" -> "-java.lang.String1983877187":"n":"n" [ id="-repl.MdocSession$MdocApp$Person560852288-0-java.lang.String1983877187" ] [ color="#104e8bff" ]
// }"""
+Shortcuts.graph(bob).encode
// res5: String = """digraph "Diagram" {
// graph [ ranksep=0.8 bgcolor="#ffffff00" ]
// node [ shape="plaintext" fontname="Source Code Pro" fontcolor="#000000ff" ]
// edge [ arrowsize=0.7 color="#000000ff" ]
// "-repl.MdocSession$MdocApp$Person1385763504" [ id="-repl.MdocSession$MdocApp$Person1385763504" label=<<table cellspacing="0" cellpadding="6" cellborder="0" columns="*" bgcolor="#ffffff00" style="rounded"><tr><td port="n" rowspan="2">Person</td><td bgcolor="#ffffff00"><i>name</i></td><td bgcolor="#ffffff00"><i>age</i></td></tr><hr/><tr><td port="0" bgcolor="#ffffff00">·</td><td bgcolor="#ffffff00">42</td></tr></table>> ] [ color="#104e8bff" fontcolor="#104e8bff" ]
// "-java.lang.String78928860" [ id="-java.lang.String78928860" label=<<table cellspacing="0" cellpadding="6" cellborder="0" columns="*" bgcolor="#ffffff00" style="rounded"><tr><td port="n" rowspan="2">"Bob"</td></tr></table>> ] [ color="#104e8bff" fontcolor="#104e8bff" ]
// "-repl.MdocSession$MdocApp$Person1385763504":"0":"s" -> "-java.lang.String78928860":"n":"n" [ id="-repl.MdocSession$MdocApp$Person1385763504-0-java.lang.String78928860" ] [ color="#104e8bff" ]
// }"""
Going even further, we can ask GraphViz for an SVG output:
-Shortcuts.svg(bob)
// res6: xml.Node = <svg width="171pt" height="168pt" viewBox="0.00 0.00 171.00 168.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g id="graph0" class="graph"><title>Diagram</title><polygon fill="transparent" stroke="transparent" points="-4,4 -4,-164 167,-164 167,4 -4,4"/><!-- -repl.MdocSession$MdocApp$Person560852288 --><g id="-repl.MdocSession$MdocApp$Person560852288" class="node"><title>-repl.MdocSession$MdocApp$Person560852288</title><path fill="transparent" stroke="transparent" d="M20,-100C20,-100 143,-100 143,-100 149,-100 155,-106 155,-112 155,-112 155,-144 155,-144 155,-150 149,-156 143,-156 143,-156 20,-156 20,-156 14,-156 8,-150 8,-144 8,-144 8,-112 8,-112 8,-106 14,-100 20,-100"/><text text-anchor="start" x="15.5" y="-124.3" font-family="Source Code Pro" font-size="14.00" fill="#104e8b">Person</text><polygon fill="transparent" stroke="transparent" points="71.5,-128 71.5,-155 117.5,-155 117.5,-128 71.5,-128"/><text text-anchor="start" x="77.5" y="-138.8" font-family="Source Code Pro" font-style="italic" font-size="14.00" fill="#104e8b">name</text><polygon fill="transparent" stroke="transparent" points="117.5,-128 117.5,-155 154.5,-155 154.5,-128 117.5,-128"/><text text-anchor="start" x="123.5" y="-138.8" font-family="Source Code Pro" font-style="italic" font-size="14.00" fill="#104e8b">age</text><polygon fill="transparent" stroke="transparent" points="71.5,-101 71.5,-128 117.5,-128 117.5,-101 71.5,-101"/><text text-anchor="start" x="90" y="-110.8" font-family="Source Code Pro" font-size="14.00" fill="#104e8b">·</text><polygon fill="transparent" stroke="transparent" points="117.5,-101 117.5,-128 154.5,-128 154.5,-101 117.5,-101"/><text text-anchor="start" x="127.5" y="-110.8" font-family="Source Code Pro" font-size="14.00" fill="#104e8b">42</text><polygon fill="#104e8b" stroke="#104e8b" points="71.5,-101 71.5,-156 71.5,-156 71.5,-101 71.5,-101"/><polygon fill="#104e8b" stroke="#104e8b" points="117.5,-128 117.5,-156 117.5,-156 117.5,-128 117.5,-128"/><polygon fill="#104e8b" stroke="#104e8b" points="71.5,-128 71.5,-128 117.5,-128 117.5,-128 71.5,-128"/><polygon fill="#104e8b" stroke="#104e8b" points="117.5,-128 117.5,-128 155.5,-128 155.5,-128 117.5,-128"/><polygon fill="#104e8b" stroke="#104e8b" points="117.5,-100 117.5,-128 117.5,-128 117.5,-100 117.5,-100"/><path fill="none" stroke="#104e8b" d="M20,-100C20,-100 143,-100 143,-100 149,-100 155,-106 155,-112 155,-112 155,-144 155,-144 155,-150 149,-156 143,-156 143,-156 20,-156 20,-156 14,-156 8,-150 8,-144 8,-144 8,-112 8,-112 8,-106 14,-100 20,-100"/></g><!-- -java.lang.String1983877187 --><g id="-java.lang.String1983877187" class="node"><title>-java.lang.String1983877187</title><path fill="transparent" stroke="transparent" d="M76.5,-4C76.5,-4 112.5,-4 112.5,-4 117.5,-4 122.5,-9 122.5,-14 122.5,-14 122.5,-24 122.5,-24 122.5,-29 117.5,-34 112.5,-34 112.5,-34 76.5,-34 76.5,-34 71.5,-34 66.5,-29 66.5,-24 66.5,-24 66.5,-14 66.5,-14 66.5,-9 71.5,-4 76.5,-4"/><text text-anchor="start" x="73.5" y="-15.3" font-family="Source Code Pro" font-size="14.00" fill="#104e8b">"Bob"</text><path fill="none" stroke="#104e8b" d="M76.5,-4C76.5,-4 112.5,-4 112.5,-4 117.5,-4 122.5,-9 122.5,-14 122.5,-14 122.5,-24 122.5,-24 122.5,-29 117.5,-34 112.5,-34 112.5,-34 76.5,-34 76.5,-34 71.5,-34 66.5,-29 66.5,-24 66.5,-24 66.5,-14 66.5,-14 66.5,-9 71.5,-4 76.5,-4"/></g><!-- -repl.MdocSession$MdocApp$Person560852288->-java.lang.String1983877187 --><g id="-repl.MdocSession$MdocApp$Person560852288-0-java.lang.String1983877187" class="edge"><title>-repl.MdocSession$MdocApp$Person560852288:s->-java.lang.String1983877187:n</title><path fill="none" stroke="#104e8b" d="M94.5,-100C94.5,-73.19 94.5,-64.76 94.5,-41.1"/><polygon fill="#104e8b" stroke="#104e8b" points="96.95,-41 94.5,-34 92.05,-41 96.95,-41"/></g></g></svg>
+Shortcuts.svg(bob)
// res6: xml.Node = <svg width="171pt" height="168pt" viewBox="0.00 0.00 171.00 168.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g id="graph0" class="graph"><title>Diagram</title><polygon fill="transparent" stroke="transparent" points="-4,4 -4,-164 167,-164 167,4 -4,4"/><!-- -repl.MdocSession$MdocApp$Person1385763504 --><g id="-repl.MdocSession$MdocApp$Person1385763504" class="node"><title>-repl.MdocSession$MdocApp$Person1385763504</title><path fill="transparent" stroke="transparent" d="M20,-100C20,-100 143,-100 143,-100 149,-100 155,-106 155,-112 155,-112 155,-144 155,-144 155,-150 149,-156 143,-156 143,-156 20,-156 20,-156 14,-156 8,-150 8,-144 8,-144 8,-112 8,-112 8,-106 14,-100 20,-100"/><text text-anchor="start" x="15.5" y="-124.3" font-family="Source Code Pro" font-size="14.00" fill="#104e8b">Person</text><polygon fill="transparent" stroke="transparent" points="71.5,-128 71.5,-155 117.5,-155 117.5,-128 71.5,-128"/><text text-anchor="start" x="77.5" y="-138.8" font-family="Source Code Pro" font-style="italic" font-size="14.00" fill="#104e8b">name</text><polygon fill="transparent" stroke="transparent" points="117.5,-128 117.5,-155 154.5,-155 154.5,-128 117.5,-128"/><text text-anchor="start" x="123.5" y="-138.8" font-family="Source Code Pro" font-style="italic" font-size="14.00" fill="#104e8b">age</text><polygon fill="transparent" stroke="transparent" points="71.5,-101 71.5,-128 117.5,-128 117.5,-101 71.5,-101"/><text text-anchor="start" x="90" y="-110.8" font-family="Source Code Pro" font-size="14.00" fill="#104e8b">·</text><polygon fill="transparent" stroke="transparent" points="117.5,-101 117.5,-128 154.5,-128 154.5,-101 117.5,-101"/><text text-anchor="start" x="127.5" y="-110.8" font-family="Source Code Pro" font-size="14.00" fill="#104e8b">42</text><polygon fill="#104e8b" stroke="#104e8b" points="71.5,-101 71.5,-156 71.5,-156 71.5,-101 71.5,-101"/><polygon fill="#104e8b" stroke="#104e8b" points="117.5,-128 117.5,-156 117.5,-156 117.5,-128 117.5,-128"/><polygon fill="#104e8b" stroke="#104e8b" points="71.5,-128 71.5,-128 117.5,-128 117.5,-128 71.5,-128"/><polygon fill="#104e8b" stroke="#104e8b" points="117.5,-128 117.5,-128 155.5,-128 155.5,-128 117.5,-128"/><polygon fill="#104e8b" stroke="#104e8b" points="117.5,-100 117.5,-128 117.5,-128 117.5,-100 117.5,-100"/><path fill="none" stroke="#104e8b" d="M20,-100C20,-100 143,-100 143,-100 149,-100 155,-106 155,-112 155,-112 155,-144 155,-144 155,-150 149,-156 143,-156 143,-156 20,-156 20,-156 14,-156 8,-150 8,-144 8,-144 8,-112 8,-112 8,-106 14,-100 20,-100"/></g><!-- -java.lang.String78928860 --><g id="-java.lang.String78928860" class="node"><title>-java.lang.String78928860</title><path fill="transparent" stroke="transparent" d="M76.5,-4C76.5,-4 112.5,-4 112.5,-4 117.5,-4 122.5,-9 122.5,-14 122.5,-14 122.5,-24 122.5,-24 122.5,-29 117.5,-34 112.5,-34 112.5,-34 76.5,-34 76.5,-34 71.5,-34 66.5,-29 66.5,-24 66.5,-24 66.5,-14 66.5,-14 66.5,-9 71.5,-4 76.5,-4"/><text text-anchor="start" x="73.5" y="-15.3" font-family="Source Code Pro" font-size="14.00" fill="#104e8b">"Bob"</text><path fill="none" stroke="#104e8b" d="M76.5,-4C76.5,-4 112.5,-4 112.5,-4 117.5,-4 122.5,-9 122.5,-14 122.5,-14 122.5,-24 122.5,-24 122.5,-29 117.5,-34 112.5,-34 112.5,-34 76.5,-34 76.5,-34 71.5,-34 66.5,-29 66.5,-24 66.5,-24 66.5,-14 66.5,-14 66.5,-9 71.5,-4 76.5,-4"/></g><!-- -repl.MdocSession$MdocApp$Person1385763504->-java.lang.String78928860 --><g id="-repl.MdocSession$MdocApp$Person1385763504-0-java.lang.String78928860" class="edge"><title>-repl.MdocSession$MdocApp$Person1385763504:s->-java.lang.String78928860:n</title><path fill="none" stroke="#104e8b" d="M94.5,-100C94.5,-73.19 94.5,-64.76 94.5,-41.1"/><polygon fill="#104e8b" stroke="#104e8b" points="96.95,-41 94.5,-34 92.05,-41 96.95,-41"/></g></g></svg>
At this point you might be guessing how we can use this as a basis for our animation approach.
Every state of a data structure will be a separate frame in the SVG format.
However, an animation consisting of these frames alone would be too jumpy.
@@ -114,7 +114,7 @@
Functio
inside a data structure of type A
and provide read-write access to it.
We will use the excellent Monocle library
to create lenses and other optics along the way:
-import monocle.macros.GenLens
val x = GenLens[Point](_.x)
// x: monocle.package.Lens[Point, Double] = repl.MdocSession$MdocApp$$anon$1@270b030c
val y = GenLens[Point](_.y)
// y: monocle.package.Lens[Point, Double] = repl.MdocSession$MdocApp$$anon$2@2fe9f8a4
(diagram(OpticFocus(x, point)).toNamespace("x") +
diagram(OpticFocus(y, point)).toNamespace("y")).render("x+y")
+import monocle.macros.GenLens
val x = GenLens[Point](_.x)
// x: monocle.package.Lens[Point, Double] = repl.MdocSession$MdocApp$$anon$1@6f44eadb
val y = GenLens[Point](_.y)
// y: monocle.package.Lens[Point, Double] = repl.MdocSession$MdocApp$$anon$2@f82e5b8
(diagram(OpticFocus(x, point)).toNamespace("x") +
diagram(OpticFocus(y, point)).toNamespace("y")).render("x+y")
Lenses provide several methods to manipulate data:
x.get(point)
// res10: Double = 0.0
y.set(20)(point)
// res11: Point = Point(x = 0.0, y = 20.0)
y.modify(_ + 20)(point)
// res12: Point = Point(x = 0.0, y = 30.0)
@@ -122,12 +122,12 @@ Functio
and update the point field by field.
We do this by piping Interpolation.double
through x
and y
lenses
and combining the resulting interpolations:
-val pointInterpolation = (
x.interpolateWith(Interpolation.double) +
y.interpolateWith(Interpolation.double))
// pointInterpolation: Interpolation[Point] = reftree.geometry.Interpolation$$anonfun$apply$4@53ecdb48
val points = pointInterpolation.sample(Point(0, 0), Point(10, 20), 5).toList
// points: List[Point] = List(
// Point(x = 0.0, y = 0.0),
// Point(x = 2.5, y = 5.0),
// Point(x = 5.0, y = 10.0),
// Point(x = 7.5, y = 15.0),
// Point(x = 10.0, y = 20.0)
// )
diagram(points).render("points")
+val pointInterpolation = (
x.interpolateWith(Interpolation.double) +
y.interpolateWith(Interpolation.double))
// pointInterpolation: Interpolation[Point] = reftree.geometry.Interpolation$$anonfun$apply$4@2c57a744
val points = pointInterpolation.sample(Point(0, 0), Point(10, 20), 5).toList
// points: List[Point] = List(
// Point(x = 0.0, y = 0.0),
// Point(x = 2.5, y = 5.0),
// Point(x = 5.0, y = 10.0),
// Point(x = 7.5, y = 15.0),
// Point(x = 10.0, y = 20.0)
// )
diagram(points).render("points")
Of course, reftree
already defines this as Point.interpolation
.
Using the same approach, we can build a polyline interpolator
(assuming the polylines being interpolated consist of equal number of points):
-Data.polyline1
// res14: Polyline = Polyline(
// points = List(Point(x = 0.0, y = 10.0), Point(x = 10.0, y = 20.0))
// )
Data.polyline2
// res15: Polyline = Polyline(
// points = List(Point(x = 20.0, y = 30.0), Point(x = 40.0, y = 50.0))
// )
val polylineInterpolation = (GenLens[Polyline](_.points)
.interpolateEachWith(Point.interpolation))
// polylineInterpolation: Interpolation[Polyline] = reftree.geometry.Interpolation$$anonfun$apply$4@762f7035
val polylines = polylineInterpolation.sample(Data.polyline1, Data.polyline2, 3).toList
// polylines: List[Polyline] = List(
// Polyline(points = List(Point(x = 0.0, y = 10.0), Point(x = 10.0, y = 20.0))),
// Polyline(points = List(Point(x = 10.0, y = 20.0), Point(x = 25.0, y = 35.0))),
// Polyline(points = List(Point(x = 20.0, y = 30.0), Point(x = 40.0, y = 50.0)))
// )
diagram(polylines).render("polylines")
+Data.polyline1
// res14: Polyline = Polyline(
// points = List(Point(x = 0.0, y = 10.0), Point(x = 10.0, y = 20.0))
// )
Data.polyline2
// res15: Polyline = Polyline(
// points = List(Point(x = 20.0, y = 30.0), Point(x = 40.0, y = 50.0))
// )
val polylineInterpolation = (GenLens[Polyline](_.points)
.interpolateEachWith(Point.interpolation))
// polylineInterpolation: Interpolation[Polyline] = reftree.geometry.Interpolation$$anonfun$apply$4@1e08a7e1
val polylines = polylineInterpolation.sample(Data.polyline1, Data.polyline2, 3).toList
// polylines: List[Polyline] = List(
// Polyline(points = List(Point(x = 0.0, y = 10.0), Point(x = 10.0, y = 20.0))),
// Polyline(points = List(Point(x = 10.0, y = 20.0), Point(x = 25.0, y = 35.0))),
// Polyline(points = List(Point(x = 20.0, y = 30.0), Point(x = 40.0, y = 50.0)))
// )
diagram(polylines).render("polylines")
We are finally ready to implement our first substantial interpolator: one that morphs graph edges.
The following approach is inspired by Mike Bostock’s path tween,
@@ -150,20 +150,20 @@ Functio
An optic that focuses on an element deep inside XML or any other recursive data structure: Optics.collectFirst
.
It is actually an Optional
, not a Lens
, since the element might be missing.
-val edgePathElement = Optics.collectFirst(XmlSvgApi.select("path"))
// edgePathElement: monocle.package.Optional[xml.Node, xml.Node] = monocle.Optional$$anon$6@9430e9f
diagram(OpticFocus(edgePathElement, Data.edge1)).render("edgePathElement")
+val edgePathElement = Optics.collectFirst(XmlSvgApi.select("path"))
// edgePathElement: monocle.package.Optional[xml.Node, xml.Node] = monocle.Optional$$anon$6@56d284c9
diagram(OpticFocus(edgePathElement, Data.edge1)).render("edgePathElement")
Next, we need to “descend” to the d
attribute. Here is where optics really shine:
we can compose Optional[A, B]
with Optional[B, C]
to get an Optional[A, C]
:
-val d = XmlSvgApi.attr("d")
// d: monocle.package.Optional[xml.Node, String] = monocle.POptional$$anon$1@698ffbcb
val edgePathString = edgePathElement composeOptional d
// edgePathString: monocle.POptional[xml.Node, xml.Node, String, String] = monocle.POptional$$anon$1@5c8c4b29
diagram(OpticFocus(edgePathString, Data.edge1)).render("edgePathString")
+val d = XmlSvgApi.attr("d")
// d: monocle.package.Optional[xml.Node, String] = monocle.POptional$$anon$1@4ee5148e
val edgePathString = edgePathElement composeOptional d
// edgePathString: monocle.POptional[xml.Node, xml.Node, String, String] = monocle.POptional$$anon$1@5f99f791
diagram(OpticFocus(edgePathString, Data.edge1)).render("edgePathString")
Next, we will use an isomorphism, another kind of optic, to view
the string as a nice case class:
-Path.stringIso
// res21: monocle.package.Iso[String, Path] = monocle.PIso$$anon$9@512f2655
val edgePath = edgePathString composeIso Path.stringIso
// edgePath: monocle.POptional[xml.Node, xml.Node, Path, Path] = monocle.POptional$$anon$1@4009fd19
diagram(edgePath.getOption(Data.edge1)).render("edgePath")
+Path.stringIso
// res21: monocle.package.Iso[String, Path] = monocle.PIso$$anon$9@4f5642c
val edgePath = edgePathString composeIso Path.stringIso
// edgePath: monocle.POptional[xml.Node, xml.Node, Path, Path] = monocle.POptional$$anon$1@46435cb7
diagram(edgePath.getOption(Data.edge1)).render("edgePath")
And finally, another isomorphism takes us from a Path
to its sampled representation
as a Polyline
. (Purists will say that this is not really an isomorphism because
it’s not reversible, but with a lot of points you can get pretty close ;))
-Path.polylineIso(points = 4)
// res23: monocle.package.Iso[Path, Polyline] = monocle.PIso$$anon$9@56ce1770
def edgePolyline(points: Int) = edgePath composeIso Path.polylineIso(points)
diagram(edgePolyline(4).getOption(Data.edge1)).render("edgePolyline")
+Path.polylineIso(points = 4)
// res23: monocle.package.Iso[Path, Polyline] = monocle.PIso$$anon$9@20647195
def edgePolyline(points: Int) = edgePath composeIso Path.polylineIso(points)
diagram(edgePolyline(4).getOption(Data.edge1)).render("edgePolyline")
Let’s interpolate!
def edgeInterpolation(points: Int) = edgePolyline(points).interpolateWith(Polyline.interpolation)
def edges(points: Int, frames: Int) = (Data.edge1 +:
edgeInterpolation(points).sample(Data.edge1, Data.edge2, frames, inclusive = false) :+
Data.edge2)
AnimatedGifRenderer.renderFrames(
edges(4, 4).map(Frame(_)),
Paths.get(ImagePath, "visualize", "edges-4.gif"),
RenderingOptions(density = 200),
AnimationOptions(framesPerSecond = 1)
)
AnimatedGifRenderer.renderFrames(
edges(100, 32).map(Frame(_)),
Paths.get(ImagePath, "visualize", "edges-100.gif"),
RenderingOptions(density = 200),
AnimationOptions(framesPerSecond = 8)
)
diff --git a/docs/talks/index.html b/docs/talks/index.html
index 15e062e..050ae4e 100644
--- a/docs/talks/index.html
+++ b/docs/talks/index.html
@@ -4,7 +4,7 @@
Talks / Demos | RefTree
-
+
diff --git a/index.html b/index.html
index 1038022..d6aa398 100644
--- a/index.html
+++ b/index.html
@@ -4,7 +4,7 @@
RefTree
-
+