Thehtmlwidgetspackage provides a framework for creating R bindings to JavaScriptlibraries. HTML Widgets can be:
By following a small set of easy-to-follow conventions, it ispossible to create HTML widgets with very little code. All widgetsinclude the following components:
Dependencies. These are the JavaScript and CSSassets used by the widget (e.g. the library you are creating a wrapperfor).
R binding. This is the function that end userswill call to provide input data to the widget as well as specify variousoptions for how the widget should render. This also includes some shortboilerplate functions required to use the widget within Shinyapplications.
JavaScript binding. This is the JavaScript codethat glues everything together, passing the data and options gathered inthe R binding to the underlying JavaScript library.
HTML widgets are always hosted within an R package and should includeall of the source code for their dependencies. This is to ensure thatcode which depends on widgets is fully reproducible (i.e. doesn’trequire an internet connection or the ongoing availability of aninternet service to run).
To start with we’ll walk through the creation of a simple widget thatwraps thesigma.js graphvisualization library. When we’re done we’ll be able to use it todisplay interactive visualizations ofGEXF (Graph Exchange XML Format) data files.For example:
Note that the above is just an image of the visualization so it’s notinteractive. You can play with the interactive version by following thesteps in the demo section below.
There is remarkably little code required to create this binding.Below we’ll go through all of the components step-by-step. Then we’lldescribe how you can create your own widgets (including automaticallygenerating basic scaffolding for all of the core components).
Let’s assume that our widget is namedsigma and islocated within an R package of the same name. Our JavaScript bindingsource code file is named sigma.js. Since our widget will read GEXF datafiles we’ll also need to include both the base sigma.min.js library aswell as its GEXF plugin. Here are the files that we’ll add to thepackage:
R/| sigma.Rinst/|-- htmlwidgets/| |-- sigma.js| |-- sigma.yaml| |-- lib/| | |-- sigma-1.0.3/| | | |-- sigma.min.js| | | |-- plugins/| | | | |-- sigma.parsers.gexf.min.jsNote the convention that the JavaScript, YAML, and other dependenciesare all contained within theinst/htmlwidgets directory(which will subsequently be installed into a package sub-directory namedhtmlwidgets).
Dependencies are the JavaScript and CSS assets used by a widget.Dependencies are included within theinst/htmlwidgets/libdirectory. Dependencies are specified using a YAML configuration filewhich uses the name of the widget as its base file name. Here’s what oursigma.yaml file looks like:
dependencies:-name: sigmaversion:1.0.3src: htmlwidgets/lib/sigma-1.0.3script:- sigma.min.js- plugins/sigma.parsers.gexf.min.jsThe dependencysrc specification refers to the directorythat contains the library andscript refers to specificJavaScript files. If your library contains multiple JavaScript filesspecify each one on a line beginning with- as shown here.You can also addstylesheet entries and evenmeta orhead entries. Multiple dependenciesmay be specified in one YAML file. See the documentation on thehtmlDependency function in thehtmltoolspackage for additional details.
We need to provide users with an R function that invokes our widget.Typically this function will accept input data as well as variousoptions that control the widget’s display. Here’s the R function forsigma:
#' @import htmlwidgets#' @exportsigma<-function(gexf,drawEdges =TRUE,drawNodes =TRUE,width =NULL,height =NULL) {# read the gexf file data<-paste(readLines(gexf),collapse="\n")# create a list that contains the settings settings<-list(drawEdges = drawEdges,drawNodes = drawNodes )# pass the data and settings using 'x' x<-list(data = data,settings = settings )# create the widget htmlwidgets::createWidget("sigma", x,width = width,height = height)}The function takes two classes of input: the GEXF data file to renderand some additional settings which control how it is rendered. Thisinput is collected into a list namedx which is then passedon to thehtmlwidgets::createWidget function. Thisx variable will subsequently be made available to theJavaScript binding for sigma (this is described below). Any width orheight parameter specified is also forwarded to the widget (widgets sizethemselves automatically by default so typically don’t require anexplicit width or height).
We want our sigma widget to also work in Shiny applications, so weadd the following boilerplate Shiny output and render functions (theseare always the same for all widgets):
#' @exportsigmaOutput<-function(outputId,width ="100%",height ="400px") { htmlwidgets::shinyWidgetOutput(outputId,"sigma", width, height,package ="sigma")}#' @exportrenderSigma<-function(expr,env =parent.frame(),quoted =FALSE) {if (!quoted) { expr<-substitute(expr) }# force quoted htmlwidgets::shinyRenderWidget(expr, sigmaOutput, env,quoted =TRUE)}Note: An older, less intuitive JavaScriptbinding API was used in htmlwidgets 0.5.2 and earlier, and continues tobe supported in newer versions of htmlwidgets. See thisarchivedversion for details on the legacy binding API. New widgets areencouraged to use the newer API described below.
The third piece in the puzzle is the JavaScript required to activatethe widget. By convention we’ll define our JavaScript binding in thefileinst/htmlwidgets/sigma.js. Here is the full sourcecode of the binding:
HTMLWidgets.widget({name:"sigma",type:"output",factory:function(el, width, height) {// create our sigma object and bind it to the elementvar sig=newsigma(el.id);return {renderValue:function(x) {// parse gexf datavar parser=newDOMParser();var data= parser.parseFromString(x.data,"application/xml");// apply settingsfor (var namein x.settings) sig.settings(name, x.settings[name]);// update the sigma object sigma.parsers.gexf( data,// parsed gexf data sig,// sigma objectfunction() {// need to call refresh to reflect new settings and data sig.refresh(); } ); },resize:function(width, height) {// forward resize on to sigma renderersfor (var namein sig.renderers) sig.renderers[name].resize(width, height); },// Make the sigma object available as a property on the widget// instance we're returning from factory(). This is generally a// good idea for extensibility--it helps users of this widget// interact directly with sigma, if needed.s: sig }; }});We provide a name and type for the widget, plus afactory function that takesel (the HTMLelement that will host this widget),width, andheight (width and height of the HTML element, in pixels–youcan always useoffsetWidth andoffsetHeightfor this).
Thefactory function should prepare the HTML element tostart receiving values. In this case we create a new sigma element andpass it theid of the DOM element that hosts the widget onthe page.
We’re going to need access to the sigma object later (to update itsdata and settings) so we save it as a variablesig. Notethat variables declared directly inside of the factory function are tiedto a particular widget instance/el.
The return value of thefactory function is called awidget instance object. It is a bridge between the htmlwidgetsruntime, and the JavaScript visualization that you’re wrapping. As thename implies, each widget instance object is responsible for managing asingle widget instance on a page.
The widget instance object you create must have one required method,and may have one optional method:
The requiredrenderValue method actually pours ourdynamic data and settings into the widget’s DOM element. Thex parameter contains the widget data and settings. We parseand update the GEXF data, apply the settings to our previously-createdsig sigma object, and finally callrefresh toreflect the new values on-screen. This method may be called repeatedlywith different data (i.e. in Shiny), so be sure to account for thatpossibility. If it makes sense for your widget, consider making yourvisualization transition smoothly from one value ofx toanother.
The optionalresize method is called whenever theelement containing the widget is resized. The only reason not toimplement this method is if your widget naturally scales (withoutadditional JavaScript code needing to be invoked) when its element sizechanges. In the case of sigma.js, we forward the sizing information onto each of the underlying sigma renderers.
All JavaScript libraries handle initialization, binding to DOMelements, dynamically updating data, and resizing slightly differently.Most of the work on the JavaScript side of creating widgets is mappingthese three functions—factory,renderValue,andresize—correctly onto the behavior of the underlyinglibrary.
The sigma.js example uses a simple object literal to create itswidget instance object, but you can also useclassbased objects or any other style of object, as long asobj.renderValue(x) andobj.resize(width, height) can be invoked on it.
You can add additional methods and properties on the widget instanceobject. Although they won’t be called by htmlwidgets itself, they mightbe useful to users of your widget that know some JavaScript and want tofurther customize your widget by adding custom JS code (e.g. using thehtmlwidgets::onRender R function). In this case we add ans property to make the sigma object itself available.
Our widget is now complete! If you want to test drive it withoutreproducing all of the code locally you can install it from GitHub asfollows:
Here’s the code to try it out with some sample data included with thepackage:
If you execute this code in the R console you’ll see the widgetdisplayed in the RStudio Viewer (or in an external browser if you aren’trunning RStudio). If you include it within an R Markdown document thewidget will be embedded into the document.
We can also use the widget in a Shiny application:
library(shiny)library(sigma)gexf<-system.file("examples/ediaspora.gexf.xml",package ="sigma")ui=shinyUI(fluidPage(checkboxInput("drawEdges","Draw Edges",value =TRUE),checkboxInput("drawNodes","Draw Nodes",value =TRUE),sigmaOutput('sigma')))server=function(input, output) { output$sigma<-renderSigma(sigma(gexf,drawEdges = input$drawEdges,drawNodes = input$drawNodes) )}shinyApp(ui = ui,server = server)To implement a widget you need to create a new R package that in turndepends on thehtmlwidgets package. You can install thepackage from CRAN as follows:
While it’s not strictly required, the step-by-step instructions belowfor getting started also make use of thedevtoolspackage which you can also install from CRAN:
To create a new widget you can call thescaffoldWidgetfunction to generate the basic structure for your widget. This functionwill:
Create the .R, .js, and .yaml files required for yourwidget;
If provided, take aBower packagename and automatically download the JavaScript library (and itsdependencies) and add the required entries to the .yaml file.
This method is highly recommended as it ensures that you get startedwith the right file structure. Here’s an example that assumes you wantto create a widget named ‘mywidget’ in a new package of the samename:
devtools::create("mywidget")# create package using devtoolshtmlwidgets::scaffoldWidget("mywidget")# create widget scaffoldingdevtools::document()# roxygenize, so NAMESPACE is updateddevtools::install()# install the package so we can try itThis creates a simple widget that takes a singletextargument and displays that text within the widgets HTML element. You cantry it like this:
This is the most minimal widget possible and doesn’t yet include aJavaScript library to interface to (note thatscaffoldWidget can optionally include JavaScript librarydependencies via thebowerPkg argument). Before gettingstarted with development you should review the introductory exampleabove to make sure you understand the various components and also reviewthe additional articles and examples linked to in the next section.
There are additional articles that cover more advanced ground:
HTML Widget Sizing explainscustom sizing policies and when you might need to use them and describesimplementing aresize method within JavaScriptbindings.
HTML Widgets: Advanced Topicsdescribes framework features that support per-widget instance data, datatransformations (e.g. converting a data frame into a d3 dataset), andproviding widget options that are live JavaScript objects (e.g. functiondefinitions).
The Sizing article is particularly important as most JavaScriptlibraries require some additional interaction to keep their sizesynchronized with their containing element.
Studying the code of other packages is a great way to learn moreabout creating widgets:
ThenetworkD3package illustrates creating a widget on top ofD3, using a custom sizing policy for alarger widget, and providing multiple widgets from a singlepackage.
Thedygraphspackage illustrates using widget instance data, handling dynamicre-sizing, and usingmagrittr to decompose alarge and flat JavaScript API into a more modular and pipeable RAPI.
Thesparkline packageillustrates providing a custom HTML generation function (sincesparklines must be housed in<span> rather than<div> elements).
If you have questions about developing widgets or run into problemsduring development please don’t hesitate topost an issueon the project’s GitHub repository.