Chapter 7 The Full Monty

With a first widget built, one can jump onto another one: gio.js, a library to draw arcs between countries on a three-dimensional globe. This will include many more functionalities such packages can comprise.

Then again, the first order of business when looking to integrate a library is to look at the documentation to understand what should be reproduced in R. Figure 7.1 is a very basic example of using Gio.js in HTML.

Gio.js example

FIGURE 7.1: Gio.js example

Gio.js has itself a dependency, three.js, which needs to be imported before gio.js, other than that not much differs from libraries previously explored in this chapter.

7.1 Dependencies

Handling the dependencies does not differ much, we create the directories within the inst path and download the dependencies within them.

This should produce the following working directory.

.
├── DESCRIPTION
├── NAMESPACE
├── R
│   └── gio.R
└── inst
    └── htmlwidgets
        ├── gio
        │   └── gio.min.js
        ├── gio.js
        ├── gio.yaml
        └── three
            └── three.min.js

The libraries have been downloaded, but the gio.yml file is yet to be edited. The order in which the libraries are listed matters. Just as in HTML, three.js needs to precede gio.js as the latter depends on the former and not vice versa.

7.2 JavaScript

Let’s copy the JavaScript code from the Get Started section of gio.js in the gio.js file’s renderValue function. At this point, the data format is not known, so we comment the line, which adds data to the visualisation.

One can document and load the package, but it will not work as the code above attempts to place the visualisation in a div with id = "globe". As for the previously-written widget, this needs to be changed to el.id.

At this stage, the widget should generate a visualisation (see Figure 7.2).

Gio output without data

FIGURE 7.2: Gio output without data

Not too shabby given how little work was put into this! Before we move on, let us optimise something. In the JavaScript code, we retrieve the container using el.id, but this in effect is very inefficient: el is identical to container.

7.3 Working with Data

An exciting start, now onto adding data. Let us take a look at the documentation to see what data the library expects.

The JSON data should constitute arrays that denote arcs to draw on the globe where each arc is defined by an exporting country (e), an importing country (i), and is given a value (v). The importing and exporting country, the source and target of the arc, are indicated by ISO alpha-2 country codes. We can read this JSON into R.

Jsonlite automagically converts the JSON into a data frame where each row is an arc, which is excellent as R users tend to prefer rectangular data over lists: this is probably what the package should use as input too. Let us make some changes to the gio function, so it takes data as input.

This must be reflected in the play.js file, where we uncomment the line previously commented and use x.data passed from R.

We can now use the function with the data to plot arcs!

Unfortunately, this breaks everything, and we are presented with a blank screen. Using console.log or looking at the source code the rendered widget reveals the problem: the data is not actually in the correct format!

Htmlwidgets serialised the data frame column-wise (long), where each array is a column, whereas gio.js expect the data to be wide (row-wise serialisation), where each array is an arc (row).

As mentioned previously, the convention has it that rectangular data (data frames) are serialised row-wise. That is likely to be a recurring problem for many widgets.

7.4 Transforming Data

There are multiple ways to transform the data and ensure the serialised JSON is as the JavaScript library expects it to be. The following sections explore those various methods before settling on a specific one for the gio package.

7.4.1 Using JavaScript

JavaScript can be used to transform the data, thereby leaving the serialiser as-is only to reshape it into the client. The htmlwidget JavaScript library (already imported by default) exports an object, which provides a method, dataframeToD3, to transform the data from long to wide.

7.4.2 Modify Serialiser

Instead of serialising the data a certain way then correct it in JavaScript as demonstrated previously, one can also modify, or even replace the htmlwidgets default serialiser. Speaking of which, below is the default serializer used by htmlwidgets.

The problem at hand is caused by the data.frame argument, which is set to columns where it should be set rows (for row-wise). Arguments are passed to the serialiser indirectly, in the form of a list set as TOJSON_ARGS attribute to the object x that is serialised. We could thus change the gio function to reflect the aforementioned change.

The above may appear confusing at first as one rarely encounters the attr replacement function.

Other arguments can be placed in the same list; they will ultimately reach the serialiser to modify its output.

7.4.3 Replace Serialiser

Otherwise, the serialiser can also be replaced in its entirety, also by setting an attribute, TOJSON_FUNC, to the x object. Below the serialiser is changed to jsonify (Cooley 2020), which by default serialises data frames to wide, unlike htmlwidgets’ serialiser, thereby also fixing the issue.

7.4.4 Modify the Data

Alternatively, one can also leave all serialisers-related options unchanged and instead format the data in R prior to the serialisation, changing the dataframe to a row-wise list.

The above would make it such that the serialiser no longer has to interpret how the data should be serialised (row-wise or column-wise), the data now being a list will be serialised correctly.

7.4.5 Pros and Cons

There are pros and cons to each method. The preferable method is probably to alter the default serialiser only where needed; this is the method used in the remainder of the book. Replacing the serialiser in its entirety should not be necessary, only do this once you are very familiar with serialisation and truly see a need for it. Moreover, htmlwidgets’ serialiser extends jsonlite to allow converting JavaScript code, which will come in handy later on. Transforming the data in JavaScript has one drawback, HTMLWidgets.dataframeToD3 cannot be applied to the entire x object, it will only work on the subsets that hold the column-wise data (x.data), which tends to lead to clunky code as one uses said function in various places.

Gio output with correct serialisation

FIGURE 7.3: Gio output with correct serialisation

Figure 7.3 shows that the arcs correctly appear on the globe once the default serialiser has been modified.

7.5 On Print Method

Let us add the option to style the globe: gio.js provides multiple themes but they are currently not applicable from R. As a matter of fact, gio.js provides dozens of customisation options that should be available in the package as well. These, however, probably should be split across different functions, just like they are in gio.js, rather than all be accessible from a single function containing hundreds of arguments. This begs the question, when would one use another function given the function gio generates the visualisation? As it happens gio itself (or rather the function htmlwidgets::createWidget it contains) does not render the output; it returns an object of class “htmlwidget,” which renders the visualisation on print (literally htmlwidget.print method).

Therefore, one can use functions on the object returned by gio, which contains, amongst other things, the x list to which we can make changes, append data, or do any other operation that standard R lists allow.

This clarified, the function to change the style of the visualisation can be added to the package. It would take as input the output of gio and append the style (name of theme) to the x list; this would then be used in JavaScript to change the look of the visualisation.

The style is applied using the setStyle method on the controller before the visualisation is created, before the init method is called, using the style passed from R: x.style.

We can now run devtools::load_all to export the newly written function and load the functions in the environment with devtools::load_all as shown in Figure 7.4.

Gio with a new theme

FIGURE 7.4: Gio with a new theme

This is great but can be greatly improved upon with the magrittr pipe (Bache and Wickham 2020), it would allow effortlessly passing the output of each function to the next to obtain an API akin to that of plotly or highcharter.

The pipe drastically improves the API that gio provides its users and thus probably should be exported by the package; the usethis package provides a function to do so easily.

This closes this chapter but is not the last we see of gio.js; we shall use it as an example in the next chapters as we extend its functionalities and polish certain aspects such as sizing and security.

References

Bache, Stefan Milton, and Hadley Wickham. 2020. Magrittr: A Forward-Pipe Operator for R. https://CRAN.R-project.org/package=magrittr.

Cooley, David. 2020. Jsonify: Convert Between ’R’ Objects and Javascript Object Notation (Json). https://CRAN.R-project.org/package=jsonify.