library(svgR)
In what follows are three short lessons that walk through some of the basics of svgR.
Before we draw anything we first make a simple call to svgR with wh=c(10,10)
svgR(wh=c(10,10))
In case you missed it, the above has a drawing region of 10 by 10 pixels. To see this lets flood the background with a color.
svgR(wh=c(10,10),
use(filter=filter( filterUnits="userSpaceOnUse",feFlood(flood.color='orange')))
)
For a bigger region, we just adjust the wh, for example, wh=c(600,100)
svgR(wh=c(600,100),
use(filter=filter( filterUnits="userSpaceOnUse",feFlood(flood.color='orange')))
)
Here wh is short for width and height. So the above is equivalent to
svgR(width="600", height="100",
use(filter=filter( filterUnits="userSpaceOnUse",feFlood(flood.color='orange')))
)
Of course, leaving out the flood, looks empty
svgR(wh=c(600,100))
One of the simplest examples to draw a circle. We might start trying just circle()
svgR( wh=c(600,100),
circle()
)
NO ERRORS BUT WE SEE NOTHING! This is because by default the radius is 0 and center of a circle is 0,0.
So lets try setting the radius to 100. We do this by supplying r=100
svgR( wh=c(600,100),
circle(r=100)
)
Better, but still only a quarter of a circle.
So next we set the center using cxy=c(100,100). (Note we could also h)
svgR( wh=c(600,100),
circle(r=100, cxy=c(100,100))
)
Hmmm. Now the circle is cut off at the bottom. This is because the vertical dimension screen display is olny 100.
So let’s reset the display to be 800 by 220
svgR( wh=c(800,220),
circle(r=100, cxy=c(100,100))
)
Now lets put the circle at the center. The center of our display is c(800/2,220/2)=c(400,110).
svgR( wh=c(800,220),
circle(r=100, cxy=c(400,110))
)
This looks better, although the circle is black. This is the default fill color. Now black is not very cheerful, so we change it to red.
svgR( wh=c(800,220),
circle(r=100, cxy=c(400,100), fill="red")
)
Now lets put some text in the center of the circle
svgR( wh=c(800,220),
text('data',cxy=c(400,110)),
circle(r=100, cxy=c(400,110), fill="red")
)
THIS DIDN’T WORK. That’s because we put the text first, and the circle painted red over it. We need to put the text after the circle if we want to see it. Order (or z-order) counts!
So we change the order of the text and circle
svgR( wh=c(800,220),
circle(r=100, cxy=c(400,110), fill="red"),
text('data',cxy=c(400,110))
)
Lets make the text larger and color it yellow
svgR( wh=c(800,220),
circle(r=100, cxy=c(400,110), fill="red"),
text('data',cxy=c(400,110), fill='yellow', font.size=50)
)
Now we add a blue stroke both
svgR( wh=c(800,220),
circle(r=100, cxy=c(400,110), fill="red", stroke='blue'),
text('data', cxy=c(400,110), fill='yellow', stroke='blue', font.size=50)
)
Now we remove the fill from the circle and the text by setting the fill to none (Remember, the fill is black by default, so if we simply ommit the fill, it will be black) By the way, since the fill is now set to none, the red fill of the circle will not cover the text if put the text first.
svgR( wh=c(800,220),
circle(r=100, cxy=c(400,110), fill="none", stroke='blue'),
text('data', cxy=c(400,110), fill='none', stroke='blue', font.size=50 )
)
Since all our objects have stroke blue and fill none, we can move those attributes assignments up one level.
svgR( wh=c(800,220),
fill="none", stroke='blue',
circle(r=100, cxy=c(400,100) ),
text('data', cxy=c(400,100), font.size=50 )
)
To make the circle and text more prominent we increase the size the stroke.
svgR( wh=c(800,220),
fill="none", stroke='blue', stroke.width=3,
circle(r=100, cxy=c(400,100) ),
text('data', cxy=c(400,100), font.size=50 )
)
Now what happens if we change the dimensions of our display area on the screen?
svgR( wh=c(400,400),
fill="none", stroke='blue', stroke.width=3,
circle(r=100, cxy=c(400,100) ),
text('data', cxy=c(400,100), font.size=50 )
)
Again, the circle is cutoff. To be independent of the display dimensions we resort to using variables.
WH<-c(600,300)
svgR( wh=WH,
fill="none", stroke='blue', stroke.width=3,
circle(r=min(WH/2), cxy=WH/2 ),
text('data', cxy=WH/2, font.size=.25*min(WH) )
)
But we would like to see the drawing region to make sure we our centering is correct. An easy solution is to again flood the region with a color. That is, use a filter with the feFlood filter element. Again, order counts, so we need to put the flood before we do the text and circle.
WH<-c(600,300)
svgR( wh=WH,
use(filter=
filter(filterUnits="userSpaceOnUse", feFlood(flood.color='lightgreen') )
),
fill="none", stroke='blue', stroke.width=3,
circle(r=min(WH/2), cxy=WH/2 ),
text('data', cxy=WH/2, font.size=.25*min(WH) )
)
Let’s start by putting down some points on our drawing area (viewPort) First
Recall from Lesson 0, that the dimensions of the display region is determined by setting wh in the call to svgR, and that we can flood the display region using a feFlood filter element. So, let’s flood the region and label some points
WH<-c(600,200)
coord<-expand.grid(c(30,WH[1]-30),c(20,WH[2]-20))
svgR( wh=WH,
use(filter=filter( feFlood(flood.color='lightblue'))),
text('Coordinates', cxy=WH/2, font.size=30 ),
text(paste(coord[1,],collapse=","), cxy=as.numeric(coord[1,])),
text(paste(coord[2,],collapse=","), cxy=as.numeric(coord[2,])),
text(paste(coord[3,],collapse=","), cxy=as.numeric(coord[3,])),
text(paste(coord[4,],collapse=","), cxy=as.numeric(coord[4,])),
rect(xy=c(0,0), wh=WH, stroke.width=3, fill="none", stroke='black')
)
Now that we can see the display region, we can identify a few points on it. But the code is a little hard on the eyes, and troublesome if we had more points. So lets combine using lapply
WH<-c(600,200)
coord<-expand.grid(c(30,WH[1]-30),c(20,WH[2]-20))
svgR( wh=WH,
use(filter=filter( feFlood(flood.color='lightblue'))),
text('Coordinates', cxy=WH/2, font.size=30 ),
lapply(1:nrow(coord), function(i){
cxy<-as.numeric(coord[i,])
txt<-paste(cxy, collapse=",")
text(txt, cxy=cxy)
}),
rect(xy=c(0,0), wh=WH, stroke.width=3, fill="none", stroke='black')
)
In svgR, unnamed arguments which are list are automatically “promoted” to become arguments of the calling function. So in our case the return of lapply is list whose results inserted in place.
Labeling more points is a snap
WH<-c(600,200)
dxy<-c(50,30)
coord<-expand.grid(
seq(from=dxy[1], to=WH[1]-dxy[1], by=dxy[1]),
seq(from=dxy[2], to=WH[2]-dxy[2], by=dxy[2])
)
svgR( wh=WH,
use(filter=filter( feFlood(flood.color='lightblue'))),
lapply(1:nrow(coord), function(i){
cxy<-as.numeric(coord[i,])
txt<-paste(cxy, collapse=",")
text(txt, cxy=cxy)
}),
rect(xy=c(0,0), wh=WH, stroke.width=3, fill="none", stroke='black')
)
Or just labeling the edges:
WH<-c(600,200)
dxy<-c(50,30)
xx<-seq(0,WH[1], by=dxy[1])
yy<-seq(0,WH[2], by=dxy[2])
svgR( wh=WH,
use(filter=filter( feFlood(flood.color='lightblue'))),
lapply(xx, function(x){ text(x,xy=c(x,10))}),
lapply(yy, function(y){ text(y,xy=c(0,y))}),
rect(xy=c(0,0), wh=WH, stroke.width=3, fill="none", stroke='black')
)
Now add some horzontal and vertical lines
WH<-c(600,200)
dxy<-c(50,30)
xx<-seq(0,WH[1], by=dxy[1])
yy<-seq(0,WH[2], by=dxy[2])
svgR( wh=WH,
use(filter=filter( feFlood(flood.color='lightblue'))),
lapply(xx, function(x){ text(x,xy=c(x,10))}),
lapply(xx, function(x){ line(xy1=c(x,0), xy2=c(x,WH[2]), stroke='black' )} ),
lapply(yy, function(y){ text(y,xy=c(0,y))}),
lapply(yy, function(y){ line(xy1=c(0,y), xy2=c(WH[1],y), stroke='black')} ),
rect(xy=c(0,0), wh=WH, stroke.width=3, fill="none", stroke='black')
)
An alternative is to combine the two lapply(xx) calls and the two lapply(yy) calls.
WH<-c(600,200)
dxy<-c(50,30)
xx<-seq(0,WH[1], by=dxy[1])
yy<-seq(0,WH[2], by=dxy[2])
svgR( wh=WH,
use(filter=filter( feFlood(flood.color='lightblue'))),
lapply(xx, function(x){
list(
text(x,xy=c(x,10)),
line(xy1=c(x,0), xy2=c(x,WH[2]), stroke='black' )
)
}),
lapply(yy, function(y){
list(
text(y,xy=c(0,y)),
line(xy1=c(0,y), xy2=c(WH[1],y), stroke='black')
)
}),
rect(xy=c(0,0), wh=WH, stroke.width=3, fill="none", stroke='black')
)
Here Each lapply returns an list of lists. Again svgR promotes these Another alternative is to use a group instead of a list. Grouping elements allows to treat the elements as a unit. Unlike the list, we can assign attributes to group. For example, here weassign two different stroke colors.
WH<-c(600,200)
dxy<-c(50,30)
xx<-seq(0,WH[1], by=dxy[1])
yy<-seq(0,WH[2], by=dxy[2])
svgR( wh=WH,
use(filter=filter( feFlood(flood.color='#DDDDFF'))),
lapply(xx, function(x){
g( stroke='red',
text(x,xy=c(x,10)),
line(xy1=c(x,0), xy2=c(x,WH[2]) )
)
}),
lapply(yy, function(y){
g( stroke='green',
text(y,xy=c(0,y)),
line(xy1=c(0,y), xy2=c(WH[1],y), stroke.dasharray=3, stroke.width=3)
)
}),
rect(xy=c(0,0), wh=WH, stroke.width=3, fill="none", stroke='black')
)
A final thought, what happens when we zoom out. (To zoom out we change the viewBox)
WH<-c(600,200)
dxy<-c(50,30)
xx<-seq(0,WH[1], by=dxy[1])
yy<-seq(0,WH[2], by=dxy[2])
svgR( wh=WH, viewBox=c(0,0,2*WH),
use(filter=filter( feFlood(flood.color='#DDDDFF'))),
lapply(xx, function(x){
g( stroke='red',
text(x,xy=c(x,10)),
line(xy1=c(x,0), xy2=c(x,WH[2]) )
)
}),
lapply(yy, function(y){
g( stroke='green',
text(y,xy=c(0,y)),
line(xy1=c(0,y), xy2=c(WH[1],y), stroke.dasharray=3, stroke.width=3)
)
}),
rect(xy=c(0,0), wh=WH, stroke.width=3, fill="none", stroke='black')
)
A Compound is a reusable composition of Elements can be inserted in an svgR call just like an element. They are reusable widget-like entities made from elements and work like elements.
Compounds should:
Simple Compounds are specially wrapped functions built by the developer that adhere to the above restrictions. The reason for these restrictions is to provide a uniform interface for their usage. (Principle of least suprises)
In this lesson we present three simple compounds, all quite similar, but each with slightly different behaviour. But first a trivial example:
Consider using a function make a red filled triangle
svgR( wh=c(600,200),
{
fn<-function(x){polygon( points = c(x,100) + c(c(-40,30), c(40,30), c(0,-50)), fill='red')}
fn(100)
}
)
This works great, but what happens moving fn outside of fn
fn<-function(x){polygon( points = c(x,100) + c(c(-40,30), c(40,30), c(0,-50)), fill='red')}
tryCatch({
svgR( wh=c(600,200),
fn(100))
},
error=function(e){ "Failed to complete execution of svgR" }
)
[1] “Failed to complete execution of svgR”
It fails, because elements are availabe only inside svgR.
To get around this restriction, we simply need to change the assignment fn <- function to fn %<c-% function.
fn %<c-% function(x){polygon( points = c(x,100) + c(c(-40,30), c(40,30), c(0,-50)), fill='red')}
tryCatch({
svgR( wh=c(600,200),
fn(100))
},
error=function(e){ "Failed to complete execution of svgR" }
)
Note: %<c-% is a short cut convenience operator that wraps the toCompound function.
The following are equivalent
We begin by considering a compound built around a group element. The example given here is groupCircleInCircle, which is a compound build from a group element containing two concentric circles:
groupCircleInCircle %<c-% function(...){
args<-list(...)
if(is.null(names(args))){
names(args)<-rep("",length(args))
}
defaults<-list(
r=50,
cxy=c(0,0)
)
#args<-c(args, defaults[setdiff(names(defaults),names(args))])
args<-c(args, defaults[sapply(args[names(defaults)], is.null)])
defaults2<-list( #secondary defaults (defaults base on primary)
r2=.8*args$r,
fill2=args$fill
)
#args<-c(args, defaults2[setdiff(names(defaults2),names(args))])
args<-c(args, defaults2[sapply(args[names(defaults2)], is.null)])
indx<-c("r","cxy", "r2", "fill2")
sargs<-args[indx]
args[indx]<-NULL
g(
circle(cxy=sargs$cxy, r=sargs$r),
circle(cxy=sargs$cxy, r=sargs$r2, fill=sargs$fill2),
args
)
}
Note:
Deploying the groupCircleInCircle Compound
WH=c(800,300)
svgR( wh=WH,
text("groupCircleInCircle compound: fill='none'",
xy=c(30,30), font.size=25),
groupCircleInCircle(cxy=c(400,150), fill='none',r=60, stroke='green', stroke.width=3)
)
Here groupCircleInCircle is given the attributes stroke=‘green’ and stroke.width=3. Inside the groupCircleInCircle function, these attributes are contained in the args variable and passed to g. From there the stroke and stroke.width attributes are propogated to each circle, supplying the resulting green stroke of width 3.
Now lets change the inner radius and fill the circles with a gradient .
WH=c(800,300)
svgR( wh=WH,
text("groupCircleInCircle compound: fill=radialGradient; r2=30",
xy=c(30,30), font.size=25),
groupCircleInCircle(cxy=c(400,150), r=60, r2=30, stroke='green', stroke.width=3,
fill=radialGradient(colors=c('red','yellow','blue'))
)
)
This time we we set the fill of the inner circle to white and the outer to red. Additionally we add a rectangle as a child.
WH=c(800,300)
svgR( wh=WH, xy=c(300,135),
text("groupCircleInCircle compound: rect element", xy=c(230,30), font.size=25),
text("here xy=c(230,60) is set at the group level", xy=c(230,60), font.size=15),
groupCircleInCircle(cxy=c(400,150), r=60, r2=40,
fill='red', fill2='white',
rect( wh=c(200,30) )
)
)
<svg xmlns=‘http://www.w3.org/2000/svg’ xmlns:xlink=‘http://www.w3.org/1999/xlink’ ev=‘http://www.w3.org/2001/xml-events’ xy=c(“‘300’”, “‘135’”) width=‘800’ height=‘300’> Note:
Explicitly specifing xy in the rect element we get:
WH=c(800,300)
svgR( wh=WH,
text("groupCircleInCircle compound: fill=radialGradient; r2=30", xy=c(30,30), font.size=25),
groupCircleInCircle(cxy=c(400,150), r=60, r2=40,
fill='red', fill2='white',
rect(xy=c(300,135), wh=c(200,30) )
)
)
Adding a blue fill attribute to the rectangle, and some white text gives:
WH=c(800,300)
svgR( wh=WH,
text("groupCircleInCircle compound: blue rect; white", xy=c(30,30), font.size=25),
groupCircleInCircle(cxy=c(400,150), r=60, r2=40,
fill='red', fill2='white',
rect(xy=c(300,135), wh=c(200,30) , fill='blue'),
text('Underground', cxy=c(400,150), font.size=20, stroke='white', fill='white')
)
)
Now suppose one wants to relocate this drawing such that the center is at c(100,200). The obvious, but messy way, would be to change all the coordinates in the call. That means 3 changes:
WH=c(800,300)
svgR( wh=WH,
text("groupCircleInCircle compound: 3 edits to translate", xy=c(30,30), font.size=25),
groupCircleInCircle(cxy=c(100,200), r=60, r2=40,
fill='red', fill2='white',
rect(xy=c(0,185), wh=c(200,30) , fill='blue'),
text('Underground', cxy=c(100,200), font.size=20, stroke='white', fill='white')
)
)
A better approach is to use the transform attribute.
WH=c(800,300)
svgR( wh=WH,
text("groupCircleInCircle compound: fill=radialGradient; r2=30", xy=c(30,30), font.size=25),
groupCircleInCircle(cxy=c(400,150), r=60, r2=40,
fill='red', fill2='white',
rect(xy=c(300,135), wh=c(200,30) , fill='blue'),
text('Underground', cxy=c(400,150), font.size=20, stroke='white', fill='white'),
transform=list(translate=c(-300,50))
)
)
This not only allows tranlation, but also scaling and rotation.
WH=c(800,300)
svgR( wh=WH,
text("groupCircleInCircle compound: fill=radialGradient; r2=30", xy=c(30,30), font.size=25),
groupCircleInCircle(cxy=c(400,150), r=60, r2=40,
fill='red', fill2='white',
rect(xy=c(300,135), wh=c(200,30) , fill='blue'),
text('Underground', cxy=c(400,150), font.size=20, stroke='white', fill='white'),
transform=list(rotate=c(45,WH/2))
)
)
In fact, a more natural usage would be to use the origin as the center and perform translation after the fact to get the proper location:
WH=c(800,300)
svgR( wh=WH,
text("groupCircleInCircle compound: design at c(0,0), then tranlate", xy=c(30,30), font.size=25),
groupCircleInCircle(cxy=c(0,0), r=60, r2=40,
fill='red', fill2='white',
rect(cxy=c(0,0), wh=c(200,30) , fill='blue'),
text('Underground', cxy=c(0,0), font.size=20, stroke='white', fill='white'),
transform=list(translate=WH/2)
)
)
This suggests that groupCircleInCircle could be simplified by harding-coding cxy=c(0,0) for each circle, and use transform tranlate in any application.
For more details on transform see …
As demonstrated the last section, without using transforms, the group-based compounds are messy to relocate.
The svg container provides an alternative to the group container. It does have an x and y, and so we can use the defaults on for the cxy of each circle. For example consider the svgCircleInCircle compound.
svgCircleInCircle %<c-% function(...){
args<-list(...)
if(is.null(names(args))){
names(args)<-rep("",length(args))
}
defaults<-list(
r=50,
cxy=c(50,50),
wh<-c(100,100),
preserveAspectRatio=c('xMidYMid','meet')
)
args<-c(args, defaults[sapply(args[names(defaults)], is.null)])
defaults2<-list( #secondary defaults (defaults base on primary)
r2=.8*args$r,
fill2=args$fill,
viewBox=c(-1,-1,2,2)*args$r
)
args<-c(args, defaults2[sapply(args[names(defaults2)], is.null)])
indx<-c("r","cxy", "r2", "fill2", "wh", "viewBox", "preserveAspectRatio")
sargs<-args[indx]
args[indx]<-NULL
svg(
cxy=sargs$cxy,
wh=sargs$wh,
viewBox=sargs$viewBox,
preserveAspectRatio=sargs$preserveAspectRatio,
circle(cxy=c(0,0), r=sargs$r),
circle(cxy=c(0,0), r=sargs$r2, fill=sargs$fill2),
args
)
}
The main differences between this and the group implementation
Note: An svg element always has an implicit viewBox generated from it’s viewPort and given the same coordinates as the view identical dimensions. When an svg element is not explicitly given a viewBox, an implicit viewBox is generate from the viewPort, which has the same coordinates as the viewPort. In the svgCircleInCircle composite a default viewBox centered about the origin is provided. This allows keeping child elements to be centered about the origin, while changing the x, y coordinates provided to the component.
Deploying the svgCircleInCircle Compound
WH=c(800,300)
r=30
svgR( wh=WH,
text("svgCircleInCircle component: fill='none'", xy=c(30,30), font.size=25),
svgCircleInCircle(cxy=WH/2, wh=c(200,100), fill='none',r=30, stroke='green')
)
Adding fills, text and children:
WH=c(800,300)
svgR( wh=WH,
text("svgCircleInCircle compound: centered", xy=c(30,30), font.size=25),
svgCircleInCircle(cxy=WH/2, wh=c(200,120),r=60, r2=40,
fill='red', fill2='white',
rect(cxy=c(0,0), wh=c(200,30) , fill='blue'),
text('Underground', cxy=c(0,0), font.size=20, stroke='white', fill='white')
)
)
The relevant points are:
Moreover to relocate to c(100,200) requires only one change: replace the single occurance of WH/2 by c(100,200)
WH=c(800,300)
svgR( wh=WH,
text("svgCircleInCircle compound: translated using cxy", xy=c(30,30), font.size=25),
svgCircleInCircle(cxy=c(100,200), wh=c(200,120),r=60, r2=40,
fill='red', fill2='white',
rect(cxy=c(0,0), wh=c(200,30) , fill='blue'),
text('Underground', cxy=c(0,0), font.size=20, stroke='white', fill='white')
)
)
Scaling can be accomplished by changinging the wh:
WH=c(800,300)
r=60
svgR( wh=WH,
text("svgCircleInCircle compound: scale using wh", xy=c(30,30), font.size=25),
svgCircleInCircle(cxy=WH/2, wh=2*c(220,120),r=60, r2=40,
fill='red', fill2='white',
rect(cxy=c(0,0), wh=c(200,30) , fill='blue'),
text('Underground', cxy=c(0,0), font.size=20, stroke='white', fill='white')
)
)
Two drawbacks to an svg-based composite
WH=c(800,300)
svgR( wh=WH,
text("svgCircleInCircle compound: fails to respond to transform", xy=c(30,30), font.size=25),
svgCircleInCircle(cxy=WH/2, wh=c(200,120),r=60, r2=40,
fill='red', fill2='white',
rect(cxy=c(0,0), wh=c(200,30) , fill='blue'),
text('Underground', cxy=c(0,0), font.size=20, stroke='white', fill='white'),
transform=list(rotate=c(45, 0,0))
)
)
WH=c(800,300)
svgR( wh=WH,
text("svgCircleInCircle compound: blue rect is being clipped", xy=c(30,30), font.size=25),
svgCircleInCircle(cxy=WH/2, wh=c(200,120),r=60, r2=40,
fill='red', fill2='white',
rect(cxy=c(0,0), wh=c(800,30) , fill='blue'),
text('Underground', cxy=c(0,0), font.size=20, stroke='white', fill='white')
)
)
A listbased Compound uses a list in place of a group or svg containers. As a result, a list-based compound can add attributes to it’s parent. To demonstrate this, consider slightly differ problem: given N, render a stack of N rectangles, each a fixed dimension. This means that the width and height of the containing svgR must be able to fit the rectangle stack.
For example, for N=4, with each rectangle having dimensions 600 by 50, with a, seperation of 5, the optimal WH is WH=c(600,215).
WH<-c(600,215)
dy<-50
W<-600
N<-4
svgR( wh=WH,
lapply(1:N, function(i){
rect(xy=c(0,(i-1)*(dy+5)),wh=c(W,dy), fill=rrgb())
}
)
)
To make this into an element with N as an attribute is simple, but would be nice is nice if the element we are inserting could set the WH for the parent svgR.
Here is how:
listMultiRects %<c-% function(...){
#the usual preproccessing
args<-list(...)
if(is.null(names(args))){
names(args)<-rep("",length(args))
}
defaults<-list(
wh=c(600,50),
sep=5,
N=4
)
missingArgs<-setdiff(names(defaults),names(args))
args<-c(args, defaults[missingArgs])
W<-args$wh[1]
dy<-args$wh[2]
N<-args$N
sep<-args$sep
WH<-c(W,N*(dy+sep))
list(
wh=WH,
lapply(1:N, function(i){
rect(xy=c(0,(i-1)*(dy+sep)),wh=c(W,dy), fill=rrgb())
}
)
)
}
WH<-c(600,215)
dy<-50
W<-600
N<-4
svgR(
listMultiRects(wh=c(500,10), N=10, sep=3)
)
Thus far all compounds were just containers for shape elements. In this section we consider something a littler different: Filters and fe elements (filter elements) Recall filters are containers for filter elements so it make since to perform a similar construct as before to create filter-based compounds.
filterChrome %<c-% function(...){
args<-list(...)
if(is.null(names(args))){
names(args)<-rep("",length(args))
}
defaults<-list(
surfaceScale=6,
specularConstant=1,
specularExponent=30,
lighting.color="yellow",
stdDeviation=5,
lighting.xyz=c(40,-30,200)
)
args<-c(args, defaults[sapply(args[names(defaults)], is.null)])
filter(
feMerge(
feMergeNode(in1="SourceGraphic"),
feMergeNode(
in1=feComposite( operator='in',
in1=feSpecularLighting(
surfaceScale=args$surfaceScale,
specularConstant=args$specularConstant,
specularExponent=args$specularExponent,
lighting.color=args$lighting.color,
in1=feGaussianBlur(
stdDeviation=args$stdDeviation,
in1="SourceAlpha"),
fePointLight(xyz=args$lighting.xyz)
),
in2="SourceAlpha"
)
)
)
)
}
Although we provided some attribute options (with defaults), this particular filter does not permit the addition other filter elements. We leave as exercise to the reader to create a filter compound taking arbritary filter elements.
WH<-c(800,300)
svgR(
wh=WH,
text("filterChrome compound: R", xy=c(30,30), font.size=25), g(
text( "S", cxy=WH/2, fill="darkblue", font.size=120,
font.family="san serif", stroke.width=3),
text( "hiny", cxy=WH/2+c(100,0), fill="darkblue", font.size=50,
font.family="san serif", stroke.width=3),
circle(cxy=WH/2, r=49, stroke.width=10, fill='none' ),
stroke='black',
filter=filterChrome(lighting.color='#EEAAFF')
)
)
A custom user defined element is a reusable widget-like entity that looks and behaves like an element. Custom defined elements should: