The hardware and bandwidth for this mirror is donated by METANET, the Webhosting and Full Service-Cloud Provider.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]metanet.ch.
shinyaframe
provides a bridge between R and Web-based Virtual Reality (WebVR) experiences using RStudio Shiny and Mozilla’s A-Frame WebVR framework. WebVR is a cross-platform standard that allows the same content to be delivered to desktop monitors as a 2D projection, to mobile phones as a 360-degree 3D experience, and to high-end virtual reality systems as an immersive, hands-on virtual reality experience.
The gg-aframe JavaScript package, also from this package’s author, is included in shinyaframe
and provides a declarative “Grammar of Graphics” style HTML syntax for constructing 3-Dimensional data visualizations for WebVR. An example of that syntax for a basic scatter plot is shown below; refer to the link above for complete documentation.
<a-scene>
<!-- position and rotation set the plot location in 3D space -->
<a-entity plot scale-shape position="0 1.6 -1.38" rotation="0 35 0">
<a-entity layer-point
data-binding__sepal.length="target: layer-point.x"
data-binding__sepal.width="target: layer-point.y"
data-binding__petal.length="target: layer-point.z"
data-binding__species="target: layer-point.shape"
data-binding__petal.width.size="target: layer-point.size">
</a-entity>
</a-entity>
</a-scene>
The data-binding
attributes in the gg-aframe
map from a central data repository to the aesthetic properties of the plot components, and this is where shinyaframe
applies. The aDataScene
function takes as input data from R and adds it to the data-binding
repository using the htmlwidgets
R package. Repeat calls will update the repository and the plot so that interactive visualizations are simple to implement in a Shiny app.
The example below shows the R code to provide the data for the gg-aframe
plot above. Note that variables mapped to position and size are scaled in R before sending to gg-aframe
whereas shape is not. In the plot specification, the scale-shape
attribute enables gg-aframe
to take raw input data for shape and map it to a polyhedral scale.
library(dplyr)
library(scales)
library(shinyaframe)
names(iris) <- tolower(names(iris))
iris %>%
# scale positional data to (0,1)
mutate_if(is.numeric, rescale) %>%
# scale size data to relative percentage
mutate(petal.width.size = rescale(petal.width, to = c(0.5, 2))) %>%
aDataScene()
Shiny provides the glue to connect the HTML and R in the above examples. The Shiny app ui
will create the HTML gg-aframe
syntax, and the server
will transform and send the data. The Shiny binding functions in shinyaframe
are aDataSceneOuput
for the ui
, which will take all additional gg-aframe
syntax as arguments, and renderADataScene
for the server
, which will process the data and return a call to aDataScene
. Also included in shinyaframe
are functions to render the custom HTML elements used by A-Frame and gg-aframe
from a ui written in R, and they are exported in the atags
list for convenient access.
The example below creates an iris data 3D scatterplot in a Shiny app. In addition to the code from the examples above, it also includes mapping and output for the axes and legend guides.
library(shiny)
library(dplyr)
library(scales)
library(shinyaframe)
shinyApp(
ui = fluidPage(
aDataSceneOutput(
# attributes and child elements provided as arguments
# server output variable name
outputId = "mydatascene",
# add backdrop
environment = "",
# gg-aframe plot syntax
atags$entity(
# an empty string sets attributes with no additional properties
plot = "",
# sizable scale option uses polyhedra scaled for equivalent volumes
`scale-shape` = "sizable",
position = "0 1.6 -1.38",
atags$entity(
`layer-point` = "",
`data-binding__sepal.length`="target: layer-point.x",
`data-binding__sepal.width`="target: layer-point.y",
`data-binding__petal.length`="target: layer-point.z",
`data-binding__species`="target: layer-point.shape",
`data-binding__petal.width.size`="target: layer-point.size",
`data-binding__species.color`="target: layer-point.color"
),
atags$entity(
`guide-axis` = "axis: x",
`data-binding__xbreaks` = "target: guide-axis.breaks",
`data-binding__xlabels` = "target: guide-axis.labels",
`data-binding__xtitle` = "target: guide-axis.title"
),
atags$entity(
`guide-axis` = "axis: y",
`data-binding__ybreaks` = "target: guide-axis.breaks",
`data-binding__ylabels` = "target: guide-axis.labels",
`data-binding__ytitle` = "target: guide-axis.title"
),
atags$entity(
`guide-axis` = "axis: z",
`data-binding__zbreaks` = "target: guide-axis.breaks",
`data-binding__zlabels` = "target: guide-axis.labels",
`data-binding__ztitle` = "target: guide-axis.title"
),
atags$entity(
`guide-legend` = "aesthetic: shape",
`data-binding__shapetitle` = "target: guide-legend.title"
),
atags$entity(
`guide-legend` = "aesthetic: size",
`data-binding__sizebreaks` = "target: guide-legend.breaks",
`data-binding__sizelabels` = "target: guide-legend.labels",
`data-binding__sizetitle` = "target: guide-legend.title"
),
atags$entity(
`guide-legend` = "aesthetic: color",
`data-binding__colorbreaks` = "target: guide-legend.breaks",
`data-binding__colorlabels` = "target: guide-legend.labels",
`data-binding__colortitle` = "target: guide-legend.title"
),
# animate the plot rotation
atags$other('animation', attribute = "rotation",
from = "0 45 0", to = "0 405 0",
dur = "10000", `repeat` = "indefinite")
)
)
),
server = function(input, output, session) {
output$mydatascene <- renderADataScene({
names(iris) <- tolower(names(iris))
# Margin in (0,1) scale keeps polyhedra from sticking out of plot area
positional_to <- c(0.01, 0.99)
# convert to #RRGGBB color
color_scale = setNames(rainbow(3, 0.75, 0.5, alpha = NULL),
unique(iris$species))
iris %>%
# scale positional data
mutate_if(is.numeric, rescale, to = positional_to) %>%
# scale size data to relative percentage, using cube root to correct
# for radius->volume perception bias
mutate(petal.width.size = rescale(petal.width^(1/3), to = c(0.5, 2)),
species.color = color_scale[species]) ->
iris_scaled
# provide guide info
make_guide <- function (var, aes, breaks = c(0.01, 0.5, 0.99)) {
guide = list()
domain = range(iris[[var]])
guide[[paste0(aes, "breaks")]] <- breaks
guide[[paste0(aes, "labels")]] <- c(domain[1],
round(mean(domain), 2),
domain[2])
guide[[paste0(aes, "title")]] <- var
guide
}
Map(make_guide,
var = c("sepal.length", "sepal.width", "petal.length"),
aes = c("x", "y", "z")) %>%
# repeat radius adjustment in the guide
c(list(make_guide("petal.width", "size", c(0.5, 1.25, 2)^(1/3)))) %>%
Reduce(f = c) ->
guides
guides$shapetitle = "species"
guides$colortitle = "species"
guides$colorbreaks = color_scale
guides$colorlabels = names(color_scale)
# convert data frame to list and combine with guides list
aDataScene(c(iris_scaled, guides))
})
}
)
The output of this Shiny app would appear as below:
You can run this example to view the interactive version by calling example("shinyaframe")
(open the app in a Web browser; it will not function in the RStudio viewer).
These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.