svgR Whirlwind Tutorial
library(svgR)

In what follows are three short lessons that walk through some of the basics of svgR.

Lesson 0: A Gentle Start:

The wh Attribute

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))

A Circle Shape

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")
)

Adding Text

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")
)
data

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))
)
data

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)
)
data

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)
)
data

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 )
)
data

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 )
)
data

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 )
)
data

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 )
)
data

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) )
)
data

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) )
)
data

Lesson 0 Summary

  • Call svgR( ) to create graphics.
  • wh sets the width and height
  • cxy sets the center (of the circle/ text)
  • lines are colored by stroke
  • regions are colored by fill
  • The upper-left hand corner is at c(0,0),
  • the coordinate system is measured down and right

Lesson 1: Building with Lists

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')
)
Coordinates 30,20 570,20 30,180 570,180

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')
)
Coordinates 30,20 570,20 30,180 570,180

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')
)
50,30 100,30 150,30 200,30 250,30 300,30 350,30 400,30 450,30 500,30 550,30 50,60 100,60 150,60 200,60 250,60 300,60 350,60 400,60 450,60 500,60 550,60 50,90 100,90 150,90 200,90 250,90 300,90 350,90 400,90 450,90 500,90 550,90 50,120 100,120 150,120 200,120 250,120 300,120 350,120 400,120 450,120 500,120 550,120 50,150 100,150 150,150 200,150 250,150 300,150 350,150 400,150 450,150 500,150 550,150

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')
)
0 50 100 150 200 250 300 350 400 450 500 550 600 0 30 60 90 120 150 180

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')
)
0 50 100 150 200 250 300 350 400 450 500 550 600 0 30 60 90 120 150 180

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')
)
0 50 100 150 200 250 300 350 400 450 500 550 600 0 30 60 90 120 150 180

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')
)
0 50 100 150 200 250 300 350 400 450 500 550 600 0 30 60 90 120 150 180

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')
)
0 50 100 150 200 250 300 350 400 450 500 550 600 0 30 60 90 120 150 180

Lessons 2: Making Compounds from Elements

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:

Scoping Issues

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

  • f %<c-% function(…){ }
  • f <- toCompound( function(…))

Group Based Compounds

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:

  • The function arguments are the ellipses (…).
  • Defaults are assigned for any missing attributes
  • Secondary defaults conditional on the first set of attrs are defined to allow for a fine control of attrs of compound elements.
  • A group container element (g) enclosing the circles is the return value
  • The args is placed at the bottom of the group element.
  • We supplied the coordinates cxy to each circle element

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)
)
groupCircleInCircle compound: fill=‘none’

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'))
  )
)
groupCircleInCircle compound: fill=radialGradient; r2=30

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’> groupCircleInCircle compound: rect element here xy=c(230,60) is set at the group level

Note:

  • Since the rectangle did not have a fill specified, it inherites the red fill from the fill attribute of the compound.
  • In contrast, the resultin rectangle is positioned at c(0,0) instead of xy=c(300,135). The reason is that groups do not support coordinate attributes such as x, y, cx, cy, r … . Assigning xy=c(300,135) at the group level is just ignored. For this reason, cxy was explicitly specified for each circle. To place the rectangle over the circle, the xy must be explicit assigned within the rect child.

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) )
  )
)
groupCircleInCircle compound: fill=radialGradient; r2=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')
  )
)
groupCircleInCircle compound: blue rect; white Underground

Relocating

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:

  • groupCircleInCircle(cxy=c(400,150), => groupCircleInCircle(cxy=c(100,200),
  • rect(xy=c(300,135), => rect(xy=c(0,185),
  • text(‘Underground’, cxy=c(400,150), => text(‘Underground’, cxy=c(100,200),
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')
  )
)
groupCircleInCircle compound: 3 edits to translate Underground

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))
  )
)
groupCircleInCircle compound: fill=radialGradient; r2=30 Underground

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))
  )
)
groupCircleInCircle compound: fill=radialGradient; r2=30 Underground

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)
  )
)
groupCircleInCircle compound: design at c(0,0), then tranlate Underground

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 …

SVG Compounds

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

  • g becomes svg
  • a viewbox is added
  • a preserveAspectRatio is added.
  • wh is added
  • circles are centered at the origin

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')
)
svgCircleInCircle component: fill=‘none’

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')
  )
)
svgCircleInCircle compound: centered Underground

The relevant points are:

  • The children (rect and text) are centered at c(0,0)
  • Specifing svgCircleInCircle(cxy=WH/2, is all that is need to center the image
    • No transforms was required
    • The center WH/2 is specified only once.
  • A wh=c(200,120) was specified.

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')
      )
)
svgCircleInCircle compound: translated using cxy Underground

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')
  )
)
svgCircleInCircle compound: scale using wh Underground

Two drawbacks to an svg-based composite

  • It will not respond to a transform attribute prevent easy rotation
  • Clipping occurs due to the viewPort. Any portion of a child element extending beyond the viewPort will not be rendered.
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))
  )
)
svgCircleInCircle compound: fails to respond to transform Underground
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')
    
  )
)
svgCircleInCircle compound: blue rect is being clipped Underground

List-based Compounds

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)
)

Filter-based compounds

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')
  )
)
filterChrome compound: R S hiny

Summary

A custom user defined element is a reusable widget-like entity that looks and behaves like an element. Custom defined elements should:

  • have arguments are either elements or attributes
  • named arguments are attributes
  • unnamed arguments not of class character (or numeric) are child elements
  • unnamed arguments of class character (or numeric) are text data
  • be deployed like an element

svgR