The renderer

One of the central components of Gorilla is its renderer. It is the renderer that determines how Clojure values appear when they are output, so it is ultimately responsible for things like plotting graphs, drawing tables and matrices etc. as well as showing more mundane values like numbers and lists. You don’t need to understand how the renderer works to use Gorilla, but you might be interested anyway. Gorilla’s renderer is very flexible and easily extensible, meaning you can customise Gorilla to show Clojure values in the way that’s most useful to you. This document will explain what you need to know to do that.

First, we’ll look at the idea behind the renderer, then we’ll work through the various stages in the rendering process. After that, we’ll look in detail at the stage of this process that you’re most likely to customise. Finally we’ll present a sort of style guide, to help you figure out the best way to implement custom renderers for your values.

The big idea

The essence of Gorilla is: run some Clojure code that evaluates to some value, and then show that value to the user. In Gorilla, plotting a graph, or showing a table isn’t a side-effect of your code - it’s just a nice way of looking at the value your code produces. You could say that Gorilla is fundamentally value-based. Being strictly value-based like this a limitation, but it’s an empowering limitation: it makes it possible to compose and aggregate rendered objects just like you compose and aggregate Clojure values, to save worksheets with their rendered output intact, and even to manipulate the output of worksheets that you haven’t/can’t run. It should be stated for balance that there are some things that Gorilla’s value-based rendering is not great at, like rapidly updating plots with many data points for instance, but it will hopefully do much of what you want to get done, and do it elegantly!

From evaluation to screen

So, how does a value get transformed into what you see on screen? And how do things like rendering an aggregate of values (say, a table of plots) work?

There are three steps to the evaluation and rendering process in Gorilla:

The render protocol

Let’s look at the second step of the evaluation-rendering process in more detail. Gorilla carries out this process rather simply by calling a single function, gorilla-renderable.core/render, on the value. This is the sole function in the Renderable protocol, found in the gorilla-renderable.core namespace. This namespace lives in its own tiny project, with no dependencies, so supporting Gorilla rendering is a very light addition to your project.

The render function takes the value and returns its rendered representation. We’ll specify the form of this rendered representation below, but it might be something like:

{:type :html :content "<span class='clj-long'>3</span>" :value "3"}

This rendered form will tell the client that the value should be rendered as an HTML fragment, and gives the contents of that fragment. Note that it also includes a readable representation of the value, as would be obtained from pring it, which is used by the client to implement value copying-and-pasting.

This explains how individual values are rendered, but what about aggregates of values? What if the result of our evaluation was [1 2 3] or a list of plots? The first part of the answer is easy: when implementing the render function for aggregates we recursively call render on each of the children. But how do we then combine those rendered values together to form the final value?

Our first attempt at implementing the render function for aggregates might look like this: we call render on the elements of the aggregate, generating HTML fragments as above, and then we assemble these HTML fragments into an HTML fragment representing the list:

{:type :html
 :content "<span class='clj-list'>[</span><span class='clj-long'>1</span> ... <span class='clj-list'>]</span>"
 :value "[1 2 3]"}

This is nice and simple, and works well in this case, but it has a number of problems:

The approach that Gorilla takes to solve these problems is to defer the reassembly of the HTML fragments to the client. To support this, the rendered representation needs some notion of an aggregate, and this is captured by the :list-like render type. In this way, the front-end has some information on how the value is composed out of sub-values, which helps solve the above problems. So in fact the rendered representation for the value [1 2 3] is:

{:type :list-like,
 :open "<span class='clj-vector'>[<span>",
 :close "<span class='clj-vector'>]</span>",
 :separator " ",
 :items
 ({:type :html, :content "<span class='clj-long'>1</span>", :value "1"}
  {:type :html, :content "<span class='clj-long'>2</span>", :value "2"}
  {:type :html, :content "<span class='clj-long'>3</span>", :value "3"}),
 :value "[1 2 3]"}

Rather verbose you might think, but very regular, and quite powerful!

So, in summary, values are rendered in Gorilla by calling the render function of the Renderable protocol on them. This transforms the value into a “rendered representation” that the front end can process to produce the final output. This rendered representation preserves the identity of the various parts of the Clojure value, and supports aggregates directly with :list-like, making it straightforward to render complex values, and enabling value copy-and-paste.

Rendered representation reference

For reference, here is a specification of the rendered representation. A valid value in the rendered representation is one of:

{:type :html :content "some html" :value "pr'ed value"}

This is the simplest, representing a raw HTML fragment that represents the value.

{:type :vega :content <<vega spec>> :value "pr'ed value"}

This represents a Vega visualisation. <<vega spec>> should be a value that, when converted to json, is a valid Vega spec.

{:type :latex :content "some latex" :value "pr'ed value"}

Represents a fragment of LaTeX. Will be displayed inline. You do not need the MathJAX delimiters ($$ or @@).

{:type :list-like,
 :open "opening string",
 :close "closing string",
 :separator "separator string",
 :items <<seq of items>>,
 :value "pr'ed value"}

Represents a general aggregate of values. <<seq of items>> is a sequence of valid rendered representation values. List-likes can be nested, which is how nested lists and maps are rendered.

Extending the renderer

Okay, now you’ve suffered through that you should have a pretty good idea of how the renderer works. Let’s look at how you might extend it to support your own values. At the risk of sounding facile, how you do that depends on what you want achieve. We can divide the sorts of thing you might want to render in to three broad classes:

Examples in these classes might be: a plot, or an image; a matrix; a number. It’s important to decide from the outset where the value you want to render fits into this classification.

In the following we’ll go through some guidelines for how you should approach rendering for each of these classes. You should think of these guidelines as something like a style guide: you don’t have to follow them, but if you do your code will fit more naturally with other things in Gorilla.

Things that are always rendered specially

For the first class of things - things that only make sense when rendered specially - it’s unlikely that the user is particularly interested in the underlying value. So the default should be that the value is rendered specially. And because Gorilla dispatches the render function on the type of the argument, this means that the value will have to have a distinct type. Sometimes this will happen naturally, say if you already had a record type that represented a PNG image. But sometimes you’ll have to implement a wrapper-type solely for the purpose of Gorilla rendering. Either way, the set of things that need to be done is more-or-less the same. Consider as an example the function list-plot from gorilla-plot. It is called with a list of data, and the output is shown as a plot in the Gorilla notebook. Let’s walk through the steps in this process:

The important things to note are: the user-facing functions (list-plot here) directly generate values that have a record type that informs the renderer how to render them; and the renderer is automatically brought into scope when the library is loaded. These two things give the user the experience they expect, that is when they run a plot command, a plot appears.

The list-plot example above uses a wrapper type, Vega, whose sole purpose is to enable the rendering. While returning a wrapped value like this from user-facing code gives a good user experience, there is a price to pay which is that the values have to be unwrapped in order to work with them further. Taking the compose function in gorilla-plot as an example, which tries to combine multiple plots onto one set of axes: it must first unwrap the individual Vega specs by extracting them from the Vega records, then compose the plot data together, and finally re-wrap into a new Vega record so that the result will be rendered as a plot. Weighing this extra work off against the user expectation of automatic rendering is the key design decision to make concerning wrapper types.

Things that are sometimes rendered specially

The second class of things, where both the raw Clojure value and specially rendered values are meaningful, is more interesting. Here we want to give the user choice as to how the value is rendered. The following approach is recommended:

This approach is nice, because it puts the control of the rendering in the hands of the user, allowing them to view the value in the way that is most useful to them. You can provide more than one view function for a given type of value, and these view functions can take options to configure the rendering. You might implement this with multiple wrapper types if need be.

Let’s make the discussion concrete by considering a renderer for a fictional 2D-matrix library. Let’s say this library has functions for manipulating matrices that are stored as simple nested Clojure vectors. As discussed in the first two points of the list above, these library functions know nothing about rendering, and the values will be rendered by the default renderer as simple nested vectors. We will implement two view functions for these matrices matrix-view and abridged-matrix-view (catchy name, eh?). Both will format the matrices as a 2D grid, and the latter will only show a subset of the data, suitable for large matrices. The rendering code might look like:

(ns my-matrix.renderer
  (:require [gorilla-renderable.core :as render]))

;; The wrapper type for the renderer
(defrecord MatrixView [contents])

;; this view function renders the matrix in 2D grid form
(defn matrix-view [m] (MatrixView. m))

(extend-type MatrixView
  Renderable
  (render [self] <<rendering code here>>))

;; A second wrapper type for indicating an abridged form should be rendered
;; the opts will be used to store render specific options, like how many values to show say
(defrecord AbridgedMatrixView [contents opts])

;; this view function renders the matrix in 2D grid form, it takes options to control the rendering
(defn abridged-matrix-view [m & opts] (AbridgedMatrixView. m opts))

(extend-type AbridgedMatrixView
  Renderable
  (render [self] <<rendering code here>>))

Hopefully this toy example gives you an idea of how your rendering code might be structured for values that can rendered in multiple ways.

Things that are never rendered specially

This final class of things is easy - Gorilla’s built-in renderer will take care of this!

Conclusion

We’ve covered a lot in this document, but hopefully it gives you a solid grounding in the way the renderer works, and has some useful suggestions for implementing your own rendering functions. You’re encouraged to implement renderers for your data and share them - if you need any help with this, please don’t hesitate to get in touch.