Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Making rgl widget #141

Open
dmurdoch opened this issue Jul 23, 2015 · 75 comments
Open

Making rgl widget #141

dmurdoch opened this issue Jul 23, 2015 · 75 comments

Comments

@dmurdoch
Copy link

I'd like to make an rgl widget, but I'm having trouble getting started, because the examples I've seen start with a Javascript library, rather than starting with R code. Here's what I'm thinking of doing:

  1. Put the rglClass definition that writeWebGL() creates into a Javascript file, and call it the Javascript library.
  2. Add functions to it to support the kinds of modifications people would want to do when using an rgl widget. These would essentially replace the writeWebGL function.

Does this sound like the right approach? Are there any examples to work from that are similar?

Duncan Murdoch

@ramnathv
Copy link
Owner

@dmurdoch Great to know that you want to make an rgl widget. The approach you have indicated is broadly correct. To make things concrete, here are some suggestions.

  1. Refactor writeWebGL separating the javascript and the R code. The js code essentially would be a large js function that accepts a json object as its argument. You can put this inside the renderValue method in the htmlwidgets/rgl.js folder.
  2. The R code would then work to create the R object that then gets passed on to this javascript function.

You can use htmlwidgets::scaffoldWidget('rgl') to set up a scaffold of all the components required for this widget. I would be happy to help with this widget. So let me know if you have any questions or run into any sort of troubles.

@ramnathv
Copy link
Owner

The one challenge I see here is that writeWebGL seems to be writing directly to script tags. For example, I pulled this off one of the examples.

<!-- ****** spheres object 6 ****** -->
<script id="vshader6" type="x-shader/x-vertex">
 attribute vec3 aPos;
 attribute vec4 aCol;
 uniform mat4 mvMatrix;
 uniform mat4 prMatrix;
 varying vec4 vCol;
 varying vec4 vPosition;
 attribute vec3 aNorm;
 uniform mat4 normMatrix;
 varying vec3 vNormal;
 void main(void) {
   vPosition = mvMatrix * vec4(aPos, 1.);
   gl_Position = prMatrix * vPosition;
   vCol = aCol;
   vNormal = normalize((normMatrix * vec4(aNorm, 1.)).xyz);
 }
</script>

Injecting such a script tag is not easy in htmlwidgets. @jcheng5 any thoughts on what mechanisms will help this use case. One approach that came to my mind is to save the generated script tag in a temporary javascript file and then use the attachment mechanism to dynamically load it.

@dmurdoch
Copy link
Author

That's not really necessary, it's just the way the model code did it that I based writeWebGL on. I was planning to make it pure Javascript anyway: I just need to put that shader code in a string.

@ramnathv
Copy link
Owner

Good to know @dmurdoch. If you hit any roadblocks along the way of implementing this widget, do let us know.

@timelyportfolio
Copy link
Collaborator

I would be happy to help also but with @dmurdoch and @ramnathv not sure I can add much.

@dmurdoch
Copy link
Author

I have some questions about details and philosophy of the implementation of htmlwidgets. Some background first:

Currently, writeWebGL is monolithic, not distinguishing between initialization and rendering. So I need to decide where the dividing line goes.

My plan for the first pass is to mimic writeWebGL from the user point of view. The user will call a series of rgl functions creating a scene within R, then make a single function call to translate everything into WebGL for display in the widget.

Next I'll add functions to allow modifications to an existing scene, but not adding or deleting elements. These will be like the existing propertySetter() and related functions.

Finally I'll make it general, so the htmlwidget display in RStudio could be used as the main display while developing the scene.

So, what's initialization, and what's rendering? One idea is a very minimal initialization: just create an empty rglClass object. When renderValue is called, argument "x" will be the whole scene to be rendered. The other extreme is that the initialization does most of what writeWebGL does now, and the renderValue function hardly does anything.

Which makes sense?

And one other question: in RMarkdown documents, it makes sense that each new display can start with the previous one, and just make some modifications. How do I do this in htmlwidgets?

@jcheng5
Copy link
Collaborator

jcheng5 commented Jul 25, 2015

(Sorry for brevity, typing this from my phone while on vacation)

Initialize is called once, renderValue can be called many times (not in an RStudio or R Markdown context, but when used with Shiny). If new data should always cause your widget to be reset then essentially everything in your monolithic approach can go into renderValue and you can leave initialize empty. However if you want to, say, preserve the camera angle (or whatever it's called in WebGL) or you want to somehow diff the old and new data and only change the parts of the scene that are necessary, then you might need to do a different initialize/renderValue split.

For the second question, we do this by having the R representation of the widget be a list that you modify using accessor functions, with pass by value semantics. The dygraph and leaflet packages both use this approach.

@jjallaire
Copy link
Collaborator

Examples of how progressively building up a leaflet visualization are here: http://rstudio.github.io/leaflet/.

You'll notice two functionally equivalent syntaxes. Here's the traditional one passing the map as the first argument to various mutator functions:

m <- leaflet()
m #print empty map

m <- addTiles(m)
m # print map with tiles

m <- addMarkers(m, lng=174.768, lat=-36.852, popup="The birthplace of R")
m # print map with markers

The same transformations can also be done via the %>% operator in a chain if you don't want to print the intermediate renderings:

leaflet() %>% 
   addTiles() %>% 
   addMarkers(lng=174.768, lat=-36.852, popup="The birthplace of R")

As Joe described there are different philosophies on whether to rebuild the world in renderValue verses doing smart "diffing" of representations and providing smooth transformation. Part of this depends on the ability of the underlying library to handle the "diffs" and part depends on whether you expect the component to be used frequently in Shiny applications where smooth transformations based on varying inputs will be required. In dygraphs we rebuild the world every time largely based on the fact that dygraphs doesn't handle incremental updates very well. In leaflet things are handled incrementally because this is often very useful for exploring spatial data.

@dmurdoch
Copy link
Author

Thanks Joe and JJ. Since rgl scenes are so big, I think it makes sense to make the initialize method fairly heavy, and then each of the renderValue calls won't need to send much.

Now I have a more detailed question. (Remember that I don't really know what I'm doing in Javascript.)

I need to transfer 4 by 4 matrices from R into Javascript "CanvasMatrix4" objects. I have been doing it by generating code like

m = new CanvasMatrix4();
m.load([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]);

Can this happen automatically in JSON (e.g. with a custom serializer), or should I just pass an array, and convert it to a CanvasMatrix4 object with Javascript code like the above?

@jcheng5
Copy link
Collaborator

jcheng5 commented Jul 26, 2015

Can this happen automatically in JSON (e.g. with a custom serializer), or should I just pass an array, and convert it to a CanvasMatrix4 object with Javascript code like the above?

The latter. htmlwidgets will only give you generic JS scalars, arrays, and objects--it's entirely on the author to turn those into domain specific objects (if necessary).

@jjallaire
Copy link
Collaborator

One more thing to keep in mind: the distinction between initialize and renderValue only matters for use within Shiny (at the R console and in R Markdown both are called only once). You can still of course progressively display / build up a widget payload with successive calls that mutate the x object but except for within Shiny every time you print the widget it's going to run both initialize and renderValue.

In the Shiny case however the widget is updated in place via the call to renderValue. This gives you the opportunity to do intelligent diffing between current and previous states of x and to only render the new options / data (and perhaps even do a nice visual transition from old to new state). However it should be emphasized that this sort of smart transition is going to be quite complex to code correctly. D3 provides a nice start for data transitions via it's enter/update/exit handlers however for many other libraries you'll need to do this from first principles. In some cases the library already supports this sort of dynamic update (in my case for dygraphs they advertised that they could do dynamic updates however in practice there were a bunch of exceptions to this so I ended up just re-rendering the world on every renderValue).

@dmurdoch
Copy link
Author

I've looked at it in more detail, and now I have an approach in mind. The initialize call will just set up an empty scene. When I call renderValue, it will either send the whole scene, or just some updates, according to a flag in x. (Essentially the flag will say whether to clear the scene or not.)

I don't want to send the whole scene every time because that can be a lot of data, and I'm worried it will be too slow. My first attempt at a Shiny implementation (back in December, long before I'd heard of htmlwidgets) resent the whole scene, and it took a couple of seconds to make a change.

@jjallaire
Copy link
Collaborator

Not to complicate things too much further, but I think the direction you are going is very similar to what we ended up with for leaflet. In that case for the Shiny scenario we created a leafletProxy function which provides a lightweight handle to an existing leaflet instance. All functions that work against leaflet also work against leafletProxy. See the docs here for usage examples: http://rstudio.github.io/leaflet/shiny.html

If the idea is to avoid the transfer of data from R to JavaScript then I think you'll need to do something like leafletProxy to avoid re-sending all of x every time. If however the main issue is rendering time then the flag within x is probably okay (that said, I think the proxy pattern is a nice way for the user to express the desire for incremental updates within Shiny). We ended up doing the same thing for DataTables (see the code at the bottom of this example: https://yihui.shinyapps.io/DT-proxy).

@jcheng5 and @yihui Know more about the particulars of the proxy pattern so may want to add something here.

@dmurdoch
Copy link
Author

Thanks for the pointer. It's the transfer of data that's the issue; WebGL is quite fast at rendering once things are in the browser. I'll follow the leafletProxy model when I get to that part of the implementation.

@dmurdoch
Copy link
Author

I'm making some progress on this, but now my lack of Javascript knowledge is slowing me down. These are probably simple questions:

  1. If I have a matrix in R, it gets passed to Javascript as an array of arrays (essentially row-major order). Are there Javascript functions defined for operations like cbind() (to concatenate rows of an n x p and an n x q matrix to form an n x (p+q) matrix?
  2. Is there a simple way to simplify my array of arrays to an array of values (e.g. change [[1,2], [3,4]] to [1,2,3,4])?
  3. Are there standard functions like nrow() and ncol(), or do I just use m.length for nrow(m), and m[0].length for ncol(m)?

@ramnathv
Copy link
Owner

There are functions that allow you to do these operations. For (2), you can use the code used in d3.merge.

d3.merge = function(arrays) {
  var n = arrays.length,
      m,
      i = -1,
      j = 0,
      merged,
      array;

  while (++i < n) j += arrays[i].length;
  merged = new Array(j);

  while (--n >= 0) {
    array = arrays[n];
    m = array.length;
    while (--m >= 0) {
      merged[--j] = array[m];
    }
  }

  return merged;
};

So d3.merge([[1, 2], [3, 4]]) will return [1, 2, 3, 4].

As for (3), the approach you are using is correct. You could wrap them up in to functions nrow and ncol so that the javascript code is R friendly.

I will try to add a solution for (1) later today.

@ramnathv
Copy link
Owner

For (1), I think you can use the concat method.

x = [[1, 2], [3, 4]]
y = [[5, 6]]
x.concat(y)
console.log(x)

This will give

x = [[1, 2], [3, 4], [5, 6]]

@timelyportfolio
Copy link
Collaborator

Ok, deleted comment also

@dmurdoch
Copy link
Author

I'm making good progress on this: most types of object now display. I'm currently working on textures. I think I could get them to work similarly to the way rgl handles them (i.e. copy the image file into the same dir as the web page, load from there), but a better way would be to put the image directly into the .html source. Is there support for that in htmlwidgets?

Specifically I need to end up with a Javascript Image() object that contains an image from a file, but I don't want the Javascript to read the file, I want R to read the file.

@jjallaire
Copy link
Collaborator

We have an attachment API (not yet documented) that was put in for just this sort of scenario. Here's some informal documentation that @ramnathv provided to another developer:

#71

Does that look like what you need? @jcheng5 Anything else to add here regarding correct use of the API?

@ramnathv
Copy link
Owner

There is also a pending PR #83 that attempts to make this process a whole lot easier. I will review it this weekend to see if we can merge this soon, as it would make things much easier.

@dmurdoch
Copy link
Author

I think my question was more naive than you thought. I've found the knitr::image_uri function, and it does what I wanted.

For anyone coming along later: In R, do

x$uri <- knitr::image_uri(filename)

which puts a long string representation of the image into a field of the object to display. In Javascript, turn it into an image by

image = new Image();
image.src = x.uri;

That's all that's needed.

@jjallaire
Copy link
Collaborator

That's great, glad it was that simple!

On Sat, Aug 22, 2015 at 8:57 AM, dmurdoch [email protected] wrote:

I think my question was more naive than you thought. I've found the
knitr::image_uri function, and it does what I wanted.

For anyone coming along later: In R, do

x$uri <- knitr::image_uri(filename)

which puts a long string representation of the image into a field of the
object to display. In Javascript, turn it into an image by

image = new Image();
image.src = x.uri;

That's all that's needed.


Reply to this email directly or view it on GitHub
#141 (comment)
.

@dmurdoch
Copy link
Author

I've now got a mostly working version of the rglwidget, in a package of that name on R-forge. It requires an unreleased version of rgl, also on R-forge.

Long range plans are to backport many of the rglwidget changes to rgl, but that won't happen for a while.

In the meantime, I've got one problem I'd like to work on. I think if I just display my rglwidget in the RStudio console, the viewer should show the display, but it doesn't. Is there anything like the Firefox web console or debugger that I can use to see what's going wrong?

@yihui
Copy link
Contributor

yihui commented Aug 26, 2015

On Linux and WIndows, you should be able to right click on the widget page, and use Inspect to debug the page. It is pretty much like Google Chrome's Developer Tools. WebGL may not work well in RStudio's viewer. At least it did not fully work when I was trying to display a 3D globe from the threejs package (https://github.com/bwlewis/rthreejs). What is your RStudio version number?

@dmurdoch
Copy link
Author

Thanks. I'm mainly using 0.99.467 on Mac OSX, so no Inspect there. I can run Windows XP in a VM, but current RStudio doesn't work well in XP. The Inspect page did work, but gave some spurious syntax errors. I can also run Ubuntu in a VM. I'm just installing 0.99.467 there.

@dmurdoch
Copy link
Author

I've basically got the rglwidget working in an Rmarkdown document now, so I wanted to get it going in Shiny. I've got two questions.

The last time I tried to do this (about 10 months ago, with rgl::writeWebGL writing the Javascript), I found the response time was too slow. I think this was because rgl scenes can really have a huge amount of data in them (up to megabytes), and the way I wrote it, this would all have to be downloaded again if the user made a change via Shiny. I want sliders that give near-instant responses.

Earlier this year I wrote various functions that took an existing scene and just modified a few
of the values within it via pure Javascript; that was fast, since it didn't need to go back to the server.

The time lag may be less of a problem now with the rglwidget since the Javascript doesn't get re-sent with each change, but there's still a lot of data. I decided on the following approach to deal with it. If I want Shiny to control a scene without redrawing the whole thing, I would insert two output objects: one that draws the scene, and the other that receives commands from Shiny and modifies the first one.
(The first is called "rglwidget", the second "rglcontroller".) The rglcontroller would be invisible.

First question: is this design a bad idea?

Assuming I go with the above design, the rglcontroller needs a way to refer to the rglwidget it is controlling. I had assumed I could use elementId for this, but I'm getting warnings:

Warning in func() :
  Ignoring explicitly provided widget ID "thewidget"; Shiny doesn't use them

Second question: how should one Shiny output object refer to another one in Javascript?

@ramnathv
Copy link
Owner

Great! A couple of things that are relevant here:

  1. In your renderValue function, you can explicitly store your instance of the rglwidget in the DOM by adding a line el.rglwidget = mywidgetinstance. From now on, you will be able to access your widget instance by id.
  2. With Shiny, you can use several explicit mechanisms to control what is passed between client and server. See this article for a nice description. I believe, this idea was used by @jcheng5 in the Leaflet package.

@jjallaire
Copy link
Collaborator

I think this may be where you need the "proxy" pattern implemented by
Leaflet (it has a leafletProxy function than supports all the same
operations as "leaflet" but references an existing instance). @jcheng5 or
@yihui can describe this in more detail.

On Sun, Aug 30, 2015 at 1:26 PM, Ramnath Vaidyanathan <
[email protected]> wrote:

Great! A couple of things that are relevant here:

In your renderValue function, you can explicitly store your instance
of the rglwidget in the DOM by adding a line el.rglwidget =
mywidgetinstance. From now on, you will be able to access your widget
instance by id.
2.

With Shiny, you can use several explicit mechanisms to control what is
passed between client and server. See this
https://ryouready.wordpress.com/2013/11/20/sending-data-from-client-to-server-and-back-using-shiny/
article for a nice description. I believe, this idea was used by
@jcheng5 https://github.com/jcheng5 in the Leaflet package.


Reply to this email directly or view it on GitHub
#141 (comment)
.

@dmurdoch
Copy link
Author

Thanks, I've got it working now with the rglcontroller. Now I need to write some controllers so I can try it out.

I'll also take a look at the leaflet source to see if that way makes more sense.

@dmurdoch
Copy link
Author

In a knitr document, I may have several variations on the same rgl scene. Using the old writeWebGL scheme I could figure out the name of the early one and copy some data from it in the later ones. I'd like to do the same with the rglwidget implementation. I can do it by explicitly specifying the elementId of each widget, but it would be nicer if the code could work out the auto-generated names that knitr will use.

Is there a way to figure out either the next auto-generated name, or the last one that was used?

@jjallaire
Copy link
Collaborator

I think that the explicit elementId is currently your only recourse (here's
where we determine the elementId if it's not provided:
https://github.com/ramnathv/htmlwidgets/blob/master/R/htmlwidgets.R#L52).

It wouldn't be too difficult to provide a bit more transparency here by
pre-generating blocks of ids on demand (thus making available nextId,
previousId, etc.) however this would be fragile visa vi whether your
widgets were interspersed with other widgets.

On Sat, Oct 17, 2015 at 12:32 PM, dmurdoch [email protected] wrote:

In a knitr document, I may have several variations on the same rgl scene.
Using the old writeWebGL scheme I could figure out the name of the early
one and copy some data from it in the later ones. I'd like to do the same
with the rglwidget implementation. I can do it by explicitly specifying the
elementId of each widget, but it would be nicer if the code could work out
the auto-generated names that knitr will use.

Is there a way to figure out either the next auto-generated name, or the
last one that was used?


Reply to this email directly or view it on GitHub
#141 (comment)
.

@dmurdoch
Copy link
Author

I tried setting the elementId automatically, but then Shiny gives a warning. Is there a way to detect that my rglwidget() is being called from a Shiny server?

@ramnathv
Copy link
Owner

Are you generating elementId automatically on the client side (in javascript?). If so, you can check for the presence of window.Shiny to detect that the widget is being called from a shiny server.

@yihui
Copy link
Contributor

yihui commented Oct 18, 2015

FYI, HTMLWidgets.shinyMode is a slightly better test for Shiny than window.Shiny.

@ramnathv
Copy link
Owner

Thanks @yihui

@dmurdoch
Copy link
Author

Thanks, but I think I need this on the R side. I'm keeping information about previous instances of the widget so that I don't have to send repeated data in later instances. I need to consult that list before constructing x in the createWidget() call.

@jcheng5
Copy link
Collaborator

jcheng5 commented Oct 19, 2015

You can try !is.null(shiny::getDefaultReactiveDomain()).

@dmurdoch
Copy link
Author

@jcheng5, thanks, that works for me.

@dmurdoch
Copy link
Author

If I have an rgl scene in a widget, I might want to add some controls for playing an animation, etc. I have two ways to do that now:

  1. In a Shiny app, I can turn on animation on a slider, and have it control the rgl widget through my invisible rglcontroller widget.
  2. In a knitr document, I can add a "playwidget" that displays some buttons and a slider to do what Shiny does, but without requiring a Shiny server to be involved.

I don't have a way to do this in the RStudio viewer, since (as far as I know) I can only display one widget at a time there. So, my questions:

  1. Is there a way to "bundle up" multiple widgets somehow so that they can be displayed simultaneously in the RStudio viewer?
  2. If not, is there a way to suppress the display of a widget? sizingPolicy(viewer.suppress = TRUE) causes my buttons to appear in a browser; I'd like them not to appear at all if I'm not in Shiny or knitr.
    (I guess I can do this with a combination test like the test for Shiny above.)
  3. Would a different design make more sense? It seems to me my rglwidget would get very complicated if it had to display buttons, sliders, etc, and it would be hard to make it flexible enough in placing them.

@jjallaire
Copy link
Collaborator

One approach to this would be to create an R function that returns a "Shiny
widget" or "Shiny gadget":

http://rmarkdown.rstudio.com/authoring_shiny_widgets.html

Basically you create an R function that returns a Shiny application which
is "printed" into the RStudio Viewer (this is essentially what ggvis does).

The downside of this is that the R console is blocked while your "gadget"
is displaying (this is a constraint we are hoping to lift eventually
though).

On Fri, Oct 30, 2015 at 1:46 PM, dmurdoch [email protected] wrote:

If I have an rgl scene in a widget, I might want to add some controls for
playing an animation, etc. I have two ways to do that now:

  1. In a Shiny app, I can turn on animation on a slider, and have it
    control the rgl widget through my invisible rglcontroller widget.
  2. In a knitr document, I can add a "playwidget" that displays some
    buttons and a slider to do what Shiny does, but without requiring a Shiny
    server to be involved.

I don't have a way to do this in the RStudio viewer, since (as far as I
know) I can only display one widget at a time there. So, my questions:

Is there a way to "bundle up" multiple widgets somehow so that they
can be displayed simultaneously in the RStudio viewer?
2.

If not, is there a way to suppress the display of a widget? sizingPolicy(viewer.suppress
= TRUE) causes my buttons to appear in a browser; I'd like them not to
appear at all if I'm not in Shiny or knitr.
(I guess I can do this with a combination test like the test for Shiny
above.)
3.

Would a different design make more sense? It seems to me my rglwidget
would get very complicated if it had to display buttons, sliders, etc, and
it would be hard to make it flexible enough in placing them.


Reply to this email directly or view it on GitHub
#141 (comment)
.

@dmurdoch
Copy link
Author

A few months ago (in pre-widget days) Henrik Bengtsson suggested I should allow users to add classes to the WebGL that writeWebGL inserted, to make it easier to do custom CSS. Now with the widget, I see the class is automatically set to my widget name plus "html-widget-static-bound".

Is there a way for users to add other class names? If not, this might be a useful addition to createWidget.

@jjallaire
Copy link
Collaborator

I think the best way to do this is allow specification of the extra class
name somewhere in the construction of your widget, save the name within the
widget's instance data passed to JavaScript, then simply add the class to
the element passed to initialize. @jcheng5 and @ramnathv is this the best
approach?

On Sat, Nov 28, 2015 at 9:12 AM, dmurdoch [email protected] wrote:

A few months ago (in pre-widget days) Henrik Bengtsson suggested I should
allow users to add classes to the WebGL that writeWebGL inserted, to make
it easier to do custom CSS. Now with the widget, I see the class is
automatically set to my widget name plus "html-widget-static-bound".

Is there a way for users to add other class names? If not, this might be a
useful addition to createWidget.


Reply to this email directly or view it on GitHub
#141 (comment)
.

@dmurdoch
Copy link
Author

Yes, I could do that pretty easily. The reason I thought it might be more appropriate in createWidget was that it's not really an rgl-specific request -- people might want to customize other widgets too. I already pass ... on to createWidget, so I'd automatically inherit this functionality if it was there.

But it's certainly not a big deal. All widget instances have ids, so their display could be customized via the id.

@ramnathv
Copy link
Owner

As @jjallaire said, this would have to be done from the javascript side, since the widget html does not receive the data passed on. We could add this behavior to htmlwidgets.js. @jcheng5 what are your thoughts?

@dmurdoch
Copy link
Author

I think it's actually in R: my widget_html function, called from htmlwidgets:::widget_html, receives a "class" argument. I think it contains just the widget name, the other name is added later. The suggestion was to allow users to add more names to the class via an argument to createWidget. But again, it's not important.

@jcheng5
Copy link
Collaborator

jcheng5 commented Nov 28, 2015

@dmurdoch Regarding the question you brought up on Oct 30 (about showing multiple widgets in the viewer and having them interact, without Shiny) you'll be able to do this soon. You can already show multiple widgets in the viewer but it's currently difficult to get them to interact:

htmltools::browsable(tagList(
  widget1,
  widget2
))

You can put arbitrary htmltools tag objects in tagList, so you can do layouts with divs, tables, or whatever.

The trouble in getting them to interact is that there's not a natural notion of "JavaScript object that represents widget x", and even if there were, there isn't a way to say "give me the JS object that represents widget x" from any old snippet of JS. These should both be coming next week; keep an eye on #171.

@jcheng5
Copy link
Collaborator

jcheng5 commented Nov 28, 2015

@dmurdoch With regards to the custom CSS classes, I envisioned that custom CSS classes would be added to your own (nested) elements in JavaScript. I wouldn't generally recommend you adding them to the outermost el that's passed to renderValue if the class is specific to one particular renderValue, because during the execution of a Shiny app, the same element can be reused for multiple renderValue calls as data changes, and it seems reasonable that the custom class could change as well. So, better to put that class on a different element inside the el, so don't have to worry about carefully removing old custom classes on the outermost element.

@dmurdoch
Copy link
Author

@jcheng5, thanks for the multiple widget pointer. In fact, that works for me right now: the normal way my rglwidget interacts with the controller is by putting an explicit elementId on the rglwidget. I can do that with your code. For example,

library(rgl)
library(rglwidget)
library(htmltools)

theta <- seq(0, 6*pi, len=100)
xyz <- cbind(sin(theta), cos(theta), theta)
plot3d(xyz, type="l")
id <- spheres3d(xyz[1,,drop=FALSE], col="red")

browsable(tagList(
  rglwidget(elementId = "testID", width=500, height=300), 
  playwidget("testID", vertexControl(values=xyz, 
                                     attributes=c("x", "y", "z"), 
                                     objid = id, param=1:100),
                                     start = 1, stop = 100, rate=10)
))

This probably needs the R-forge versions of rglwidget and rgl; there have been a lot of changes lately.

@dmurdoch
Copy link
Author

dmurdoch commented Dec 2, 2015

@jcheng5, I've noticed one strange thing: if I display an rglwidget in a browsable tagList as above, and the rglwidget has some semi-transparent components (e.g. by adding "alpha = 0.5" to the spheres3d() call in that example), then the rglwidget doesn't display properly at first. Once I move it, things are fine.

Displaying the same rglwidget on its own is fine.

Is there some difference between the order of initialization for a widget being displayed on its own and one being displayed as part of a browsable tagList?

@jcheng5
Copy link
Collaborator

jcheng5 commented Dec 2, 2015

@dmurdoch: Hmmm, that's quite surprising. If you View Source in a browser, does the generated HTML look different? Especially the <script> and <link> tags in the head?

@timelyportfolio
Copy link
Collaborator

standalone does result in a different container. See lines as.tags.htmlwidget and then toHTML. Would that help explain the difference? I have run into different behavior before in mapshaper_htmlwidget. I inserted these lines to make it position correctly.

@dmurdoch
Copy link
Author

dmurdoch commented Dec 2, 2015

On 02/12/2015 12:39 PM, timelyportfolio wrote:

standalone does result in a different container. See lines as.tags.htmlwidget and then toHTML. Would that help explain the difference?

Thanks for the suggestions. I'm not going to have a lot more time today
to look into these, but I'll do so tomorrow.

Duncan

@dmurdoch
Copy link
Author

dmurdoch commented Dec 3, 2015

I'm seeing the same error elsewhere, it has nothing to do with the tagList(). My previous test was flawed.

So this is purely my error. Sorry for the noise.

@dmurdoch
Copy link
Author

dmurdoch commented Dec 7, 2015

I think I understand the problem now, and have mostly fixed it.

A different question: I'm trying out magrittr pipes as a way to construct more complicated displays, e.g.

  theta <- seq(0, 6*pi, len=100)
  xyz <- cbind(sin(theta), cos(theta), theta)
  lineid <- plot3d(xyz, type="l", alpha = 1:0, lwd = 5, col = "blue")["data"]

  rglwidget(width=500, height=300) %>%
  playwidget(ageControl(births = theta,
                    ages = c(0, 0, 1),
                    objids = lineid,
                    alpha = c(0, 1, 0)),
         start = 1, stop = 6*pi,
         step = 0.1, rate = 6)

I'll want these to handle objects produced by rglwidget and playwidget, but also other kinds of things, like static HTML, or other widgets. What classes can I expect to see? Here are the ones I know about:

  • shiny.tag or shiny.tag.list from static HTML
  • htmlwidget from other widgets

Are there any others? Is there an existing convention in other widgets for doing this (i.e. an arg name for the receiver?) I'd like to be able to do something like

h2("Title") %>% rglwidget() %>% tagList(leaflet() ...)

@jcheng5
Copy link
Collaborator

jcheng5 commented Dec 7, 2015

There isn't really a convention for this yet (that I know of) as we're just
getting going with the multiple-widgets-on-a-page stuff (other than Shiny).
We haven't discussed layout yet but I have been thinking about it for some
time.

I was imagining not combining different widgets with %>% but rather with
layout function calls, as in Shiny.

htmlPage(
h2("Title"),
rglwidget(),
tagList(leaflet() ...)
)

I don't imagine that mere aggregation of these widgets is going to
suffice--you're going to want to position them relative to each other,
put them in grids, make them use the full height of the browser,
etc.--so we'd have a whole family of function calls to do some of
those common things. They should probably live in the htmltools
package, as they don't have a technical reason to be tied to
htmlwidgets or Shiny.

On Mon, Dec 7, 2015 at 6:02 AM dmurdoch [email protected] wrote:

I think I understand the problem now, and have mostly fixed it.

A different question: I'm trying out magrittr pipes as a way to construct
more complicated displays, e.g.

theta <- seq(0, 6*pi, len=100)
xyz <- cbind(sin(theta), cos(theta), theta)
lineid <- plot3d(xyz, type="l", alpha = 1:0, lwd = 5, col = "blue")["data"]

rglwidget(width=500, height=300) %>%
playwidget(ageControl(births = theta,
ages = c(0, 0, 1),
objids = lineid,
alpha = c(0, 1, 0)),
start = 1, stop = 6*pi,
step = 0.1, rate = 6)

I'll want these to handle objects produced by rglwidget and playwidget,
but also other kinds of things, like static HTML, or other widgets. What
classes can I expect to see? Here are the ones I know about:

  • shiny.tag or shiny.tag.list from static HTML
  • htmlwidget from other widgets

Are there any others? Is there an existing convention in other widgets for
doing this (i.e. an arg name for the receiver?) I'd like to be able to do
something like

h2("Title") %>% rglwidget() %>% tagList(leaflet() ...)


Reply to this email directly or view it on GitHub
#141 (comment)
.

@dmurdoch
Copy link
Author

dmurdoch commented Dec 7, 2015

On 07/12/2015 12:59 PM, Joe Cheng wrote:

There isn't really a convention for this yet (that I know of) as we're just
getting going with the multiple-widgets-on-a-page stuff (other than Shiny).
We haven't discussed layout yet but I have been thinking about it for some
time.

I was imagining not combining different widgets with %>% but rather with
layout function calls, as in Shiny.

htmlPage(
h2("Title"),
rglwidget(),
tagList(leaflet() ...)
)

I don't imagine that mere aggregation of these widgets is going to
suffice--you're going to want to position them relative to each other,
put them in grids, make them use the full height of the browser,
etc.--so we'd have a whole family of function calls to do some of
those common things. They should probably live in the htmltools
package, as they don't have a technical reason to be tied to
htmlwidgets or Shiny.

Thanks. The main advantage I see to using the %>% notation is that my
rglwidget() and playwidget() need to interact, and that notation makes
one an argument to the other. The way the version on CRAN does it
(without %>%) is to name everything explicitly and pass the names to
other components, but that ends up more verbose.

Duncan

@jcheng5
Copy link
Collaborator

jcheng5 commented Dec 7, 2015

The main advantage I see to using the %>% notation is that my
rglwidget() and playwidget() need to interact, and that notation makes
one an argument to the other.

OK, sure, that makes sense. If you want you can now retrieve widget instance objects by class (HTMLWidgets.find(".rglwidget")), but I can see how your approach has a more explicit object-to-object relationship while still being concise.

@ramnathv
Copy link
Owner

ramnathv commented Dec 7, 2015

I see the advantage of using %>%, but I prefer the flexibility provided by htmltools to do the layout. This way a user can position the rglwidget and playwidget as desired.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants