(Sorry, I know this is a long message. I'm happy to discuss/demo over vchat or in person at the rOpenSci event in late March.)
Motivation
d3 is awesome, we all know that.
I've always felt d3 presented a programming model that was subtle and tricky enough, that it might be irresponsible to ask casual JavaScript programmers to make sense of it. It's fairly easy to start from a Bostock example and get an 80% correct solution (doesn't work correctly in Shiny, doesn't resize properly) but the other 20% will require a deep understanding of d3. And there's not enough commonality between different pure d3 widgets for htmlwidgets (or Shiny, for that matter) to provide much leverage to widget developers.
However, d3 + React together feels to me like it just might be simple enough to enable casual JS developers to succeed in making 100% correct widgets. I'm not saying it'll be easy for them, but it doesn't feel unreasonable to me anymore.
And even for expert widget authors, d3 + React just requires less thought and eliminates entire classes of bugs. It's faster to write, simpler to read, easier to reason about.
How React can be used with d3
You can think of d3 as 1) a set of "abstract" implementations of projections, layouts, scales, parsers, etc., and by abstract I mean not tied to any particular visual representation; 2) a data-to-DOM-node data binding mechanism (selectAll/data/enter/exit); and 3) a set of functions for mutating DOM nodes (attr, append).
My assertion is that React is a perfect replacement for 2 and 3, and offers significant advantages over using d3 alone. The enter/exit mechanism is beautiful and elegant, but quite difficult to grasp (it took me maybe 3 tries over a period of months to really grok it) and in the context of htmlwidgets, very easy to get wrong. If you put too much functionality in the enter() side of things, your widget will look great in static contexts like RStudio viewer or Rmd, but will be broken in Shiny apps. Unfortunately, many if not most of the d3 examples in the wild are written in this style.
d3's DOM mutation API is straightforward but it doesn't lead to particularly readable code. It's not easy to look at d3 code and intuit the DOM tree that's being built up.
React solves both of these problems by working in a far more, well, reactive way. Your renderValue callback just needs to create a React virtual DOM and render it. For the most part you don't need to worry about what state is already in the DOM and how to get it to the state that you want; just create the DOM tree you want, and let React figure out how to render it.
In other words, both d3 and d3+React are straightforward for rendering a widget in "State A". What happens now when you want to transition the widget to "State B"? In d3, you code up the instructions for 1) creating new nodes, 2) removing no-longer-needed nodes, 3) changing existing nodes. In d3 + React, you essentially just write the desired HTML you want to see at the end, and let React take care of turning that into the same set of instructions d3 would execute.
Example
Compare these two rendering codepaths:
The React version was much faster to write, much easier to read, and definitely has "good enough" performance (I didn't measure the pure d3 implementation but IIRC the React version can render 10,000 bubbles in a second).
It's not a perfectly fair comparison right now because the React example doesn't do smoothly animated transitions, I haven't learned how to do those in React just yet but I'm assuming/hoping there's a nice React mechanism and it will work sensibly in htmlwidgets.
Next steps
I'm creating this issue to foster discussion around this approach (would especially like feedback from @timelyportfolio). I would love others who are trying to build d3-based htmlwidgets to try swapping in React for the actual rendering. See https://github.com/jcheng5/bubbles/tree/react for an example. I would especially love to know whether transitions can be achieved easily.
If it turns out that this is as useful a combination as I hope it is, then I would like to discuss how we could make it easier for potential widget authors to work with it. At least a tutorial somewhere (either at htmlwidgets.org or Kenton's blog), all the way up to having a facade over HTMLWidgets.widget() that is specific for d3 + React.
Build notes
The biggest downside of the way I wrote the React version of the bubbles package is that it uses JSX, which lets you put "HTML" right inline in your JavaScript; it's cool but requires a build-time compile step. So you need to install the JSX compiler (install Node.js, then run sudo npm install -g react-tools
). If you're on Mac or Linux you can use the build.sh script in the root of the package; either run it without arguments, or pass it -w
to have it continuously monitor the JSX file and build the resulting JS. If you choose to use JSX in your package, please check in the generated JavaScript so people can install your package using devtools::install_github
and won't need the JSX compiler to be installed.
If you want you can skip JSX and just write straight JavaScript, here's what that looks like. (It's possible for it to be a bit more terse using React.DOM, but it's not a gigantic difference.) Definitely doesn't look as nice but the semantics are identical.