Skip to content

hejfelix/reftree

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

71 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

reftree — diagrams for immutable data

Join the chat at https://gitter.im/stanch/reftree

This project aims to provide visualizations for common functional data structures used in Scala. The visualizations are generated automatically from code, which allows to use them in an interactive fashion. To use this library you will need to have GraphViz installed (and have dot on your PATH).

For more examples see the materials for my talk “Unzipping Immutability”.

Features

  • Pre-made visualizations of many standard collections.

  • Automatic visualization of case classes (using shapeless).
case class Employee(
  name: String,
  salary: Long
)

case class Startup(
  name: String,
  founder: Employee,
  team: List[Employee]
)

  • Static images as well as animations can be generated.
  • Hassle-free captions (using sourcecode).

Getting Started

RefTree

This library renders diagrams based on a simple data representation called RefTree. Essentially, a RefTree denotes either an object (AnyRef) with a number of fields, or a primitive (AnyVal).

To render a value of type A, you will need an implicit instance of ToRefTree[A]. For many Scala collections, as well as case classes, no extra work is needed, as these instances are readily available or generated on the fly.

For examples of manual instance derivation, see the contrib package.

Renderer

To render diagrams and animations, you will need a Renderer:

import reftree.render._
import reftree.diagram._
import java.nio.file.Paths
import scala.collection.immutable.Queue

val renderer = Renderer(
  renderingOptions = RenderingOptions(density = 75),
  directory = Paths.get("images", "usage")
)

You can also pass a format parameter as a String to the Renderer constructor to specify the format you require. The default is png. You can specify any file type supported by dot -T.

There are two ways to use it:

// using the `render` method
renderer.render("queue", Diagram(Queue(1)))

// using syntactic sugar
import renderer._
Diagram(Queue(1)).render("queue")

There are various rendering options you can set, for example:

// using the `render` method
renderer.tweakRendering(_.withVerticalSpacing(2)).render("queue", Diagram(Queue(1)))

// using syntactic sugar
Diagram(Queue(1)).render("queue", _.withVerticalSpacing(2))

Diagram

Diagrams can be created and combined into bigger diagrams using the following API:

// no caption
Diagram(Queue(1)).render("caption-none")

// automatically set caption to "Queue(1) :+ 2"
Diagram.sourceCodeCaption(Queue(1) :+ 2).render("caption-source")

// use toString to get the caption, i.e. "Queue(1, 2)"
Diagram.toStringCaption(Queue(1) :+ 2).render("caption-tostring")

// merge two diagrams, set captions manually
(Diagram(Queue(1)).withCaption("one") + Diagram(Queue(2)).withCaption("two")).render("one-two")

// isolate each diagram in its own namespace (graph nodes will not be shared across them)
(Diagram(Queue(1)).toNamespace("one") + Diagram(Queue(2)).toNamespace("two")).render("namespaced")

Animation

Animation is essentially a sequence of diagrams, which can be rendered to an animated GIF. The simplest way to create an animation is to use the builder API:

(Animation
  .startWith(Queue(1))
  .iterateWithIndex(2)((queue, i)  queue :+ (i + 1))
  .build()
  .render("animation-simple"))

You can also configure how the diagram for each frame is produced:

(Animation
  .startWith(Queue(1))
  .iterateWithIndex(2)((queue, i)  queue :+ (i + 1))
  .build(Diagram(_).withCaption("My Queue").withColor(2))
  .render("animation-captioned-red"))

Note that by default the library will try to reduce the average movement of all tree nodes across animation frames. Sometimes you want to “anchor” the root of the data structure instead, to force it to stay still while everything else is moving. You can achieve this via withAnchor method:

(Animation
  .startWith(Queue(1))
  .iterateWithIndex(2)((queue, i)  queue :+ (i + 1))
  .build(Diagram(_).withAnchor("queue").withCaption("This node is anchored!"))
  .render("animation-anchored"))

Finally, animations can be combined in sequence or in parallel, for example:

val queue1 = (Animation
  .startWith(Queue(1))
  .iterateWithIndex(2)((queue, i)  queue :+ (i + 1))
  .build()
  .toNamespace("one"))

val queue2 = (Animation
  .startWith(Queue(10))
  .iterateWithIndex(2)((queue, i)  queue :+ (10 * (i + 1)))
  .build()
  .toNamespace("two"))

(queue1 + queue2).render("animation-parallel")

See the materials for my talk “Unzipping Immutability” for more inspiration!

Usage

This project is intended for educational purposes and therefore is licensed under GPL 3.0.

To try it interactively:

$ 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 (the latest version can be found here: Download ):

resolvers ++= Seq(
  Resolver.bintrayRepo("stanch", "maven"),
  Resolver.bintrayRepo("drdozer", "maven")
)

libraryDependencies += "org.stanch" %% "reftree" % "latest-version"

About

Automatic object tree diagrams for immutable data

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Scala 100.0%