svgPanZoom - htmlwidget to Pan / Zoom R graphics
svgPanZoom
is a htmlwidgets
wrapper for svg-pan-zoom.js
. svgPanZoom
gives R
users an easy way to add panning and zooming to any R
graphics (base, ggplot2, lattice, and lots more). It prioritizes ease and simplicity, but does offer some ability to tailor the experience and enhance the interactivity.
To get started, you may choose from CRAN install.packages("devtools")
or Github remotes::install_github("timelyportfolio/svgPanZoom")
. svgPanZoom
is fairly stable, so CRAN is likely your best choice.
As stated in the introduction svgPanZoom
works with almost all R
graphics types. For base
graphics, we'll need the svglite
package.
library(svgPanZoom) # see install step above
library(svglite)
svgPanZoom(
svglite:::inlineSVG(
plot(1:10)
)
)
There are lots more examples below, but real quickly here is how we can use it in Shiny.
library(shiny)
library(svglite)
library(svgPanZoom)
library(ggplot2)
ui <- shinyUI(bootstrapPage(
svgPanZoomOutput(outputId = "main_plot")
))
server = shinyServer(function(input, output) {
output$main_plot <- renderSvgPanZoom({
p <- ggplot() + geom_point(data=data.frame(faithful),aes(x=eruptions,y=waiting)) + stat_density2d(data=data.frame(faithful),aes(x=eruptions,y=waiting, alpha =..level..),geom="polygon") + scale_alpha_continuous(range=c(0.05,0.2))
svgPanZoom(p, controlIconsEnabled = T)
})
})
runApp(list(ui=ui,server=server))
svglite
also works with grid
graphics, such as ggplot2
and lattice
. Before I show an example though, I highly recommend using gridSVG
for ggplot2
and lattice
. For some good reasons, please see this from Paul Murrell and Simon Potter. If you are making big graphics--think maps, multiple graphs, etc.--for speed stick with svglite
. Here is a simple example using ggplot2
with svglite
and svglite:::inlineSVG
.
library(svgPanZoom)
library(svglite)
library(ggplot2)
svgPanZoom(
svglite:::inlineSVG(
#will put on separate line but also need show
show(
ggplot(data.frame(x=1:10,y=1:10),aes(x=x,y=y)) + geom_line()
)
)
)
Now let's do that same plot with gridSVG
.
svgPanZoom(
ggplot(data.frame(x=1:10,y=1:10),aes(x=x,y=y)) + geom_line()
)
You might notice right off that sizing is better handled, but more importantly, the resulting SVG
is much better structured XML
. That small time lag will really start to hurt if you are using gridSVG
with large or complicated graphics. So if speed is important, ignore the better structure from gridSVG
and stick with svglite
.
As promised, lattice
(yes, I still use it and like it) works just as nicely.
library(svgPanZoom)
library(svglite)
library(lattice)
# with gridSVG
svgPanZoom(
xyplot( y~x, data.frame(x=1:10,y=1:10), type = "b" )
)
# with svgPlot
svgPanZoom(
svglite:::inlineSVG(
show(xyplot( y~x, data.frame(x=1:10,y=1:10), type = "b" ))
)
)
If you are not impressed yet, then maybe the graphics were not compelling enough. Let's add svgPanZoom
to some more complicated visualizations.
library(choroplethr)
# from chorplethr documetation
data(df_pop_state)
m = state_choropleth(
df_pop_state
, title="US 2012 State Population Estimates"
, legend="Population"
)
# take a peek
m
# would be so much more fun with pan and zoom
svgPanZoom( m )
# if your map is big and hairy do this instead
svgPanZoom(
svglite:::inlineSVG(
show(m )
# will have to manually size the svg device
, height = 10, width = 16 )
)
How about using ggfortify
? Here are some great examples. Let's make some pan and zoom.
# devtools::install_github('sinhrks/ggfortify')
library(ggfortify)
svgPanZoom(
autoplot(lm(Petal.Width ~ Petal.Length, data = iris))
)
library(survival)
svgPanZoom(
autoplot(survfit(Surv(time, status) ~ sex, data = lung))
)
If ternary diagrams excite you, let's do this USDA soil example from ggtern
.
#install.packages("ggtern")
# use example from ?ggtern::USDA
library(ggtern)
library(plyr)
#Load the Data.
data(USDA)
#Put tile labels at the midpoint of each tile.
USDA.LAB <- ddply(USDA,"Label",function(df){apply(df[,1:3],2,mean)})
#Tweak
USDA.LAB$Angle=0; USDA.LAB$Angle[which(USDA.LAB$Label == "Loamy Sand")] = -35
# Construct the plot.
gTern <- ggtern(data=USDA,aes(Sand,Clay,Silt,color=Label,fill=Label)) +
geom_polygon(alpha=0.75,size=0.5,color="black") +
geom_mask() +
geom_text(data=USDA.LAB,aes(label=Label,angle=Angle),color="black",size=3.5) +
theme_rgbw() +
theme_showsecondary() +
theme_showarrows() +
weight_percent() + guides(fill='none') +
theme_legend_position("topleft")
labs(
title="USDA Textural Classification Chart",
fill="Textural Class",color="Textural Class"
)
svgPanZoom(gTern)
The true test for me though will be financial time series plots. Will svgPanZoom
pass the test?
library(svgPanZoom)
library(svglite)
library(PerformanceAnalytics)
data(edhec)
svgPanZoom(
svglite:::inlineSVG(
charts.PerformanceSummary(
edhec
,main = "Performance of EDHEC Indicies"
)
),
controlIconsEnabled = TRUE
)
library(quantmod)
getSymbols("JPM", from = "2013-12-31")
svgPanZoom(
svglite:::inlineSVG(
chartSeries(JPM, theme = chartTheme('white'),
multi.col=T,TA="addVo();addBBands();addCCI()")
,height = 7
,width = 12
)
)
Tal Galili's dendextend
offers another great use case for pan and zoom interaction. Let's look at one of the examples from the package vignette.
# install.packages("dendextend")
library(dendextend)
library(svglite)
library(svgPanZoom)
data(iris)
d_iris <- dist(iris[,-5]) # method="man" # is a bit better
hc_iris <- hclust(d_iris)
dend_iris <- as.dendrogram(hc_iris)
iris_species <- rev(levels(iris[,5]))
dend_iris <- color_branches(dend_iris,k=3, groupLabels=iris_species)
# have the labels match the real classification of the flowers:
labels_colors(dend_iris) <-
rainbow_hcl(3)[sort_levels_values(
as.numeric(iris[,5])[order.dendrogram(dend_iris)]
)]
# We'll add the flower type
labels(dend_iris) <- paste(as.character(iris[,5])[order.dendrogram(dend_iris)],
"(",labels(dend_iris),")",
sep = "")
dend_iris <- hang.dendrogram(dend_iris,hang_height=0.1)
# reduce the size of the labels:
dend_iris <- assign_values_to_leaves_nodePar(dend_iris, 0.5, "lab.cex")
par(mar = c(3,3,3,7))
svglite:::inlineSVG(
{
plot(dend_iris,
main = "Clustered Iris dataset
(the labels give the true flower species)",
horiz = TRUE, nodePar = list(cex = .007))
legend("topleft", legend = iris_species, fill = rainbow_hcl(3))
}
, height = 12, width = 14
) %>% svgPanZoom
For what I consider the ultimate test, will it work with HiveR
?
# from HiveR documentation ?plotHive
library(HiveR)
library(grid)
library(svglite)
library(svgPanZoom)
data(HEC)
svgPanZoom(
svglite:::inlineSVG(
{
data(HEC)
currDir = getwd()
setwd(system.file("extdata", "Misc", package = "HiveR"))
plotHive(HEC, ch = 0.1, bkgnd = "white",
axLabs = c("hair\ncolor", "eye\ncolor"),
axLab.pos = c(1, 1),
axLab.gpar = gpar(fontsize = 14),
anNodes = "HECnodes.txt",
anNode.gpar = gpar(col = "black"),
grInfo = "HECgraphics.txt",
arrow = c("more\ncommon", 0.0, 2, 4, 1, -2))
grid.text("males", x = 0, y = 2.3, default.units = "native")
grid.text("females", x = 0, y = -2.3, default.units = "native")
grid.text("Pairing of Eye Color with Hair Color", x = 0, y = 3.75,
default.units = "native", gp = gpar(fontsize = 18))
grid.text("A test of plotHive annotation options", x = 0, y = 3.25,
default.units = "native", gp = gpar(fontsize = 12))
grid.text("Images from Wikipedia Commons", x = 0, y = -3.5,
default.units = "native", gp = gpar(fontsize = 9))
setwd(currDir)
}
, height = 20
, width = 30)
)