Reveal: Extend for your project

There are 3 ways to extend Reveal to your needs: custom formatters, actions, and views.

One feature that they all share is annotations — non-intrusive metadata that exists alongside your objects in the Reveal state. Unlike datafy/nav based tooling, it does not obstruct your objects, leaving Clojure’s metadata exactly as it is in your program, and, since the annotation is alongside the object, Reveal allows any object to be annotated — not just IMetas.

Formatters

Formatters define how values are stringified, formatted and syntax-highlighted in the output panel. Formatter dispatch is a multimethod that looks at :vlaaad.reveal.stream/type meta key or, if it’s absent, at object’s class. The recommended way to extend this multi-method is using (r/defstream dispatch-val [x ann?] sf) macro that automatically marks the formatted region with the value that is being formatted. There is a small set of functions that perform the streaming of the formatting in an efficient way called streaming functions (sfs for short).

Low-level text emitting sfs

These are usually used in the r/defstream body to configure how something looks as text. There are only 5 of them:

Delegating sfs

These sfs allow you to stream other values using their default streaming. This is also a place to annotate the streamed values.

Annotations are only useful if they are used, and they are used from actions. There is an example that configures formatting with annotations and uses these annotations for data inspections here.

Overriding sfs

These sfs allow modifying some aspect of a streaming:

Actions

If selected text in Reveal UI has associated value, requesting a context menu on it will show a popup that checks all registered actions and suggests ones that apply. Use (r/defaction id [x ann?] body*) macro to register new actions.

Action body should return a 0-arg function to indicate that this action is available: this function will be executed if the action is selected in the context menu popup. Any other results, including thrown exceptions, are ignored. The action body should be reasonobly fast (e.g. not performing disk IO) since all actions are always checked when the user asks for a popup. Returned function, on the other hand, may block for as long as needed: Reveal will show a loading indicator while it’s executed.

Minimal action example that shows how strings look unescaped (e.g. display "hello\nworld" as hello and world on separate lines):

(r/defaction ::unescape [x]
  (when (string? x)
    #(r/as x (r/raw-string x {:fill :string}))))

As mentioned earlier, there is a bigger example that shows how actions and formatting can build on each other to aid with data exploration.

You can execute registered actions programmatically by calling (r/execute-action id x ann?) — it will return future with execution result (that will be completed exceptionally if action is unavailable for supplied value). All built-in action keyword ids use vlaaad.reveal.action namespace, e.g. :vlaaad.reveal.action/java-bean.

Views

A major difference between Output panel and Results panel is that the latter can show any graphical node allowed by Reveal’s UI framework (JavaFX). Reveal is built on cljfx — declarative, functional and extensible wrapper of JavaFX inspired by react. Reveal converts all action results to cljfx component descriptions, and if returned action result is cljfx description, it is rendered as UI element.

Short cljfx intro

To describe a node, cljfx uses maps with a special key — :fx/type — that defines a type of node, while other keys define properties of that node. Value on :fx/type key can be a keyword (kebab-cased JavaFX class name) or a function (that receives a map of props and returns another description).

Some examples of most commonly used descriptions:

;; Showing a text
{:fx/type :label
 :text (str (range 10))}

;; Showing a button with a callback:
{:fx/type :button
 :text "Deploy"
 :on-action (fn [event] (deploy-to-production!))}

;; Combining views together
{:fx/type :v-box ;; vertically
 :children [{:fx/type r/value-view ;; built-in component
             :value msft-stock}
            {:fx/type :h-box ;; horizontally
             :children [{:fx/type :button
                         :text "Sell"
                         :on-action (fn [_] (sell! :msft))}
                        {:fx/type :button
                         :text "Buy"
                         :on-action (fn [_] (buy! :msft))}]}]}

While cljfx supports using maps to define callbacks, you should only use functions — behavior of map event handling is an implementation detail that is subject to change.

To thoroughly learn cljfx/JavaFX, you should go through cljfx readme and examples to get familiar with semantics and explore JavaFX javadoc to find available views.

Built-in views

All built-in views that you can use and compose are described in the views section of using reveal at the REPL documentation.

Pluggable context menu

Fancy visualizations don’t have to be leaf nodes that you can only look at — wouldn’t it be nice to select a data point on a plot and explore it as a value? Reveal supports this continued data exploration for built-in views like charts and tables out of the box. In addition to that it provides a way to install the action popup on any JavaFX node with a special component — r/popup-view:

{:fx/type r/popup-view
 :value (the-ns 'clojure.core)
 :desc {:fx/type :label
        :text "The Clojure language library"}}

This description shows label that you can request a context menu on, and its popup will suggest acions on clojure.core ns. There is a bigger example showing how to create a custom view for a chess server that displays active games as chess boards and allows inspecting any piece.