ramnathv / htmlwidgets

HTML Widgets for R
http://htmlwidgets.org
Other
790 stars 207 forks source link

d3 + React.js #82

Open jcheng5 opened 9 years ago

jcheng5 commented 9 years ago

(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.

ramnathv commented 9 years ago

@jcheng5 This is very interesting. I have been experimenting with D3 combined with several frameworks like Ember, Angular and React. Providing a setup inside of htmlwidgets is a natural next step and can not only accelerate widget development, but also allow embedding of reactive controls that change state (like Shiny, except that the reactive controls are now on the client side).

If it would be useful, we should try and setup a Google Hangout for a demo of the approach followed by a discussion of what we want to achieve. These are exciting steps :+1:

timelyportfolio commented 9 years ago

Thanks @jcheng5 for the very thorough discussion. I certainly sympathize with many of your thoughts and struggle with how much Javascript an htmlwidget author should need to add/know to make a good widget. As you say, many of the d3 examples, while nice, are not complete enough to satisfy my htmlwidget checklist.

I would love to work through a couple examples starting with your bubbles and try to iterate through some with react and also as @ramnathv says, the other contenders Ember, Angular, Backbone and even some of the more limited but specific bindings. For instance, htmlbars from Ember could potentially be applied without Ember.

I also wonder how much of the DOM elements could be produced in R. However, this makes it generally even more difficult to intuit the final product.

I will say that the non-JSX does not seem a whole lot better. The first example though is certainly much clearer, and React is gathering quite some momentum. Maybe, some of the JS library authors will start integrating for us :)

I'll try to work through a couple examples myself and post them up to add to the discussion.

More than anything best practices and thorough discussion will immensely add to quality of future htmlwidgets.

jcheng5 commented 9 years ago

I think using Ember, Angular, or Backbone make sense with d3 in a JS-only world, much less so in an R+JS world. So much of what we want to do needs to be composable, and what I've seen of these frameworks it will be hard to achieve that (though I admit Angular is the only one I've spent a lot of time with). On the other hand, React's JSX/React.DOM and virtual DOM technology slots in where we need it like a hand in a glove.

I say that not to discourage you from experimenting with all of these technologies--by all means, do--but just don't be surprised if what makes tons of sense when writing a pure webapp, becomes awkward to fit into, say, an Rmd or Shiny context.

BTW the key thing about React's virtual DOM stuff (versus a more naive approach like htmlbars) it that you re-generate the virtual DOM but it uses a minimal set of mutations to change the actual DOM to what you want. I can't stress enough how important that is, and how it's different than clearing the previous DOM nodes out and replacing them with a new set. Again, perhaps best discussed/demonstrated in realtime.

jcheng5 commented 9 years ago

@ramnathv This might be too much of a tangent, if we go more than a couple of rounds with this line of thinking let's start a separate issue--but talking about putting reactive controls entirely in the client-side, it seems like this would imply that whatever calculation/logic turns input into output would then have to be expressed in JavaScript. Are you envisioning then that the widget user (not author) would need to write JavaScript logic to turn inputs into outputs?

I've heard this idea of using R to create client-only reactive apps many times, but it's never clear to me what the actual goal here looks like--unless either the set of transformations you can do on the data is predetermined (filtering, cross-filtering, searching, etc.) or the user is writing JS code.

timelyportfolio commented 9 years ago

@jcheng5 I agree with you that React probably best handles this particular situation. What are the common use cases besides streaming data where enter/exit type behavior would be necessary instead of destroy/rebuild?

I'm still trying to decide

do we really expect a typical R user to be able to not only write a wrapper but also implement functionality into the Javascript and/or build a Javascript library from scratch?

I think no, and I think this is important to help direct our efforts in education and setting best practices, since our audience in this case is very Javascript literate.

Shiny Inputs

Every time I demo a widget, it seems the first question is how to get the info back to R, so I definitely think it would be very worthwhile to work hard to demonstrate how to do this with multiple examples.

Javascript Reactivity

I think this is a limited but very important piece assuming that an htmlwidget author is Javascript literate. Often for me, allowing a completely standalone web page that requires no persistent R connection, no R knowledge, and no Javascript knowledge is critical. In these cases, I think building a widget with known transforms are important. The other use case for me here is inter-htmlwidget communication especially with shared data, such as crossfilter + dc.js.

Love the discussion and really enjoy the collaboration!

ramnathv commented 9 years ago

@jcheng5 I was just suggesting the reactive controls aspect as something to explore, not necessarily a part of this thread. The basic idea is to push simpler filtering/query operations to the client side for lower latency, and leave the heavier operations to R on the server.

A concrete example would be a plot where you want the user to be able to select the x and y variables interactively. I do this routinely for the widgets I write and I was wondering if React or one of the other frameworks would make this easier to accomplish in a more systematic way.

I agree with @timelyportfolio that a more important issue to address would be inter-widget communication and getting data back to R from plots (like mouseover, click etc). I was at a hackathon at Imperial College where I helped people build a few widgets and this was one of the questions that routinely came up.

timelyportfolio commented 9 years ago

good reference http://nicolashery.com/integrating-d3js-visualizations-in-a-react-app/ but chooses to stick with d3 enter \ exit (see lines)

jcheng5 commented 9 years ago

@timelyportfolio Yeah, I saw that link too and was disappointed in the approach it took.

timelyportfolio commented 9 years ago

Ryan Florence MagicMove & Animated Spending are potentially relevant but use CSS transforms/animation which don't always work well with SVG elements.

timelyportfolio commented 9 years ago

Sure you have seen this also, but it is probably more relevant http://viget.com/extend/visualization-is-for-sharing-using-react-for-portable-data-visualization.

timelyportfolio commented 9 years ago

This is a really good discussion of D3 + React http://youtu.be/2ii1lEkIv1s?t=18m31s

jcheng5 commented 9 years ago

@timelyportfolio I love that talk.

jcheng5 commented 9 years ago

So I think transitions/animation are going to be a little tricky with this approach. Not insurmountable by any means, but React's official Animate add-in doesn't have much to offer here. Something like https://github.com/chenglou/react-tween-state or https://github.com/pleasetrythisathome/react.animate may be helpful.

timelyportfolio commented 9 years ago

@jcheng5 I did this https://github.com/timelyportfolio/react_tutorial_in_R to maybe help R folks who might be interested in React. I was able to do all but the last bit > tutorial 14 where the comments are posted to server. Maybe this would be interesting to do in Shiny :)

timelyportfolio commented 9 years ago

https://github.com/hughsk/eue might be interesting as a standalone enter/exit tool.

timelyportfolio commented 9 years ago

Thought this little twitter blip might lend some insight

image

timelyportfolio commented 8 years ago

any more thoughts on this? At a minimum, has anyone tied Shiny + react.js together in any examples?

hafen commented 8 years ago

My 2 cents about transitions... I think if you want nice transitions in your vis, things get a little too complicated with React+d3. I've been doing some React+d3 work (some prototypes here and here for example) and have learned (the hard way) that if you want nice transitions, you need to let d3 take care of it. React tween / animation libraries are terrible for this kind of thing (at least the several approaches I have tried). But if you want to have d3 take care of transitions, you need to let d3 take care of its part of the DOM, and you therefore need to use d3 for (2) and (3). It doesn't feel very natural since both d3 and React want to own the DOM. It can be done, but is not super straightforward. Maybe there are approaches I'm not aware of though.

I'm working on a react app that I plan to tie in to shiny. I'll report any observations I have when I get there...

timelyportfolio commented 8 years ago

I should have known you would already have some beautiful examples. While I understand the value of transitions in visualizations, I am more concerned with coordinated views and centralized state. I would love to see anything that you can show from your react+shiny work. I will try to work up some experiments to more fully understand what is in my head.

ramnathv commented 8 years ago

I arrived at the same conclusion as @hafen. I have been building pure js stuff using react/d3/mobx. Transitions are easier with d3 while virtual dom libraries are great at structure. I will post some examples too.

hafen commented 8 years ago

I'll have to check out mobx. I've been recently using redux and have been pretty happy with it. It would be nice to formalize a way to get data to/from shiny that can be plugged in to these flux-like frameworks.