Using React Vis from ClojureScript

We are going to create a chart using a JavaScript React data visualisation library called React Vis, translating the original JavaScript source code from Advanced Visualisation with React Vis into ClojureScript. Along the way we will learn about Fulcro and ClojureScript ‘interop’. It would be good if you could read the original article and if you are new to Fulcro here’s some suggested reading: Fulcro Getting Started Guide.

The source code is on GitHub. Assuming you already have npm installed, run this command from the project directory to setup and run the application:

make shadow

You will be prompted to run various other commands. Just CTRL-C out and make shadow in again and follow along until shadow-cljs is satisfied. Then point your browser to http://localhost:3449.

In the original article there are three React components. In the translated article they have been faithfully preserved and are named NaiveChart for the first naive attempt, and StaticChart and InteractiveComponents for the second attempt, which is then later speed-ed up by making the chart’s lines simpler. In Fulcro components are defined with the defsc macro. Notice that there are a few extras: Game, PlayerYear and DesiredLabels, which have no render function, but exist to support the React Vis components, supplying all their data needs through incoming props.

To understand the relationships between Fulcro components/entities you can use a tool that allows you to view the client state of the running application. This tool is already included in the project, you just have to install the Chrome Extension mentioned here: Fulcro Inspect.

For more of a ‘code perspective’ search the source code for mprim/get-one. That will give you a high level ‘design time’ view of the whole application. ‘To one’ relationships tend to have their data provided statically — you can see this by searching for prim/get-initial-state and noticing the exact correspondence: all the ‘to one’ relationships are filled in as part of loading initial state into all the defsc components. In this application ‘static’ means before game data is loaded. As the game data is loaded the mprim/get-many relationships will become filled with player game data.

I should say that mprim is my own namespace and get-one and get-many are ‘for documentation purposes only’. I find it helpful to be explicit about the cardinality of a field join (in database terminology a field join would be a foreign key column). The alternative would be to decipher the field join’s cardinality from its name. Fulcro inherits from Om Next, where drawing props from another component is simply prim/get-query. So here is the truly tiny mprim namespace:

(def get-one prim/get-query)

(def get-many prim/get-query)

You could use these get-one/get-many searches and Fulcro Inspect to draw yourself a diagram of the application’s entities and relationships. Looking at it you would know the denormalised structure that is the application’s UI. It is denormalised because components such as PlayerYear turn up many times: a normalised diagram would not have such repeats. It is Fulcro’s job to turn the normalised database (the application’s state) into a denormalised tree of props for the UI of React components.

Of course changes that you make, known as mutations, are made to the normalised state. We only have one mutation in this application, called fill-desired-labels:

This mutation is run (use prim/transact! to run mutations) right after the initial load of player years with associated game data. In the client database :player-year/by-id is a table and as such is indexed. Thus doing vals on it gives us all the table records. These player-year records were installed during initial load when :ui/desired? was marked using a def from the constants namespace. We collect up the ids and use them to make a vector of idents. This vector of idents is then put in the :desired-labels/by-id table under the :items field join. Desired labels are player-years that are shown on the chart without any need for mouse movement. As you may have guessed :singleton is used as a convention to indicate that there is only one record in the :desired-labels/by-id table.

So now you might be wondering how the load into client state occurred in the first place. The answer is that as long as your input data has keys that match with the keys of the defsc’s queries, then the import will be automatic. Here is the code statement that does the job:

(prim/merge-component! reconciler comp d)

Here reconciler is the entry point into Fulcro, comp will be the actual defsc component PlayerYears, and d is the data. The format of the data will always be a map where any ‘to many’ field join values are vectors of maps. Here there is only one ‘to many’ field join: :games. (:games was known as gameData in the original code).

Apart from the mutation, the only other place where any real processing goes on (i.e. where the code is not declarative) is the creation of :items in the d map:

Here you can see for example that if the configuration option stage is set to :improved (the other stages are :naive and :nyt) that a player’s season of games (i.e. his line on the chart) will be simplified.

So much for Fulcro. What about ‘interop’? You will be guided to npm install the JavaScript libraries that are required. Importing is then a bit tricky. JavaScript:

import {scaleLinear} from ‘d3-scale’;
import simplify from ‘simplify-js’;

Equivalent ClojureScript:

[“d3-scale” :refer (scaleLinear)]
[“simplify-js” :default simplify]

Use this table for translations: Using NPM Packages.

The other main thing that needs to be done is convert data structures between the two worlds using clj->js, js->clj and #js. Plenty of examples in the code.

Fulcro is put to best use writing large applications. However even in this tiny example application, where we are using native React components for rendering, and thus cannot render from Fulcro components, we can see that Fulcro’s model is capable of cleaning away arbitrary data structures from React components, replacing them with simple props that are queried from other components. In the end most of the code becomes declarative.