svgR Users Guide

This document describes some of the basics of svgR. This document has been created from an R Markdown document using svgR. For more details on using R Markdown see http://rmarkdown.rstudio.com. For a good starting place on using SVG see http://tutorials.jenkov.com/svg and https://github.com/willianjusten/awesome-svg





One Line Teasers

Here we illustrate a simple circle of radius 50 located at 200,20

svgR( wh=c(600, 100), circle(cxy=c(200,50),r=50, fill='lightgreen') )

This illustates the use of lapply to constuct multiple circles

svgR(  wh=c(600, 150), lapply(1:10, function(i)circle(cxy=c(i*30,i*10),r=5+i, fill=rrgb() ) ))

Again using lapply, but with random colors.

svgR( wh=c(600, 100),  lapply(10:1, function(i)circle(cxy=c(100,50),r=i*5, fill=rrgb()) ))

Again using lapply, on circle radii.

svgR( wh=c(600, 100),  lapply(10:1, function(i)circle(cxy=sample(1:100,2),r=5+sample(5:50,1), fill=rrgb()) ))

Using a sapply on polylines

svgR( wh=c(600, 100),  polyline(points=sapply(4*pi*(0:5)/5,function(i)c(100,50)+50*c(cos(i),sin(i))), fill='none', stroke='red'))

Using a sapply on polylines

svgR( wh=c(600, 100),  polyline(points=sapply(1:41,function(i)50*(c(i,1)-i%%2)), fill='red'))

Rectangles

svgR( wh=c(600, 100),  lapply(1:15, function(i)rect(xy=i*c(80,0), wh=sample(20:80,2), fill=rrgb())))
svgR( wh=c(600, 100),  lapply(1:256, function(i)line(xy1=c(255,0), xy2=c(i*2,100), stroke=as.rgb(255-i,128,255-i))))
svgR( wh=c(600, 100),  lapply(1:100, function(i)line(x12=sample(0:600,2), y12=c(0,100), stroke=rrgb())))
svgR( wh=c(600, 100),  lapply(1:10, function(i) rect(cxy=c(100+i*10,50), wh=c(80+i*10,40), fill='none', stroke=rrgb())))
svgR( wh=c(600, 200),  lapply(1:45, function(i) ellipse(cxy=c(100,100), rxy=c(80,40), transform=list(rotate= c(i*4, 100,100)), fill='none', stroke=rrgb())))
svgR( wh=c(600, 200),  lapply(1:15, function(i) rect(cxy=c(100,100), wh=c(80,80), transform=list(rotate= c(i*8, 100,100)), fill='none', stroke=rrgb())))
svgR(wh=c(600,200), lapply(0:10,  function(i)circle(cxy=c(350,100),r=30,fill=rrgb(),opacity=.3, transform=list(rotate= c(-i*18,300,100)))))
svgR( wh=c(600, 200), lapply( 1:5,  function(x)polygon(points= 5*(c(0,0,-4,6, 4, 6)+ x*c(4,6)), fill='brown')))
svgR( wh=c(600, 100), line( xy1=c(0,50), xy2=c(600,50), stroke.width=20, stroke.dasharray=8, stroke.dashoffset=16, stroke="lightblue", animate(attributeName="stroke.dashoffset", from=16, to=0 , dur=0.5, repeatCount="indefinite")))
WH<-c(15,18)
svgR(wh=WH,
     circle(cxy=c(5,9), r=3, stroke.width=3, fill="none", stroke="#DDDDDD" ),
     circle(cxy=c(10,9), r=3, stroke.width=3, fill="none", stroke="#DDDDDD" )
)





Setting Up

Two Ways To Install

1. Using DevTools

One to install is to use the devtools to install svgR from the github repository:

From withing an R session

A. Install the devtools package

install.packages("devtools")
  1. Use devtools to install from github

    library(devtools)
    install_github("mslegrand/svgR")

2. Using Drat

Another way to install is to use the drat package manager.

From within R session

A.

install.packages("drat", repos="http://cran.rstudio.com")
  1. library(drat)
    drat:::add("mslegrand")
    install.packages("svgR") #for R only.

One argument in favour of drat is the abiltily to seemlessly install/update, using the usual install.packages, update.packages commands. The only caveat being that to make it seemless, one should add the .Rprofile the line

drat:::add(mslegrand)

See Setting up with drat

To render in an RPres or R Markdown document

  • Include prior to any calls to svgR include a block with
```{r, echo=FALSE} library(svgR) ```

To render

either

  • a simple call to svgR inside a block with results=‘asis’
```{r, echo=TRUE, results=“asis”} svgR( wh=c(600, 100), circle(cxy=c(200,50),r=50, fill=“red”) ) ```

or

  • a call to svgR to create an svg markup object and echo back within a block with results=‘asis’
```{r, echo=TRUE, results=“asis”} svgM<-svgR( wh=c(600, 100), circle(cxy=c(200,50),r=50, fill=“red”) ) svgM ```

Note Note the mustache is three backticks

To Rendered in an RStudio Shiny-server

  • Require svgR
  • In the ui, set the svg output to be an htmlOutput
  • In the server,
  • convert the svg markup to text using as.character
  • set the corresponding output object to renderUI of an HTML frame of the svg markp text.

A short example is:

require(shiny)
svgMarkup<-svgR( circle(cxy=c(200,100),r=50, fill='red') )
app <- list(
  ui = bootstrapPage(  htmlOutput("svg") ) ,
  server = function(input, output) {
    output$svg<-renderUI( { HTML(
      as.character(svgMarkup)) } ) } 
) 
runApp(app) 

Again Note we convert the svgMarkup to a character vector before rendering.






Overview

path<-"./Compounds/"
source(paste0(path,"svgR_logo.R"))
source(paste0(path,"svgRTree.R"))

The svgR package provides a DSL to create SVG markup from R. It is similar to grid graphics, in the sense that it is a low level api upon which higher level graphics constructs can be built.

The best way to learn is by example, so here is a short example.

A Short Example

We start by first considering the short example show below together with the rendered result.

svgR( wh=c(100,50),
  text( xy=c(20,10), "line 1", 
    tspan(dy=10, "line2 ")
  )
)
line 1 line2

What is hidden in the above code listing is the usual 3 backtics and the options echo=FALSE and results=“asis” For completeness, we show these below in red.

```{r, echo=FALSE, results=“asis”} svgR( wh=c(100,50), text( xy=c(20,10), “line 1”, tspan(dy=10, “line2”) ) ) ```

Next we break apart the code into it’s basic components, starting with the function call svgR

svgR( wh=c(100,50), text( xy=c(20,10), “line 1”, tspan(dy=10, “line2”) ) )

It has two arguments, a named parameter wh=c(100,50) and an unnamed parameter which is the function called text.

The named parameter wh (shown in orange below) is an attribute specifying the width and height of the viewport, the region on the screen where the image is shown.

svgR( wh=c(100,50), text( xy=c(20,10), “line 1”, tspan(dy=10, “line2”) ) )

The text function (shown in blue) is one of 80 functions that can be called within the scope of svgR to render an SVG markup. These functions are called svgR elements (or simply elements) and each svgR element creates and SVG node whose tag name is the same as the function name.

svgR( wh=c(100,50), text( xy=c(20,10), “line 1”, tspan(dy=10, “line2”) ) )

This text element has 3 components: an xy attribute (in orange), text content (in green), and a child element named tspan (in blue).

svgR( wh=c(100,50), text( xy=c(20,10), “line 1”, tspan( dy=10, “line2” ) ) )

The tspan element contains an dy attribute (orange) and text content (green)

svgR( wh=c(100,50), text( xy=c(20,10), “line 1”, tspan( dy=10, “line2” ) ) )

The generated SVG markup is

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
  width="100" height="50">
  <text x="20" y="10">
    line 1
    <tspan dy="10">tspan line 2</tspan>
  </text>
</svg>

Note: The SVG root node was created by the svgR call.

Taxonomy

In the previous section we walked though an example where we encountered

  • elements (text, tspan),
  • attributes (xy, wh, dy ) and
  • text content (line1, line2).

However, this is only small fragment of the available elements and attributes.

The elements (functions within an svgR call) can be broadly broken into several categories:

elements graphical containers animators modifiers other shape related text related filters gradients masks markers

Graphical Elements

The Graphical Elements, Text and Shapes, are the building blocks of an SVG image. Hence it is fundamental to understand their usage. This means that one needs to understand the attributes that can be applied to Text and Shapes. Some of the relevant prominent attributes are

attribute description
xy,wh,cxy, … intial positioning
transform for repositioning
fill to specify how to fill the interior
filter modify the image
mask to hide a portion
clip.path to trim a portion

Containers

To apply the same value of an attribute to a collection of shapes and text, it is customary to group them together using a container element such as either a g element or the svg element. The distinction between g and svg lies in which attributes the can take.

  • svg can has coordinate attributes xy, wh but not a transform attribute
  • g has the transform attribute, but no coordinates

Other containers elements are defs, symbol.
However, these are not displayed directly, but are used by reference.

Animators

Animation can be either instaneous or transitional, i.e. occuring over period of time.

The Prominent Animation Elements are

element description
Set Instaneous change
Animate Continous change
Animate Transform Continous Change
Animate Motion Continous Chagne

Animators are often given as a child of the element to be animated, i.e., an unnamed argument of the shape/text element.

Modifiers: Gradients, Filters, Masks, Markers

These Modifiers modify the appearence graphical element. More specifically,

  • Gradients: provide a continous flow of the fill color from one spectrum to another
  • Masks: hides the portion of an image given by the mask
  • Markers: decorates the endpoints of a line, allow us to create arrow heads, tails, etc.
  • Filters: are containers for filter primitives, used to provide, lighting, dropshawdow, and mamy special effects.

These elements are specified by the value of a named parameter of the element they are to influence. For example:

element parameter usage
Gradients fill=linearGradient(…) or fill=radialGradient(…)
Filter filter=filter(…)
Mask mask=mask(…)
marker marker.end=marker(…), marker.start=marker(…), marker.mid=marker(…)

Structure of This Document

As stated before, the graphical elements are the building blocks of any SVG image. However, in order to use those building blocks, we need to know where to put them and how to glue them together.

The sections

section description
Coordinates, Viewports and Containers Placement and grouping of shapes
Transform Attribute Modifying the size/placement/orientation of shapes
Shapes, Paths, markers basic shapes
Text Displaying shapes
The Fill Attribute Gradients, Patterns
The Filter Attribute Gaussian Blur, Filter Coords
Manipulating Colors Filters that influence color
Combining Filter Primitives Using feComposite, feBlend
Drop Shadows Adding shadows behind shapes/text
Textures Different textures such as marbling, wood, wtucco, …
Lighting Types of lighting
Shiny Bevels Examples using filters and compounds
Shape Shifting Clipping, masking, morphology, displacing
Animating Overview
Discret Animation Visibility, opacity, positioning, stroking
Continous Animation Filters, fill, shapes, lighting
Continous Transform Animating transforms: rotate, scale, skew
Path Animation Paths, key-splines, key-times

Each section contains multiple short examples of each topic. Enjoy!





Coordinates, Viewports, and Containers

This section discusses some of the basics of coordinates systems, viewports and viewboxes.

Viewports and Viewboxes

The viewport is the viewing area through which image is rendered. Essentially, it is a rectangular window on the page.

A viewBox is the area of the graphics surface which is to be placed inside the viewport. When not specified, the viewbox

alt text

alt text

Specifying the Viewport and the Viewbox

When the svgR function is called, it generates an SVG tree of nodes, with to top most node being an svg element.

The dimensions of the viewPort is determined by the wh attributes supplied in the call to svgR. Thus a larger wh produces a larger display area on the screen.

The dimensions of our viewPort are given by the wh in the svgR call, for example seting wh=c(100,50) we have

Here the c(100,50) are measured in “pixels” of the “user space”. Other units available are: em, ex, px, pt, pc, cm, mm, in, and percentages.

For example

svgR(wh=c("2in","1in"), 
     use(filter=filter( 
     filterUnits="userSpaceOnUse",
     feFlood(flood.color='lightblue') )) #flood svg with lightBlue
)

produces a 2 inch by 1 inch image on the screen.

Coordinates are layed out with x increasing to the right, y increasing down.

WH<-c(300,300)
svgR(wh=WH, 
  graphPaper(WH, dxy=c(50,50), labels=TRUE)
)
0 50 100 150 200 250 300 0 50 100 150 200 250 300

Here the Viewport is 400 by 300 pixels, which was determined by the attribute assignment wh=c(300,300). Additionally, there is an implict viewBox=c(0,0,400,300). When not specified, the viewBox is assumed to have a xy=c(0,0) and a width and height equal to it’s corresponding viewPort (wh=c(300,300 in this case).

Note: We are using our graphPaper utilitiy, which starts drawing lines and coordinates at c(0,0) and continues to WH in steps of (50,50).

In order to see better what happens when using a different viewport,modify the above example by adding a circle in the left-hand corner and some text.

WH<-c(300,300)
svgR(wh=WH, 
  graphPaper(WH, dxy=c(50,50), labels=TRUE),
  circle(cxy=c(0,0),r=100, fill="pink", stroke='red'),
  text('Big  Data', xy=c(-40, 50), font.size=20),
  text('Small Data', xy=c(50, 50), font.size=10)
)
0 50 100 150 200 250 300 0 50 100 150 200 250 300 Big Data Small Data

Again the viewPort is 400 by 300 pixels and the viewBox is also 400 by 300 with the upper left corner at c(0,0).

Nexe we reset the viewBox. The region viewed is changes, but the size of the display area (viewPort) remains the same.

WH<-c(300,300)
svgR(wh=WH, viewBox=c(-WH,2*WH),
  use(filter=filter( filterUnits="userSpaceOnUse", feFlood(flood.color='lightblue') )),
  graphPaper(WH, dxy=c(50,50), labels=TRUE),
  circle(cxy=c(0,0),r=100, fill="pink", stroke='red'),
  text('Big  Data', xy=c(-40, 50), font.size=20),
  text('Small Data', xy=c(50, 50), font.size=10)
)
0 50 100 150 200 250 300 0 50 100 150 200 250 300 Big Data Small Data

Now we can see the entire circle (and the grid). Also, since the viewBox has become now larger, the image appears smaller. This is because the image in the viewBox is compressed to fit inside the viewPort.

And here is another more example, this time the image size increases.

WH<-c(300,300)
svgR(wh=WH, viewBox=c(.1*WH,.5*WH),
  use(filter=filter( filterUnits="userSpaceOnUse", feFlood(flood.color='lightblue') )),
  graphPaper(WH, dxy=c(50,50), labels=TRUE),
  circle(cxy=c(0,0),r=100, fill="pink", stroke='red'),
  text('Big  Data', xy=c(-40, 50), font.size=20),
  text('Small Data', xy=c(50, 50), font.size=10)
)
0 50 100 150 200 250 300 0 50 100 150 200 250 300 Big Data Small Data

Summarizing

  • Decreasing the view box zooms out
  • Conversley inceasing the viewport zooms in.

Nesting svgs

Svg’s can be nested: The simplest example being a single child svg, (the root svg is implict in svgR). Here is such an example:

WH<-c(400,300)
WH2<-WH/2
id=autoId()
svgR(wh=WH, 
     use(filter=filter( filterUnits="userSpaceOnUse", feFlood(flood.color='lightblue') )),
     g( id=id,
        graphPaper(WH, dxy=c(50,50), labels=TRUE),
        circle(cxy=c(0,0),r=r, fill="pink")
     ),
     svg(xy=WH/4, wh=WH/2, 
           use(filter=filter( filterUnits="userSpaceOnUse", feFlood(flood.color='yellow') )),
           use(xlink.href=paste0("#",id))
    )
)
0 50 100 150 200 250 300 350 400 0 50 100 150 200 250 300

Note: The viewBox of the child svg is c(0,0,200,150). When not specified the viewBox defaults to:

  • an origin c(0,0)
  • with dimensions identical to the corresponding viewPort

In this example, the parent svg was flooded withblue, and the child svg with red. The parent contains the group consisting of some graphPaper and a circle. The child svg uses the same graphPaper and circle by using a reference link. The only difference between the child and parent are the flood colors and the viewPorts. In both cases, the circle is clipped so that only 1/4 of it si visible.

Summarizing:

  • svgs contain their own coordinate system (viewBox)
  • svgs clip the the source to their own viewPort

Note We specified the location of the child with the attribute xy=c(x,y). However, for the svg root element, the xy coordinates will always be xy=c(0,0).

Finally we go a step further and provide a viewbox when embeding a child svg.

WH<-c(800,300)
id=autoId()
XY2<-c(600,40)
WH2<-c(180,200)
svgR(wh=WH, 
  g( id=id,
    graphPaper(WH, dxy=c(50,50), labels=TRUE),
    lapply(1:15, function(i){
      text("Running R Really Rocks", xy=i*c(20,20), stroke=rrgb(), font.size=10)
    })
  ),
  rect(xy=XY2, wh=WH2, stroke='blue', stroke.width=2, fill='#FFDDDD'),
  svg(xy=XY2, wh=WH2, viewBox=c(.1*WH,.1*WH),
      use(xlink.href=paste0('#',id))
  )
)
0 50 100 150 200 250 300 350 400 450 500 550 600 650 700 750 800 0 50 100 150 200 250 300 Running R Really Rocks Running R Really Rocks Running R Really Rocks Running R Really Rocks Running R Really Rocks Running R Really Rocks Running R Really Rocks Running R Really Rocks Running R Really Rocks Running R Really Rocks Running R Really Rocks Running R Really Rocks Running R Really Rocks Running R Really Rocks Running R Really Rocks





The Transform Attribute

Transform operations

The transform attribute allows for multiple linear operations to be applied prior to rendering.

SVG supports the composition 5 types transforms:

  • translate: Translates the orgin
  • rotate: Rotates through a given angle about a given point
  • scale:
  • skew
  • matrix

We discus translate, rotate, scale, skew in this section and leave matrix to the next section.

The transform attribute is assigned a value that consists of a named list. The name list entry name provides the type of operation, and list entry value give the amount.

For example transform=list(translate=c(20,30),scale=c(1,2)) translates and then scales. The transorm is sensitive to the order of operations, that is, transform operations are seldom commute.

In the remainging of this section we give some examples, but first we build a user defined element to illustrating those transforms.

Remember A transform is not an element, it is an attribute, like xy.

The Tale of Two Circles, a Square and Some Lines

The cirCirSqr element

To illustrate the effects of transform we first create a custom element, for our illustration. It will consist of an ensemble of

  • a border cosnisting of dashed lines , colored red on the top, left, colored green on the bottom right
  • blue dash lines intersecting at the center
  • a semi-transparent blue rectangle with it’s upper left corner in the center
  • a semi-transparent red circle centered on the upper left corner of the blue rectangle
  • a semi-transparent gree circle centered on the upper right corner of the blue rectangle
# A function to tell the tale of two circles and a rectangle
cirCirSqr %<c-% function(...){
  attrs<-list(...)
  stopifnot("cxy" %in% names(attrs))
  centerPt<-attrs[['cxy']]
  attrs$cxy<-NULL
  wh<-c(100,50) #sets the rectangle dimension
  pt1<-centerPt+c(.5,0)*wh
  pt2<-centerPt+c(-.5,0)*wh
  g(
    attrs,
    line(xy1=c(0,0), xy2=c(WH[1],0), stroke='red', stroke.dasharray=10, stroke.width=4),
    line(xy1=c(0,0), xy2=c(0,WH[2]), stroke='red', stroke.dasharray=10, stroke.width=4 ),
    line(xy1=WH, xy2=c(WH[1],0), stroke='green', stroke.dasharray=15, stroke.width=4),
    line(xy1=WH, xy2=c(0,WH[2]), stroke='green', stroke.dasharray=15, stroke.width=4 ),
    line(xy1=c(0,WH[2]/2), xy2=c(WH[1],WH[2]/2), stroke='blue', stroke.dasharray=10),
    line(xy1=c(WH[1]/2,0), xy2=c(WH[1]/2,WH[2]), stroke='blue', stroke.dasharray=10 ),
    rect(xy=pt2, wh=wh,  stroke ='black', fill='blue',  opacity=.3),
    circle(cxy=pt1, r=wh[2], stroke="black", fill="red",   opacity=.3),
    circle(cxy=pt2, r=wh[2], stroke="black", fill="green", opacity=.3),
    text(paste("(",centerPt[1],",",centerPt[2],")"), cxy=centerPt, font.size=12)
  )
}

And also for convenience we create a function for showing a background of graphPaper and a title.

# Utility to cover my back
myBack %<c-% function(WH, txt){
  list(wh=WH, #sets the viewPort dimension
      text(txt, xy=c(20,25), font.size=18),
      graphPaper(wh=WH, dxy=c(50,50), labels=TRUE),
      line(xy1=c(0,WH[2]/2), xy2=c(WH[1],WH[2]/2), stroke='blue' ),
      line(xy1=c(WH[1]/2,0), xy2=c(WH[1]/2,WH[2]), stroke='blue' )
  )
} 

Ok, now let’s see what this looks like

Without Any Transform

WH=c(800, 400) # window rect
svgR( 
  myBack(WH, "Without any Transform"),
  cirCirSqr(cxy=WH/2)
)
Without any Transform 0 50 100 150 200 250 300 350 400 450 500 550 600 650 700 750 800 0 50 100 150 200 250 300 350 400 ( 400 , 200 )

Translating

We want:

  • to transform the circles, square, lines in cirCirSqr
  • not to transform the background in myBack

This suggests grouping the circCirSqr with the transform.

translate=c(100,40)

WH=c(800, 400) # window rect
svgR(
  myBack( WH, "With Transform translate=c(100,40)" ),
  cirCirSqr(cxy=WH/2, transform=list(translate=c(100,40) ) ) 
)
With Transform translate=c(100,40) 0 50 100 150 200 250 300 350 400 450 500 550 600 650 700 750 800 0 50 100 150 200 250 300 350 400 ( 400 , 200 )

translate=c(-100,-40)

WH=c(800, 400) # window rect
svgR( 
  myBack(WH, "With Transform translate=c(-100,-40)"),
  cirCirSqr(cxy=WH/2, transform=list(translate=c(-100,-40))) 
)
With Transform translate=c(-100,-40) 0 50 100 150 200 250 300 350 400 450 500 550 600 650 700 750 800 0 50 100 150 200 250 300 350 400 ( 400 , 200 )

Scaling

Scaling can be applied horizontally, vertically, or both. A factor of 1 leaves the figure unscaled. A facor greater than 1 magnifies and factor less that 1 reduces. In our examples, we use values less than 1, so that the entire image can be seen.

Scaling about the Origin

We begin by presenting two examples:

  • The first scales by a factor of \(\frac{1}{2}\) horizontally
WH=c(800, 400) # window rect
svgR( 
  myBack(WH, "With Transform scale=.5"),
  cirCirSqr(cxy=WH/2, transform=list(scale=c(.5,1))) 
)
With Transform scale=.5 0 50 100 150 200 250 300 350 400 450 500 550 600 650 700 750 800 0 50 100 150 200 250 300 350 400 ( 400 , 200 )
  • The seconds scales in both directions by a factor of \(\frac{1}{2}\)
WH=c(800, 400) # window rect
svgR( 
  myBack(WH, "With Transform scale=.5"),
  cirCirSqr(cxy=WH/2, transform=list(scale=.5) ) 
)
With Transform scale=.5 0 50 100 150 200 250 300 350 400 450 500 550 600 650 700 750 800 0 50 100 150 200 250 300 350 400 ( 400 , 200 )

Note: The dimension of our figure have been reduce by \(\frac{1}{2}\) and the distance to origin has been halved. This is because scaling is scales using the origin as it’s fixed point. (Other points change)

Scaling about a Given Fixed Point

To scale about a given point pt, we move pt p to the center, scale and then move back. More precicely, if p is the fixed point and \(\alpha\) the scale factor, then

  1. translate by -p
  2. scale by \(\alpha\)
  3. translate back by \(\frac{2p}{\alpha} -p\)

For example:

WH=c(800, 400) # window rect
alpha<-.3
svgR( 
  myBack(WH, "Transform: scale=0.3 with center fixed"),
  cirCirSqr(cxy=WH/2, transform=list(translate=-0.5*WH, scale=alpha, translate=WH*(1/alpha -1/2)))
)
Transform: scale=0.3 with center fixed 0 50 100 150 200 250 300 350 400 450 500 550 600 650 700 750 800 0 50 100 150 200 250 300 350 400 ( 400 , 200 )

Above we scale with a fixed center, below we start with an image centered at c(.25,.25)WH and scale with a value of .3, with a fixed point of c(.25,.25)WH.

WH=c(800, 400) # window rect
alpha<-.3
pt<-c(.25,.25)
svgR( 
  myBack(WH, "With Transform scale=0.3 and fixed point c(.25,.25)"),
  cirCirSqr(cxy=pt*WH, transform=list(translate=-pt*WH, scale=alpha, 
                                  translate=WH*(2*pt/alpha - pt)))
)
With Transform scale=0.3 and fixed point c(.25,.25) 0 50 100 150 200 250 300 350 400 450 500 550 600 650 700 750 800 0 50 100 150 200 250 300 350 400 ( 200 , 100 )

Reflections

Reflections are a special case of scaling. Just use a negative as a scale factor. We give 2 examples:

Vertical Reflection though the center,
WH=c(800, 400) # window rect
alpha<-c(1,-1)
pt<-c(.5,.5)
svgR( 
  myBack(WH, "Reflection scale=c(1,-1) and fixed point=c(0,0)"),
  cirCirSqr(cxy=pt*WH, transform=list(translate=-pt*WH, scale=alpha, 
                                  translate=WH*(2*pt/alpha - pt)))
)
Reflection scale=c(1,-1) and fixed point=c(0,0) 0 50 100 150 200 250 300 350 400 450 500 550 600 650 700 750 800 0 50 100 150 200 250 300 350 400 ( 400 , 200 )

Rotations

Like scaling, rotations default to having the origin be the fixed point.

Rotation about the Origin:

This is simple rotate by 10 degrees (For those not familiar with degrees, thats \(\frac{pi}{18}\))

WH=c(800, 400) # window rect
degrees<-10
svgR(  
  myBack(WH, paste0("With Transform rotate=",degrees)),
  g(cirCirSqr(cxy=WH/2),
     transform=list(rotate=degrees)
  )
) 
With Transform rotate=10 0 50 100 150 200 250 300 350 400 450 500 550 600 650 700 750 800 0 50 100 150 200 250 300 350 400 ( 400 , 200 )

Observer: Rotation is about the origin.

Rotation about a Fixed Point

transform=list(rotate=c(5,400,200))

Althought we can try an approach similar to what was done for scaling, we fortunately have an easy alternative, simple supply the position of the fixed point as the second and third coordinates:

Here we rotated about the center of our viewPort.

WH=c(800, 400) # window rect
svgR( 
  myBack(WH, "With Transform rotate=c(5, WH/2)"),
  g(cirCirSqr(cxy=WH/2),
     transform=list(rotate=c(5, WH/2))
  )
)
With Transform rotate=c(5, WH/2) 0 50 100 150 200 250 300 350 400 450 500 550 600 650 700 750 800 0 50 100 150 200 250 300 350 400 ( 400 , 200 )

Skewing

There are two skews available: skewX, skewY. They are illustrated as following:

skewX=30

SkewX (a shear stress) by an angle of \(\frac{\pi}{6}\)

WH=c(800, 400) # window rect
svgR( 
  myBack(WH, "With Transform skewX=30"),
  g(cirCirSqr(cxy=WH/2),
     transform=list(skewX=30)
  )
)
With Transform skewX=30 0 50 100 150 200 250 300 350 400 450 500 550 600 650 700 750 800 0 50 100 150 200 250 300 350 400 ( 400 , 200 )

Now skewY (a shear stress) by an angle of \(\frac{\pi}{36}\)

WH=c(800, 400) # window rect
svgR( 
  myBack(WH, "With Transform skewY=5"),
  g(cirCirSqr(cxy=WH/2),
     transform=list(skewY=5)
  )
)
With Transform skewY=5 0 50 100 150 200 250 300 350 400 450 500 550 600 650 700 750 800 0 50 100 150 200 250 300 350 400 ( 400 , 200 )

Combining Transforations

We have already seen an example of combining transformations. However we a few more just to demonstrate order matters!

WH=c(800, 400) # window rect
svgR( 
  myBack(WH, "Order Matters: rotate, translate"),
  g(cirCirSqr(cxy=WH/2),
     transform=list(rotate=c(45,WH/2), translate=c(100,0))
  )
)
Order Matters: rotate, translate 0 50 100 150 200 250 300 350 400 450 500 550 600 650 700 750 800 0 50 100 150 200 250 300 350 400 ( 400 , 200 )
WH=c(800, 400) # window rect
svgR( 
  myBack(WH, "Order Matters: translate, rotate"),
  g(cirCirSqr(cxy=WH/2),
     transform=list(translate=c(100,0), rotate=c(45,WH/2))
  )
)
Order Matters: translate, rotate 0 50 100 150 200 250 300 350 400 450 500 550 600 650 700 750 800 0 50 100 150 200 250 300 350 400 ( 400 , 200 )

Tranform Attribute using Matrix as a Value

In the previous section, we discussed using the transform with the operation types: tranlate, scale, rotate, and skew. In this section we discuss using matrix opertion.

The advantage to using matrix is two fold

  • we can combine several operations into one
  • we need only know the desired effect on 3 points to get our specification (less thinking involved)

But first some background

Linear Algebra

A basic result in linear algebra is any function mapping between vector spaces that satisifies \[T(\alpha \vec v_1 + \alpha \vec v_2)=\alpha T ( \vec v_1 + \vec v_2 ) \] can be represented by a matrix. Considering points (x,y) on the screen as elements in a 2 dimensional vector space we can easlily see that (rotations, scaling, fit the criteria. However translation does not. But all is not lost. We can envision that our 2-d space is just the plane in 3-space defined by z=1. With trick, translation can be created (on that plane) using a shear stress, which does satisfy the above requirement.

Essentially the trick is sending (x,y) to (x,y,1). Then the effect on points using rotation, scaling, translation, and skewing can be constructed as simple matrix muliplication of the form: \[ \begin{pmatrix} x_{new} \\ y_{new} \\ 1 \end{pmatrix} = \begin{pmatrix} a & c & e \\ b & d & f \\ 0 & 0 & 1\end{pmatrix} \begin{pmatrix} x_{old} \\ y_{old} \\ 1 \end{pmatrix} \]

For example, translation is performed by \[ \begin{pmatrix} x_{new} \\ y_{new} \\ 1 \end{pmatrix} = \begin{pmatrix} 1 & 0 & h \\ 0 & 1 & k \\ 0 & 0 & 1\end{pmatrix} \begin{pmatrix} x_{old} \\ y_{old} \\ 1 \end{pmatrix} \]

and rotation (about the origin) is given by \[ \begin{pmatrix} x_{new} \\ y_{new} \\ 1 \end{pmatrix} = \begin{pmatrix} cos(\theta) & sin(\theta) & 0 \\ -sin(\theta) & cos(\theta) & 0 \\ 0 & 0 & 1\end{pmatrix} \begin{pmatrix} x_{old} \\ y_{old} \\ 1 \end{pmatrix} \]

and scaling by \[ \begin{pmatrix} x_{new} \\ y_{new} \\ 1 \end{pmatrix} = \begin{pmatrix} S_x & 0 & 0 \\ 0 & S_y & 0 \\ 0 & 0 & 1\end{pmatrix} \begin{pmatrix} x_{old} \\ y_{old} \\ 1 \end{pmatrix} \]

Scaling with a Given Fixed Point

Now the transformation is always of the form \[ \begin{pmatrix} x_{new} \\ y_{new} \\ 1 \end{pmatrix} = \begin{pmatrix} a & c & e \\ b & d & f \\ 0 & 0 & 1\end{pmatrix} \begin{pmatrix} x_{old} \\ y_{old} \\ 1 \end{pmatrix} \] with the last column always being \[ \begin{pmatrix} 0\\ 0 \\ 1 \end{pmatrix} \]

Thus in practice, only the \(a,b,c,d,e,f\) need be supplied. This is done either by

transformation=list(matrix=matrix(c(a,b,c,d,e,f)2,3))

or

transformation=list(matrix=c(a,b,c,d,e,f))

Now the transformation matrix \(A\) is determined by the values \(Y_i=A[X_i]\) on any three collinear points \(X_i\). In particular if X and Y the matrices whose columns are the \(X_i\) and \(Y_i\), then \(X\) is non-singular and \(Y= A X\). From this we solve for A.

We given two examples, the first being scaling in about a fixed point.

Let \(\alpha=(\alpha_x, \alpha_y)\) be scaling factors in the x and y direction. Let \(p=(p_x,p_y)\) be the fixed point. Then we have \[ \begin{pmatrix} p_x+1 \\ p_y \end{pmatrix} \mapsto \begin{pmatrix} p_x+\alpha_x \\ p_y \end{pmatrix}, \qquad \begin{pmatrix} p_x \\ p_y+1 \end{pmatrix} \mapsto \begin{pmatrix} p_x \\ p_y+\alpha_y \end{pmatrix}, \qquad \begin{pmatrix} p_x \\ p_y \end{pmatrix} \mapsto \begin{pmatrix} p_x \\ p_y \end{pmatrix} \] Appending 1’s and placing into matrix form we have \[ \begin{pmatrix} p_x+1 & p_x & 0 \\ p_y & p_y+1 & 0 \\ 1 & 1 & 1 \end{pmatrix} \mapsto \begin{pmatrix} p_x+\alpha_x & p_x & 0 \\ p_y & p_y+\alpha_y & 0 \\ 1 & 1 & 1 \end{pmatrix} \] So \[ \begin{pmatrix} p_x+\alpha_x & p_x & 0 \\ p_y & p_y+\alpha_y & 0 \\ 1 & 1 & 1 \end{pmatrix} = \begin{pmatrix} a & c & e \\ b & d & f \\ 0 & 0 & 1 \end{pmatrix} \begin{pmatrix} p_x+1 & p_x & 0 \\ p_y & p_y+1 & 0 \\ 1 & 1 & 1 \end{pmatrix} \] Taking transposes \[ \begin{pmatrix} p_x+\alpha_x & p_y & 1 \\ p_x & p_y+\alpha_y & 1 \\ 0 & 0 & 1 \end{pmatrix} = \begin{pmatrix} p_x+1 & p_y & 1 \\ p_x & p_y+1 & 1 \\ 0 & 0 & 1 \end{pmatrix} \begin{pmatrix} a & b & 0 \\ c & d & 0 \\ e & f & 1 \end{pmatrix} \]

Setting up the augmented matrix \[ \left[\begin{array}{rrr|rrr} p_x+\alpha_x & p_y & 1 & p_x+1 & p_y & 1 \\ p_x & p_y+\alpha_y & 1 & p_x & p_y+1 & 1 \\ 0 & 0 & 1 & 0 & 0 & 1 \end{array}\right] \]

This reduces to \[ \left[\begin{array}{rrr|rrr} \alpha_x & 0 & 0 & 1 & 0 & 0 \\ 0 & \alpha_y & 0 & 0 & 1 & 0 \\ p_x(1-\alpha_x) & P_y(1-\alpha_y) & 1 & 0 & 0 & 1 \end{array}\right] \]

So the transformation matrix value is \[ \begin{pmatrix} a & c & e \\ b & d & f \end{pmatrix} = \begin{pmatrix} \alpha_x & 0 & p_x(1-\alpha_x) \\ 0 & \alpha_y & p_y(1-\alpha_y) \end{pmatrix} \] or

 c(alpha[1], 0 , 0, alpha[2], p[1]*(1-alpha[1]), p[2]*(1-alpha[2]) )
Scaling with the center as a fixed point using matrix,
inPlaceTrans<-function(fixedPoint, alpha){
  c( alpha[1],0,0, alpha[2], 
    (1-alpha[1])*fixedPoint[1],
    (1-alpha[2])*fixedPoint[2] )
}

svgR( 
  myBack(WH, "Scaling with the center as fixed point using matrix"),
  cirCirSqr(cxy=WH/2, 
      transform=list( matrix=inPlaceTrans( WH/2, c(0.5,1.5) ) )
  )
)
Scaling with the center as fixed point using matrix 0 50 100 150 200 250 300 350 400 450 500 550 600 650 700 750 800 0 50 100 150 200 250 300 350 400 ( 400 , 200 )

Rotation with the Center as a Fixed Point

For our second example, we peform a rotation of \(\frac{\pi}{4}\) around the point c(400,200). The difference here is that this time we use R to solve the matrix equation.

We choose our points x_i to be equaly space around the fixed point with radius 100. The points and mapping are: \[ \begin{pmatrix} 400+100 cos(\frac{2 \pi k}{3}) \\ 200+ 100 sin(\frac{2 \pi k}{3}) \\ 1 \end{pmatrix} \mapsto \begin{pmatrix} 400+100 cos(\frac{2 \pi k}{3} + \frac{\pi}{4}) \\ 200+ 100 sin(\frac{2 \pi k}{3}+ \frac{\pi}{4}) \\ 1 \end{pmatrix} \]

WH<-c(800,400)
theta<-pi/4 #The angle we will rotate about
r<-100
eta<-(1:3)*(2*pi)/3
X<-rbind( r*cos(eta), r*sin(eta), rep(1,3) ) + c(WH/2,0)
eta<-(1:3)*(2*pi)/3 +theta 
Y<-rbind( r*cos(eta), r*sin(eta), rep(1,3) ) + c(WH/2,0)
solve(t(X),t(Y))->A
as.numeric(t(A[,1:2]))->A

3 Rotation though the center using matrix,

svgR( 
  myBack(WH, "Rotation though the center using matrix"),
  cirCirSqr(cxy=WH/2 , transform=list( matrix= A[1:6] ) )
)
Rotation though the center using matrix 0 50 100 150 200 250 300 350 400 450 500 550 600 650 700 750 800 0 50 100 150 200 250 300 350 400 ( 400 , 200 )

Some different ways to represent the transform sequence.

To keep a clear conscience, we provide three alternatives for a value representing a transform sequence.

Consider the tranlate, rotate scale sequence.

Representing the transform value as a list

The value is a named list with name defining the operations, whose values determine the amount of that operation. For example:

list(translate=c(200,100) , rotate=-30, scale=c(1,2))

we have

WH=c(600, 200) # window rect
wh=c(80,30) # small rect
svgR( 
  wh=WH, 
  graphPaper(wh=WH, dxy=c(50,50),TRUE),
  g(
      rect( xy=pt, wh=c(60,30), stroke ='black', fill='blue', opacity=.3),
      circle(cxy=pt, r=15, stroke="black", fill="red", opacity=.3),
      transform=list(translate=c(200,100) , rotate=-30, scale=c(1,2))
  )
 )
0 50 100 150 200 250 300 350 400 450 500 550 600 0 50 100 150 200
Representing the transform value as a vector of operations.

The operations defined by the helper functions translate, rotate, and scale. For example:

c(translate(200,100) , rotate(-30), scale(1,2))

We have

WH=c(600, 200) # window rect
wh=c(80,30) # small rect
svgR( 
  wh=WH, 
  graphPaper(wh=WH, dxy=c(50,50),TRUE),
  g(
      rect( xy=pt, wh=c(60,30), stroke ='black', fill='blue', opacity=.3),
      circle(cxy=pt, r=15, stroke="black", fill="red", opacity=.3),
      transform=c(translate(200,100) , rotate(-30), scale(1,2))
  )
 )
0 50 100 150 200 250 300 350 400 450 500 550 600 0 50 100 150 200

The helper functions, translate, rotate, scale are included as part of the svgR api.

Representing the transform value by a single character string

And finally representing the value as a single character string for example:

"translate(200,100) rotate(-30) scale(1,2)"

we have

WH=c(600, 200) # window rect
wh=c(30,30) # small rect
pt=c(0,0)
svgR( 
  wh=WH, 
  graphPaper(wh=WH, dxy=c(50,50), labels=TRUE),
  g(
      rect( xy=pt, wh=c(60,30), stroke ='black', fill='blue', opacity=.3),
      circle(cxy=pt, r=15, stroke="black", fill="red", opacity=.3),
      transform="translate(200,100) rotate(-30) scale(1,2)"
  )
 )
0 50 100 150 200 250 300 350 400 450 500 550 600 0 50 100 150 200





Shapes, Paths, Markers

In this section we discuss shapes. Shapes are circles, ellipses, lines, paths, polygons, polylines, rectangles.

The Line

We begin with the most basic of shapes, a simple line segment from a point xy1 to xy2

WH<-c(600, 100) # window rect
svgR( wh=WH,
      line( xy1=c(50,50), xy2=c(500,50), stroke='black')
)

Changing the color is easy to do, simply assign a color to the stroke parameter of line.

WH<-c(600, 100) # window rect
svgR( wh=WH,
      line( xy1=c(50,50), xy2=c(500,50), stroke='red')
)

This color can also be specified as #FF0000 or rgb(255,0,0)

To change the line width simply set

WH<-c(600, 100) # window rect
svgR( wh=WH,
      line( xy1=c(50,50), xy2=c(500,50), stroke='#00FF00', stroke.width=10)
)

To create a dashed line set the stroke.dasharray attribute

WH<-c(600, 100) # window rect
svgR( wh=WH,
      line( xy1=c(50,50), xy2=c(500,50), stroke='rgb(0,0,255)', stroke.dasharray=8 )
)

The polyline

To produce a sequence of connected line segments use polyline. The points are specified with the points attribute. Points can be specified in any of the following ways:

  • a list of n vectors of length 2
  • a n by 2 matrix
  • a vector of the form \((x_1,y_1,x_2,y_2 ...x_n,y_n)\)

Other attributes can be set as before.

WH<-c(600, 100) # window rect
svgR( wh=WH,
      polyline( 
        points=rbind(seq(from=20, to=500, by=50),c(20,80)), 
        stroke='#FF00AA', stroke.dasharray=8, fill='none' )
)

Note: we added an additional attribute fill=‘none’. If we don’t, most browser will fill this with a black color.

The polygon

For closed figures, we use the polygon element. We have the same options to specify points as we did with the polyline.

WH<-c(600, 100) # window rect
points<-lapply((0:7)*(2*pi)/8, function(theta){
  c(100,50)+40*c(cos(theta),sin(theta))
} )
svgR( wh=WH,
      polygon( 
        points=points, stroke='red', fill='none' )
)

Additionally, we can supply a color to the fill variable:

WH<-c(600, 100) # window rect
points<-lapply(0:7, function(i){
  theta<-i*(2*pi)/8
  c(100,50)+20*(1+i%%2)*c(cos(theta),sin(theta))
} )
svgR( wh=WH,
      polygon( 
        points=points, stroke='blue', fill='pink' )
)

And remove the stroke

WH<-c(600, 100) # window rect
points<-lapply(0:15, function(i){
  theta<-i*2*pi/16
  c(100,50)+20*(1+i%%2)*c(cos(theta),sin(theta))
} )
svgR( wh=WH,
      polygon( points=points, fill='orange' )
)

The rectangle,

For a rectangle, (a four sided polygon) we use the rect element. We can specify the position of the upper-left corner of the rectangle in 2 ways:

  • one at a time, for example: x=50, y=20
  • as a vector, for example: xy=c(50,20)

Alternative we can specify the center of the rectangle

  • as a vector, for example cxy=c(100,40)

Also we can specify the dimensions in 2 ways:

  • one at a time, for example: w=100, h=40
  • as a vector, for example: wh=c(100,40)
WH<-c(600, 100) # window rect
svgR( wh=WH,
      rect( xy=c(50,20), wh=c(100,40), stroke='blue', fill='none' )
)

The circle

For a circle, we specify the center and the radius r. The center can be specified in 2 ways:

  • one at a time, for example: cx=100, cy=60
  • as a vector, for example: cxy=c(100,60)
WH<-c(600, 150) # window rect
svgR( wh=WH,
  circle( cxy=c(120,60), r=25, stroke='blue', fill='none' )
)

Moreover, we can compose with rect using cxy to create concentric rectangles and circles:

WH<-c(600, 150) # window rect
cxy<-c(120,60)
dx<-10
svgR( wh=WH,
  circle( cxy=cxy, r=1*dx*sqrt(2), stroke='blue', fill='none' ),
  circle( cxy=cxy, r=2*dx*sqrt(2), stroke='blue', fill='none' ),
  circle( cxy=cxy, r=3*dx*sqrt(2), stroke='blue', fill='none' ),
  rect(cxy=cxy, wh=1*dx*c(2,2), stroke='red',fill='none'),
  rect(cxy=cxy, wh=2*dx*c(2,2), stroke='red',fill='none'),
  rect(cxy=cxy, wh=3*dx*c(2,2), stroke='red',fill='none')
)

However, a better way is to use lapply and iterate

WH<-c(600, 200) # window rect
cxy<-c(120,100)
dx<-10
n<-6
svgR( wh=WH,
  lapply(1:n, function(i)circle( cxy=cxy, r=i*dx*sqrt(2), stroke='blue', fill='none' )),
  lapply(1:n, function(i)rect(cxy=cxy, wh=i*dx*c(2,2), stroke='red',fill='none'))
)

The ellipse

An ellipse is similar to a circle, but instead of r, we specify a pair rx and ry. This can be done in two ways:

  • one at a time, for example: rx=50, ry=30
  • as a vector, for example: rxy=c(50,30)

We demonstrate this with a filled ellipse inside a filled rect

WH<-c(600, 150) # window rect
cxy=c(200,75)
rxy<-c(50,30)
svgR( wh=WH,
  rect(cxy=cxy, wh=2*rxy, stroke='blue', fill='yellow'),
  ellipse(cxy=cxy, rxy=rxy, stroke='green', fill='orange' )
)

The Path Data Attribute

The path element is used to specify a curve. The path may be rendered directly by the setting the stroke attribute or used by other elements such as textPath.

The curve of a path may consist of any combination of lines, Bézier curves (cubic and quadratic), and ellisoid arcs. The exact combination specified in the d atttribue as a sequence of commands and points, summarized by the following table

Command Points Interpretation
M,m (x y)+ Move the current point to a new location (without drawing)
L,l (x, y)+ Draw a line from the current point to x,y
H,h x+ Draw a vertical line from the current
V,v y+ Draw a horizontal line from the current point
C,c (x1 y1 x2 y2 x y)+ Draw a cubic Bézier curve to x,y using x1,y1; x2,y2 as control pts
S,s (x2 y2 x y)+ Draw a cubic Bézier to x,y using x2,y2 as the second control pt (inheriting the first control point from a preceding Bézier)
Q,q (x1 y1 x y)+ Draw a quadratic Bézier curve to x,y using x1,y1 as control pts
T,t (x y)+ Draw a quadratic Bézier curve to x,y (inheriting the first control from a preceding Bézier)
A,a (rx ry x-axis-rotation large-arc-flag sweep-flag x y)+ Draw an elliptical arc to x, y
Z closes the path

Capital lettered commands interpret the points as absolute coordinates, lowercase interprets the points as relative point to the current (starting) point.

The following illustrates the basics of each command.

Lines

Using absolute coordinates (Note: we graphPaper background to illustrate the coordinates)

WH<-c(600,200)
svgR( wh=WH, 
      graphPaper(WH, dxy=c(20,20), labels=TRUE),
      path(d=c("M", 180,90, "L", 150,142,90,142,60,90,90,38,150,38) , stroke='red', fill='none') )
0 20 40 60 80 100 120 140 160 180 200 220 240 260 280 300 320 340 360 380 400 420 440 460 480 500 520 540 560 580 600 0 20 40 60 80 100 120 140 160 180 200

Using relative coordinates

WH<-c(600,200)
svgR( wh=WH, 
      graphPaper(WH, dxy=c(20,20), labels=TRUE),
      path(d=c("m", 180,90, "l", -30,52,-60,0,-30,-52,30,-52,60,0 ) , stroke='green', fill='none') )
0 20 40 60 80 100 120 140 160 180 200 220 240 260 280 300 320 340 360 380 400 420 440 460 480 500 520 540 560 580 600 0 20 40 60 80 100 120 140 160 180 200

Closing the path. To close the path add Z at the end

WH<-c(600,200)
svgR( wh=WH, 
      graphPaper(WH, dxy=c(20,20), labels=TRUE),
      path(d=c("m", 180,90, "l", -30,52,-60,0,-30,-52,30,-52,60,0, "Z" ) , stroke='blue', fill='none') )
0 20 40 60 80 100 120 140 160 180 200 220 240 260 280 300 320 340 360 380 400 420 440 460 480 500 520 540 560 580 600 0 20 40 60 80 100 120 140 160 180 200

The Elliptic Arc Curve

An elliptic Arc is an arc formed from fragment of an ellipse which has possibly been rotated.

To specify that arc, we specify the end points of the arc and then the ellipse from which the arc is formed, and a flag to indicate which portion of the ellipse contains the arc.

To specify a general ellipse, we need 5 numbers, for example a rotational angle \(\theta\), (called x-axis-rotation), the major and minor radii rx, ry and something equivalent to the center of the ellipse. However, we note that rx, ry, rotation together with endpoints of the arc, determine the center to be of one of two locations. So we need only one additional parameter, called the large-arc-flag

theta=20
startPt=c(300,75)
endPt<-startPt+c(0,50)
rxy=c(100,50)
laf0=0
laf1=1
sf0=0
sf1=1
WH<-c(600, 200)
svgR( wh=WH, fill="none",
      graphPaper(WH),
      stroke.width=3,
      path(d=c("M", startPt, "A", rxy, theta, laf0, sf0, endPt ) , stroke='red') ,
      path(d=c("M", startPt, "A", rxy, theta, laf0, sf1, endPt ) , stroke='blue') ,
      path(d=c("M", startPt, "A", rxy, theta, laf1, sf0, endPt ) , stroke='green') ,
      path(d=c("M", startPt, "A", rxy, theta, laf1, sf1, endPt ) , stroke='orange'),
      line( xy1=startPt, xy2=startPt-c(0,40), stroke.width=1, stroke='black'),
      line( xy1=endPt, xy2=endPt+c(0,40), stroke.width=1, stroke='black'),
      text("start pt", cxy=startPt-c(0,50), font.size=12, stroke='black', stroke.width=1),
      text("end pt",   cxy=endPt+c(0,50), font.size=12, stroke='black', stroke.width=1),
      text("sf=0 on this side", xy=c(WH[1]-100,50), font.size=12, stroke='black', stroke.width=1),
      text("sf=1 on this side", xy=c(50,WH[2]-50),  font.size=12, stroke='black', stroke.width=1)

)
start pt end pt sf=0 on this side sf=1 on this side

Q: The Quadratic Bézier

A Quadratic Bézier is a parametric curve whose coodinates are given by quadratic expressions in a parameter t. The most common formulation is given points \(P_0\), \(P_1\), \(C_0\), a point on the curve is given by \[ P= (1-t)^2 P_0 + 2(1-t)t C_0 +t^2 P_1\] for \(0 \leq t \leq1\)

Thus Quadratic Bézier requires three points: \(P_0\), the starting point, \(P_1\) the ending point, and \(C_0\), a control point. One can easliy verify that the control point lies on the intersection of the the lines tangent to the end points.

This is illustrated in the following example

P0=c(200,50)
P1=c(300,150)
C0<-c(100,100)
WH<-c(600,200)
svgR( wh=WH, 
  graphPaper(wh=WH, dxy=c(20,20), labels=TRUE), 
  path(d=c("M",P0, "Q", C0,  P1) , stroke='red',
  stroke.width=3, fill='none') ,
  g( 
    stroke='blue',
    line(xy1=P0,xy2=C0),
    line(xy2=P1,xy1=C0)
  ),
  g(
    stroke="black",
    text("P0",cxy=P0 ),
    text("C0",cxy=C0),
    text("P1", cxy=P1)
  )
)
0 20 40 60 80 100 120 140 160 180 200 220 240 260 280 300 320 340 360 380 400 420 440 460 480 500 520 540 560 580 600 0 20 40 60 80 100 120 140 160 180 200 P0 C0 P1

T: Extending a Bézier with a Quadratic Bézier

Using the T command, we can append to a given Bézier a Quadratic Bézier without specifying an additional control point. The control point that is used will be the reflection of the control point of the previous Bézier. That is, \[C_{i+1}= 2 \times P_i + C_i\] where \(C_i\) is the control point of the previous Bézier. Thus,we ensure continuity of the deriverative of the beginning endpoint of the appended Bézier.

We demonstarte this in the following example

Pt0=c(200,50)
Pt1=c(300,150)
C0<-c(100,100)
C1<-2*Pt1 -C0
Pt2=c(400,50)
svgR( wh=c(600, 250), 
      graphPaper(c(600,250)),
      path(d=c("M",Pt0, "Q", C0,  Pt1 , "T", Pt2) , stroke='red', stroke.width=3, fill='none') ,
      #path(d=c("T", endPt2) , stroke='green', fill='none') ,
      g( 
        stroke='blue',
        line(xy1=Pt0,xy2=C0),
        line(xy2=Pt1,xy1=C0),
        line(xy1=Pt1,xy2=C1),
        line(xy2=Pt2,xy1=C1)
      ),
      g(
        stroke="black",
        text("P0",cxy=Pt0 ),
        text("C0",cxy=C0),
        text("C1",cxy=C1),
        text("P2",cxy=Pt2),
        text("P1", cxy=Pt1)
      )
    )
P0 C0 C1 P2 P1

C: The Cubic Bézier

The Cubic Bézier is similar to the Quadratic Bézier, but has a higher degree allowing for a S-shaped curve. The Cubic Bézier requires four points, \(P_0\) the start point, \(P_1\) the end point and two control points \(C_0\), \(C_1\) . \[ P(t)=(1-t)^3 P_0 + 3 (1-t)^2 t C_0 + 3 (1-t)t^2 C_1 + t^3 P_p \] for \(0 \leq t \leq 1\)

The first control point, \(C_0\), together with the starting point, \(P_0\), determines a vector, \(C_0-P_0\) which is tangent to the curve at \(P_0\). Similarly, the second control point, \(C_1\), together with ending point, \(P_1\), determine a vector, \(P_1-C_1\) which is tangent to the curve at \(P_1\). The magnititude of the vectors \(C_0 - p_0\), and \(P_1 -C_1\) determine the intermediate shape.

The current location is considerd to be the start point (\(P_0\)), so we usually begin a path specification with an M command. This is followed by a C command. (or a lower case c if we are using relative coordinates) and the control points, \(C_0\), \(C_1\), and the ending point \(P_1\).

This is illustrated by the following:

P0=c(200,50)
P1=c(300,150)
C0<-c(100,100)
C1<-c(400,100)
svgR( wh=c(600, 200), 
      graphPaper(c(600,200)),
      path(d=c("M",P0, "C", C0, C1, P1) , stroke='red', stroke.width=3, fill='none') ,
      g( 
        stroke='blue',
        line(xy1=P0,xy2=C0),
        line(xy2=P1,xy1=C1)
      ),
      g(
        stroke="black",
        text("P0",cxy=P0 ),
        text("C0",cxy=C0),
        text("C1",cxy=C1),
        text("P1", cxy=P1)
      )
    )
P0 C0 C1 P1

Interchanging ctrlPt2 with the end point gives

P0=c(200,50)
C1=c(300,150)
C0<-c(100,100)
P1<-c(400,100)
svgR( wh=c(600, 200), 
      graphPaper(c(600,200)),
      path(d=c("M",P0, "C", C0, C1, P1) , stroke='red', stroke.width=3, fill='none') ,
      g( 
        stroke='blue',
        line(xy1=P0,xy2=C0),
        line(xy2=P1,xy1=C1)
      ),
      g(
        stroke="black",
        text("P0",cxy=P0 ),
        text("C0",cxy=C0),
        text("C1",cxy=C1),
        text("P1", cxy=P1)
      )
    )
P0 C0 C1 P1

S: Extending a Bézier with a Cubic Bézier

Just as with the quadratic case, we can extend a Bézier with a Cubic Bézier while specifying one less control point, i.e. we take as the first control point,the last control point of the previous Bézier, thus ensuring continuity of the first derivative.

We illustrate this with the following example.

P0<-c(350,30)
P1<-c(400,150)
P2<-c(200,250)
  
C0<-P0+c(-100,50)  
C1<-P1+c(100,-50) 
C2<-P2+c(-80,-100)

R2<-2*P1-C1 #used only to display the reflected control point

svgR( wh=c(600, 300), 
      graphPaper(c(600,300)),
      #The path of interest
      path(d=c("M",P0, "C", C0, C1, P1, "S", C2, P2) , stroke='red', stroke.width=3, fill='none') ,
      
      g( # lines to illustrate control points
        stroke='blue',
        line(xy1=P0,xy2=C0),
        line(xy2=P1,xy1=C1),
        line(xy1=P1,xy2=R2),
        line(xy2=P2,xy1=C2)
      ),
      g( # labeling of control points
        stroke="black",
        text("P0",cxy=P0 ),
        text("C0",cxy=C0),
        text("C1",cxy=C1),
        text("P1", cxy=P1),
        text("C2",cxy=C2),
        text("R2",cxy=R2),
        text("P2", cxy=P2)
      )
    )
P0 C0 C1 P1 C2 R2 P2

Note, the control points for the \(P_1-P_2\) portions of the path are \(R_2\), \(C_2\), where \(C_2\) is specified in the path, but \(R_2\) is implicitly defined as the reflection of \(C_1\) insidet the path data description attribute d. That is “S” uses the \(P_1\) and \(C_1\) defined to it’s left to compute \(R_2=2 \times P_1 - C_1\).

More Bezier Examples

svgR( wh=c(800, 200), 
      graphPaper(c(800,200)),
      g( stroke.width=3, fill="transparent",
        path(d = c("M",100, 0, "C", 100, 100, 200, 0, 200, 100), stroke='red' ), 
        path(d = c("M",200, 0, "c", 0, 100, 100, 0, 100, 100),  stroke='pink'), 
        path(d = c("M",300, 0, "c", 0, 100, 100, 0, 100, 100),  stroke='purple'), 
        path(d = c("M",400, 0, "c", 0, 100, 100, 0, 100, 200),  stroke='green'), 
        path(d = c("M",500, 0, "c", 0, 100, 100, 0, 100, 200),  stroke='blue'), 
        path(d = c("M",600, 0, "c", 0, 300, 100, -100, 100, 200),  stroke='orange')
      )
 )

Markers

Markers are used to decorate the ends paths and any adjoining segements. Markers allow for a completely customizable looking arrowhead and tails

First we show some simple examples for the end, start, middle

An Arrow Tail

WH=c(600, 100) # window rect
svgR( wh=WH,
  line(xy1=c(20,50), xy2=c(300,50), 
    stroke='black', stroke.width=2, 
    marker.start=marker(viewBox=c(0, 0, 10, 10), refXY=c(1,5), 
      markerWidth=6, markerHeight=6, orient="auto",
      path( d="M 0 0 L 10 5 L 0 10 z" )
    ) 
  )
)

An Arrow Middle

We can also place markers in middle points that define a path.

WH=c(600, 100) # window rect
svgR( wh=WH,
  path(d="M100,20 l50,0 l0,50 l50,0", fill="none",
    stroke='black', stroke.width=2, 
    marker.mid=marker(viewBox=c(0, 0, 10, 10), refXY=c(4,4), 
      markerWidth=7, markerHeight=7, orient="auto",
      rect(xy=c(1,1),wh=c(5,5), fill='black')
    ) 
  )
)

An Arrow Head

WH=c(600, 100) # window rect
svgR( wh=WH,
  line(xy1=c(20,50), xy2=c(300,50), 
    stroke='black', stroke.width=2, 
    marker.end=marker(viewBox=c(0, 0, 10, 10), refXY=c(1,5), 
      markerWidth=6, markerHeight=6, orient="auto",
      path( d=c("M", 0, 0, "L", 10, 5, "L", 0, 10, "z") )
    ) 
  )
)

Putting it together

Tail, Middle, Head

We can also place markers in middle points that define a path.

WH=c(600, 100) # window rect
svgR( wh=WH,
  path(d="M100,20 l50,0 l0,50 l50,0", fill="none",
    stroke='black', stroke.width=2, 
    marker.start=marker(viewBox=c(0, 0, 10, 10), refXY=c(1,5), 
      markerWidth=6, markerHeight=6, orient="auto",
      path( d="M 0 0 L 10 5 L 0 10 z" )
    ),
    marker.mid=marker(viewBox=c(0, 0, 10, 10), refXY=c(4,4), 
      markerWidth=7, markerHeight=7, orient="auto",
      rect(xy=c(1,1),wh=c(5,5), fill='black')
    ),
    marker.end=marker(viewBox=c(0, 0, 10, 10), refXY=c(1,5), 
      markerWidth=6, markerHeight=6, orient="auto",
      path( d=c("M", 0, 0, "L", 10, 5, "L", 0, 10, "z") )
    ) 
  )
)

Exotic Markers

Varing the marker path allows for a more exotic look.

WH=c(600, 100) # window rect
svgR( wh=WH,
  path(d="M100,20 l50,0 l0,50 l50,0", fill="none",
    stroke='black', stroke.width=2, 
    marker.start=marker(viewBox=c(0, 0, 10, 10), refXY=c(1,5), 
      markerWidth=16, markerHeight=16, orient="auto",
      path( d=c( "M", c(0,0), "A", c(5,5), 0, 1,  1,  c(0,10),  "Z" ) )
    ),
    marker.mid=marker(viewBox=c(0, 0, 10, 10), refXY=c(4,4), 
      markerWidth=7, markerHeight=7, orient="auto",
      circle(cxy=c(5,5),r=5, fill='red')
    ),
    marker.end=marker(viewBox=c(0, 0, 20, 20), refXY=c(1,10), 
      markerWidth=16, markerHeight=20, orient="auto",
      path( fill="blue",
        d=c("M", c(0,0), 
            "A", c(20,20), 0, 0,  0,  c(10,10),
            "A", c(20,20), 0, 0,  0,  c(0,20),
            "Z") 
      )
    ) 
  )
)

Multiple Arrows

When employing a marker multiple times, it may be more efficient to include it in a defs container and then provide a reference to it.

WH=c(600, 100) # window rect
svgR( wh=WH,
  defs(
    marker(id="marker.start", viewBox=c(0, 0, 10, 10), refXY=c(1,5), 
      markerWidth=16, markerHeight=16, orient="auto",
      path( d=c( "M", c(0,0), "A", c(5,5), 0, 1,  1,  c(0,10),  "Z" ) )
    ),
    marker(id="marker.mid", viewBox=c(0, 0, 10, 10), refXY=c(4,4), 
      markerWidth=7, markerHeight=7, orient="auto",
      circle(cxy=c(5,5),r=5, fill='red')
    ),
    marker(id="marker.end", viewBox=c(0, 0, 20, 20), refXY=c(1,10), 
      markerWidth=16, markerHeight=20, orient="auto",
      path( fill="blue",
        d=c("M", c(0,0), 
            "A", c(20,20), 0, 0,  0,  c(10,10),
            "A", c(20,20), 0, 0,  0,  c(0,20),
            "Z") 
      )
    ) 
  ),
  path(d="M100,20 l50,0 l0,50 l50,0", fill="none",
    stroke='black', stroke.width=2, 
    marker.start='url(#marker.start)',
    marker.end='url(#marker.end)',
    marker.mid='url(#marker.mid)'
  ),
  path(d = c("M",200, 20, "C", 250, 80, 380, 0, 400, 50, "L", 300, 80),  
    stroke='green', stroke.width=2, fill="none",
    marker.start='url(#marker.start)',
    marker.end='url(#marker.end)',
    marker.mid='url(#marker.mid)'
  )
)





Text

WH=c(800, 400) # window rect
r=WH[2]/2
svgR( wh=WH,
  g(
    circle(cxy=WH/2, stroke.width=8, stroke="red", r=r),
    text('Data Rules', cxy=WH/2, font.size=80, stroke="red") 
    ) 
)
Data Rules

A good reference is: http://www.hongkiat.com/blog/scalable-vector-graphics-text/

Simple Text

Font Families and Styling

Attribute Values Initial Comment
font.family CSS font family Depends Name of font or font family
font.size pt, px, em, %, etc, inherit medium The font size. Any SVG unit can be used.
font.style (normal| italic|oblique| inherit) normal Style in which letters are rendered.
font.variant (normal| small.caps | inherit) normal ‘small.caps’ sets lower case letters render to small caps.
font.weight (normal|bold|inherit|+others) normal The thickness of the font.

Examining some font families using font.family

y<-40
fontName<-c("serif","sans-serif","cursive","fantasy","Arial","Times New Roman","Verdana", "Comic Sans")
WH=c(800,200)
svgR( wh=WH,
      graphPaper(WH, dxy=c(40,40), labels=TRUE),
      font.size=40, stroke='black',  stroke.width=0.5,
      lapply(1:8, function(i){
        text(fontName[i], xy=c(40+400*(i%%2), 40*floor((i+1)/2)), font.family=fontName[i], fill=rrgb())
      })
)
0 40 80 120 160 200 240 280 320 360 400 440 480 520 560 600 640 680 720 760 800 0 40 80 120 160 200 serif sans-serif cursive fantasy Arial Times New Roman Verdana Comic Sans

In addition, there we can specify a font style:

y<-40
WH=c(800,120)
fontStyle=c("normal" , "italic" , "oblique", "inherit")
svgR( wh=WH,
      font.style= "inherit",
      graphPaper(WH, dxy=c(40,40), labels=TRUE),
      font.size=40, stroke='black',  stroke.width=0.5,
      lapply(1:4, function(i){
        text(fontStyle[i], xy=c(40+400*(i%%2), 40*floor((i+1)/2)), font.style=fontStyle[i], fill=rrgb())
      })
)
0 40 80 120 160 200 240 280 320 360 400 440 480 520 560 600 640 680 720 760 800 0 40 80 120 normal italic oblique inherit

Text Positioning

text-anchor

By default, text begins at the x value, however the this can be modified by the text.anchor attribute (parameter). The text.anchor attribute can take one of four values: “start” , “middle” , “end” , “inherit”. These are illustrated below.

WH=c(800,220)
textAnchor=c("start" , "middle" , "end" , "inherit")
svgR( wh=WH,
      graphPaper(WH, dxy=c(40,40), labels=TRUE),
      anchorAt(WH[1]/2,WH),
      lapply(1:4, function(i){
        y<-i*40
        g(
          baseLineAt(y,WH),
          text( textAnchor[i], xy=c(400, y), fill=rrgb(), text.anchor=textAnchor[i],
                font.size=40, font.weight="bold", stroke='black',  stroke.width=0.5)
        )
      }
      )
)
0 40 80 120 160 200 240 280 320 360 400 440 480 520 560 600 640 680 720 760 800 0 40 80 120 160 200 anchor baseline start baseline middle baseline end baseline inherit

dominant.baseline

By default, text lies directly above the y coordinate (the baseline) This alignment can be adjusted by setting the dominant.baseline attribute (parameter). The baseline is the y coordinate given to the text element. By default, the text is aligned using the alphabetic option as the baseline. Other alignments are possible by setting the alignment.baseline parameter, as illustrated in the following:

alignment.baseline

The baseline is the y coordinate given to the text element. By default, the text is aligned using the alphabetic option as the baseline. Other alignments are possible by setting the alignment.baseline parameter, as illustrated in the following:

WH<-c(800,80*9)
baseLine<-c( "alphabetic","ideographic","hanging","mathematical","central","middle",
             "text-before-edge","text-after-edge")
svgR( wh=WH,
      graphPaper(WH, dxy=c(40,40), labels=TRUE),
      anchorAt(WH[1]/2,WH), 
      lapply(1:8, function(i){
        y<-i*80
        g(
          baseLineAt(y,WH),
          text( baseLine[i], xy=c(400, y), fill=rrgb(), dominant.baseline=baseLine[i],
                font.size=40, font.weight="bold", stroke='black',  stroke.width=0.5)
        )
      }
      )
)
0 40 80 120 160 200 240 280 320 360 400 440 480 520 560 600 640 680 720 760 800 0 40 80 120 160 200 240 280 320 360 400 440 480 520 560 600 640 680 720 anchor baseline alphabetic baseline ideographic baseline hanging baseline mathematical baseline central baseline middle baseline text-before-edge baseline text-after-edge

tspan

Within a text element we can change the positioning, style, color of component words using the tspan element child element.

WH<-c(800,160)
svgR( wh=WH,
      graphPaper(WH, dxy=c(40,40), labels=TRUE),
      text( xy=c(40,120), lapply(1:14, function(i){
        tspan( LETTERS[i] , font.size=i*10, fill=rrgb() )}
      ))
)
0 40 80 120 160 200 240 280 320 360 400 440 480 520 560 600 640 680 720 760 800 0 40 80 120 160 A B C D E F G H I J K L M N
WH<-c(800,160)
svgR( wh=WH,
      graphPaper(WH, dxy=c(40,40), labels=TRUE),
      text( xy=c(40,40), font.size=20, lapply(1:14, function(i){
        tspan( LETTERS[i] , dy=10*(i%%2), fill=rrgb() )}
      ))
)
0 40 80 120 160 200 240 280 320 360 400 440 480 520 560 600 640 680 720 760 800 0 40 80 120 160 A B C D E F G H I J K L M N

baseline.shift for superscripts and subscriptss

Baseline.shift can be used to create super/subscripts. Values for baseline.shift are

  • baseline: not shift
  • sub: subscript
  • super: superscript
  • percentage: a percentate
  • length
WH<-c(800,120)
shift<-c( "auto","baseline","super","sub","60%", 6, "inherit")
bl<-80
svgR( wh=WH,
      graphPaper(WH, dxy=c(40,40), labels=TRUE),
      baseLineAt(bl,WH),
      lapply(1:7, function(i){
        text( "X",
              tspan( shift[i], baseline.shift=shift[i] , font.size=20),
              xy=c(i*120, bl), fill=rrgb(), 
              font.size=20, font.weight="bold", stroke='black',  stroke.width=0.5
        )
      }
      )
)
0 40 80 120 160 200 240 280 320 360 400 440 480 520 560 600 640 680 720 760 800 0 40 80 120 baseline X auto X baseline X super X sub X 60% X 6 X inherit

Math equations: Superscripts, subscripts

WH=c(800,240)
svgR( wh=WH,
      graphPaper(WH, dxy=c(40,40), labels=TRUE),
      refLines(WH),
      text( 
        tspan(mathSymbol("\\int"), font.size=120, dominant.baseline='mathematical'), 
        "e",
        tspan("-x", baseline.shift="60%", dominant.baseline="mathematical", font.size=40,
              tspan( "2", baseline.shift='super', font.size=20)),
        "dx",
        dominant.baseline='mathematical', xy=WH/2,  font.size=80, 
        stroke='black',  stroke.width=0.5, fill='lightblue'
      )
)
0 40 80 120 160 200 240 280 320 360 400 440 480 520 560 600 640 680 720 760 800 0 40 80 120 160 200 240 baseline anchor e -x 2 dx

Text Along a Path

Here we create text in a circle.

WH<-c(800, 600)
txt<-"The product of the circumferenc times the redius equals twice the area."
r=200
Pt1=WH/2+c(0,r)
Pt2=WH/2-c(0,r)
rxy=c(r,r)
svgR( wh=WH,
  defs( 
    path(id="C1",d=c("M", Pt1, "A", rxy, 0, 1, 0, Pt2, "A", rxy, 0, 1, 0, Pt1) , stroke='red')
  ), 
  graphPaper(WH, dxy=c(50,50), labels=TRUE),
  text( textPath(txt, font.size=40, stroke='black', stroke.width=0.5, fill='red', xlink.href="#C1")),
  circle(cxy=WH/2, r=90, stroke='red', fill='orange')
)
0 50 100 150 200 250 300 350 400 450 500 550 600 650 700 750 800 0 50 100 150 200 250 300 350 400 450 500 550 600 The product of the circumferenc times the redius equals twice the area.

We can also spiral our text

WH<-c(800, 300)
txt<-"help, I'm getting dizzy!"
r=200
Pt1=WH/2+c(0,r)
Pt2=WH/2-c(0,r)
rxy=c(r,r)
svgR( wh=WH,
  defs( 
    path(id="spiralText",
         d = "M 50,50 A 20,20 0 0 1 90,50 A 40,40 0 0 1 10,50 A 60,60 0 0 1 130,50",
         stroke='blue')
  ), 
  graphPaper(WH, dxy=c(50,50), labels=TRUE),
  text( textPath(txt, font.size=30, stroke='black', stroke.width=0.5, fill='red', xlink.href="#spiralText"),
        transform=list(translate=c(100,100))
  )
)
0 50 100 150 200 250 300 350 400 450 500 550 600 650 700 750 800 0 50 100 150 200 250 300 help, I’m getting dizzy!

Reflection Transformation

WH=c(800,250)
svgR( wh=WH,
      graphPaper(WH, dxy=c(50,50), labels=TRUE),
      text( "Reflections", xy=c(0,0), 
            font.size=100, font.weight="bold", 
            stroke='black',  stroke.width=0.5, fill='red',
            transform=list(translate=c(100,100))
            ),
      text( "Reflections", xy=c(0,0), 
            font.size=100, font.weight="bold", 
            stroke='black',  stroke.width=0.5, fill='pink',
            transform = list(translate=c(100, 100), scale=c(1, -1) ) 
          )
)
0 50 100 150 200 250 300 350 400 450 500 550 600 650 700 750 800 0 50 100 150 200 250 Reflections Reflections

Writing mode

The writing mode is useful to change text orientation to top-bottom

WH<-c(200,600)
svgR( wh=WH,
      graphPaper(WH, dxy=c(40,40), labels=TRUE),
      text( linearRegression ,  writing.mode="tb" , 
              font.size=100,
              xy=c(80, 80), fill='red',
              font.weight="bold", stroke='black',  stroke.width=0.5
      )
)
0 40 80 120 160 200 0 40 80 120 160 200 240 280 320 360 400 440 480 520 560 600 线性回归

Text can be outlined or filled depending on what argument is supplied to the fill attribute.

WH=c(800,100)
svgR( wh=WH,
      graphPaper(WH, dxy=c(20,20), labels=TRUE),
      text( "Outlined Text", xy=c(60,80), 
            font.size=50, font.weight="bold", 
            stroke='black',  stroke.width=0.5, fill='none'),
      text( "Filled Text", xy=c(460,80), 
            font.size=50, font.weight="bold", 
            stroke='black',  stroke.width=0.5, fill='lightblue')
)
0 20 40 60 80 100 120 140 160 180 200 220 240 260 280 300 320 340 360 380 400 420 440 460 480 500 520 540 560 580 600 620 640 660 680 700 720 740 760 780 800 0 20 40 60 80 100 Outlined Text Filled Text
WH=c(800,120)
svgR( wh=WH,
      graphPaper(WH, dxy=c(50,50), labels=TRUE),
      text( "svgRRocks", xy=c(50,100), 
            font.size=100, font.weight="bold", 
            stroke='black',  stroke.width=0.5, fill='red')
)
0 50 100 150 200 250 300 350 400 450 500 550 600 650 700 750 800 0 50 100 svgRRocks





The Fill Attribute

Fill’er up

At this point, we have seen several shapes, with the fill attribute specified either as

However, we can also fill with

Fill with a Linear Gradient

As first example, we take an ellipse to fill with a linear gradient.

colors=c('blue','green','white','orange','red')
svgR( 
  wh=c(600, 200), 
  ellipse(cxy=c(250,100), rxy=c(200,80), 
          fill=linearGradient( xy1=c(0,0), xy2=c(1,0), colors=colors)     
  )
)

The above example invokes the svgR quick approach with fill=lineargradient(…), but this is not standard svg. Standard svg requires a reference to a gradient to be assigned to the fill attribute, i.e. fill=“url(#id)” and usually the gradient definition is placed inside a defs element, as illustrated in the following.

colors<-c('green','white','orange')
svgR( 
   wh=c(600, 200), 
  defs(
    linearGradient(id='grad1', xy1=c(0,0), xy2=c(1,0), colors=colors)
  ),
  ellipse(cxy=c(250,100), rxy=c(200,80), fill="url( #grad1 )") 
)

In the second approach, we can reuse the gradient definition multiple times referencing the url for each use

color<-c('green','white','orange')
svgR( 
   wh=c(600, 200), 
  defs(
    linearGradient(id='grad1', xy1=c(0,0), xy2=c(1,0),colors=colors)
  ),
  ellipse(cxy=c(50,100), rxy=c(20,80), fill="url( #grad1 )"),
  ellipse(cxy=c(100,100), rxy=c(20,80), fill="url( #grad1 )"),   
  ellipse(cxy=c(150,100), rxy=c(20,80), fill="url( #grad1 )"), 
  ellipse(cxy=c(200,100), rxy=c(20,80), fill="url( #grad1 )"),
  ellipse(cxy=c(250,100), rxy=c(20,80), fill="url( #grad1 )") 
)

However, if we explicity assign an id to the fill=lineargrad(id=…), for the first ellipse, we can also refer to that gradient in the subsequent ellipses

colors<-c('blue','green','white','orange','red')
svgR( 
  wh=c(600, 200), 
  ellipse(cxy=c(50,100), rxy=c(20,80), 
          fill=linearGradient( id="grad2", xy1=c(0,0), xy2=c(1,0), colors=colors)), 
  ellipse(cxy=c(100,100), rxy=c(20,80), fill="url( #grad2 )"),
  ellipse(cxy=c(150,100), rxy=c(20,80), fill="url( #grad2 )"),
  ellipse(cxy=c(200,100), rxy=c(20,80), fill="url( #grad2 )"),
  ellipse(cxy=c(250,100), rxy=c(20,80), fill="url( #grad2 )") 
)

Of course, an elegant approach is to put the ellipses in a group and iterate

colors<-c('blue','green','yellow','white','orange','red','purple')
svgR( 
  wh=c(600, 200),
  g(
    lapply(seq(50,550,by=50), function(x)ellipse(cxy=c(x,100), rxy=c(20,80))),
    fill=linearGradient( xy1=c(0,0), xy2=c(1,0), colors=colors)
  )
)

Filling with a radial gradient

Filling radial gradient is essentially the same as with a linear gradient

colors=c('blue','green','white','orange','red')
svgR( 
  wh=c(600, 200), 
  ellipse(cxy=c(250,100), rxy=c(200,80), 
          fill=radialGradient(
            colors=colors)     
  )
)
Radial Gradient fill with center offset
colors=c('white', 'green', 'blue')
WH=c(600, 200)
S=190 # Size of square
svgR( 
  wh=WH, 
  rect(cxy=WH/2, wh=c(190,190), stroke='black', stroke.width=3,
      fill=radialGradient( 
        cxy=c(.5,.5), 
        r=.5,
        fxy=c(.6,.6)+c(0,0),
        colors=colors)     
  )
)

Here the attributes of the radialGradient are:

  • cxy The center of the outer circle
  • r The radius of the outer circe
  • fxy The center of the gradient focus
  • colors, an array of gradient colors

All the above units are in relative to the bounding box, which in this case is the dimensions of the rectangle.

Alternativly, we can set the units via filterUnits=“userSpaceOnUse”,

Radial Gradient fill with center offset
colors=c('white', 'green', 'blue')
WH=c(600, 200)
S=190 # Size of square
svgR( 
  wh=WH, 
  rect(cxy=WH/2, wh=c(S,S), stroke='black', stroke.width=3,
    fill=radialGradient( gradientUnits="userSpaceOnUse",
            cxy=WH/2, 
            r=.5*S,
            fxy=WH/2+.1*S,
            colors=colors)     
  )
)

Fill with Patterns

We can also fill with a pattern (Although, depending on the your system, you may need to refresh after scrolling)

WH=c(600, 200) # window rect
svgR( wh=WH,
  ellipse( cxy=c(250,100), rxy=c(200,80), 
    fill=pattern( xy=c(10,10), wh=c(40,40), patternUnits="userSpaceOnUse",
                  circle(cxy=c(10,10), r=10, fill='blue')
         )
  )
)

Or using defs for the fill

WH=c(600, 200) # window rect
patternId<-autoId()
patternUrl<-sprintf("url(#%s)",patternId)
svgR( wh=WH,
  defs(
    pattern(
        id=patternId, xy=c(10,10), wh=c(40,40), patternUnits="userSpaceOnUse" ,
        circle(cxy=c(10,10), r=10, fill='red')
    )
  ),
  ellipse( cxy=c(250,100), rxy=c(200,80), fill=patternUrl)

)

And we can compose pattern with gradients:

WH=c(600, 200) # window rect
colors=c('white','yellow','orange','green', 'blue','black')
svgR( wh=WH,
  ellipse( cxy=c(250,100), rxy=c(200,80), 
    fill=pattern( xy=c(10,10), wh=c(40,40), patternUnits="userSpaceOnUse",
                  rect(cxy=c(20,20), wh=c(40,40), 
                       fill=radialGradient(  colors=colors)
                  )
         )
  )
)

Filling text

We can also fill text with gradients and patterns

WH=c(600, 300) # window rect
colors1=c('white','yellow','orange','green', 'blue','black')
colors2=c('blue','green','orange','yellow','orange', 'green','blue')
svgR( wh=WH,
  text( cxy=c(150,100), font.size=300, "R", stroke='green',
        fill=pattern( xy=c(0,0), wh=c(20,20), patternUnits="userSpaceOnUse",
                  text("R", cxy=c(10,10), stroke='red')
        )
  )
)
R R
WH=c(600, 300) # window rect
svgR( wh=WH,
  text( cxy=c(150,100), font.size=300, "R",
        fill=radialGradient( colors=colors1)
  ),
  text( cxy=c(420,50), font.size=60, "Programming",
        fill=linearGradient(colors=colors2)
  )
)
R Programming




svgR





The Filter Attribute

Overview of Filters

Introduction

In the previous section we explored how the fill attribute can be applied to text and shapes. Another attribute for text and shapes, is the filter attribute. Filter attributes may take as values a filter element or a reference to a filter element. A Filter element is a container for filter effect elements, also called filter primitives or fe-elements . The fe-elements feed into each other to form a directed graph with a root. Typically, the fe elements are linked together using the in1, in2 attributes. One might visualize a filter to look something like

feBlend SourceGraphic feGaussianBlur feOffset feComposite feFlood SourceGraphic in1 in2 in1 in1 in1 in2

One distinct difference between fills and filters, is that a fill is restricted to the interior, while filters are not.

Note Technically this is not a tree, since the SourceGraphics label appears muliple places.

Note Pure SVG allows only references, but svgR allows to use the filter element directly as the value of a filter attribute.

Todo!!!:

  • feFuncX
  • feTile

The In Attributes of Filter components

Most fe-element (filter primitive) require an input specification, which is preformed by assigning values to their in attributes: in1 and in2.

(Two notable exceptions to this are the fe-elements: feImage and feFlood.)

Values for the in attributes are given by the following table

Value Description
SourceGraphic represents the original element to which the filter is applied (as full RGBA image data)
SourceAlpha represents just the alpha channel of the original element to which the filter is applied
BackgroundImage represents the area directly underneath the filter region
BackgroundAlpha represents just the alpha channel of the area underneath the filter region
FillPaint represents whatever fill was applied to the element in question
StrokePaint represents whatever stroke was applied to the element
a reference Any other value should be a reference equal to the result attribute for any preceding filter primitive. In this case, the output of the referenced primitive will be used as the specified input.

The fe-Elements (Filter primitives)

Element Attributes
feBlend in2=“second source
mode=“normal” “multiply” “screen” “darken” “lighten”
feColorMatrix type=“matrix” “saturate” “hueRotate” “luminanceToAlpha”
values=“matrix values” “saturation value (0-1)” “rotate degrees
feComponentTransfer container for
feFuncR,
feFuncG,
feFuncB, and
feFuncA elements.
feFuncX type=“identity” “table” “discrete” “linear” “gamma”
tableValues=“intervals for table, steps for discrete
slope=“linear slope
intercept=“linear intercept
amplitude=“gamma amplitude
exponent=“gamma exponent
offset=“gamma offset
feComposite in2=“second source
operator=“over” “in” “out” “atop” “xor” “arithmetic”
The following attributes are used with arithmetic:
k1=“factor for in1,in2
k2=“factor for in1
k3=“factor for in2
k4=“additive offset
feConvolveMatrix order=“columns rows” (default 3 by 3)
kernel=“values
bias=“offset value
feDiffuseLighting Container for a light source element.
surfaceScale=“height” (default 1)
diffuseConstant=“factor” (must be non-negative; default 1)
feDisplacementMap scale=“displacement factor” (default 0)
xChannelSelector=“R” “G” “B” “A”
yChannelSelector=“R” “G” “B” “A”
in2=“second input
feFlood flood.color=“color specification
flood.opacity=“value (0-1)
feGaussianBlur stdDeviation=“blur spread” (larger is blurrier; default 0)
feImage xlink:href=“image source
feMerge container for feMergeNode elements
feMergeNode in1 = “intermediate result
feMorphology operator=“erode” “dilate”
radius=“x-radius y-radius
radius=“radius
feOffset dx=“x offset” (default 0)
dy=“y offset” (default 0)
feSpecularLighting Container for a light source element.
surfaceScale=“height” (default 1)
specularConstant=“factor” (must be non-negative; default 1)
specularExponent=“exponent” (range 1-128; default 1)
feTile tiles the in1 layer
feTurbulence type=“turbulence” “fractalNoise”
baseFrequency=“x-frequency y-frequency
baseFrequency=“frequency
numOctaves=“integer
seed=“number
feDistantLight azimuth=“degrees” (default 0)
elevation=“degrees” (default 0)
fePointLight x=“coordinate” (default 0)
y=“coordinate” (default 0)
z=“coordinate” (default 0)
feSpotLight x=“coordinate” (default 0)
y=“coordinate” (default 0)
z=“coordinate” (default 0)
pointsAtX=“coordinate” (default 0)
pointsAtY=“coordinate” (default 0)
pointsAtZ=“coordinate” (default 0)
specularConstant=“focus control” (default 1)
limitingConeAngle=“degrees

The Gaussian Blur Filter

WH=c(800, 100) # window rect
svgR( wh=WH,
  text(xy=c(20,15), "On the left, the original shape, on the right, the shape with a gaussian blur applied"),
  text( 'svgR', xy=c(50,80), font.size=50, fill="blue"),
  text( 'svgR', xy=c(250,80), font.size=50, fill="blue",
    filter =filter( 
      y=-5, height=40, 
      feGaussianBlur(in1="SourceGraphic", stdDeviation=3)
    )
  ),
  text( 'svgR', xy=c(450,80), font.size=50, fill="blue",
    filter=filter( 
      y=-5, height=40, 
      feGaussianBlur(in1="SourceAlpha", stdDeviation=3)
    )
  ),
  text( 'svgR', xy=c(650,80), font.size=50, fill="blue",
    filter=filter( 
      y=-5, height=40, 
      feGaussianBlur(in1="SourceGraphic", stdDeviation=c(8,0))
    )
  )
)
On the left, the original shape, on the right, the shape with a gaussian blur applied svgR svgR svgR svgR

Alternatively, we can place the filter inside a defs element.

WH=c(800, 100) # window rect
svgR( wh=WH,
  defs( 
    filter(
      id="blurFilter1", 
      y=-5, height=40,
      feGaussianBlur(in1="SourceGraphic", stdDeviation=3)
    ),          
    filter(
      id="blurFilter2", 
      y=-5, height=40,
      feGaussianBlur(in1="SourceAlpha", stdDeviation=3)
    ),           
    filter(
      id="blurFilter3", 
      y=-5, height=40,
      feGaussianBlur(in1="SourceGraphic", stdDeviation=c(8,0))
    )   
  ),
  text( 'svgR', xy=c(50,80),  font.size=50, fill="blue"),
  text( 'svgR', xy=c(250,80), font.size=50, fill="blue", filter="url(#blurFilter1)"),
  text( 'svgR', xy=c(450,80), font.size=50, fill="blue", filter="url(#blurFilter2)"),
  text( 'svgR', xy=c(650,80), font.size=50, fill="blue", filter="url(#blurFilter3)")
)
svgR svgR svgR svgR

Note The last filter effect gives the appearence of motion and is achieved using a stdDeviation in the x direction of 8, and a stdDeviation in the y direction of 0.

Gaussian Blur and Opacity

It is important to note that blur effect is accomplished by adjusting the opacity, as see below. This property is extremely useful for creating an alpha channel to be used in lighting.

WH=c(800, 100) # window rect
svgR( wh=WH,
  rect(xy=c(0,0), wh=WH, 
    fill=pattern( xy=c(0,0), wh=c(20,20), patternUnits="userSpaceOnUse",
      text("R", cxy=c(10,10), font.size=18, stroke='red')
    )
  ), 
  text( 'DATA', cxy=WH/2, font.size=90, fill="blue",
    filter=filter( y=-5, height=40, feGaussianBlur(in1="SourceGraphic", stdDeviation=5))
  )
)
R DATA

Filter Coordinates

Understanding the Filter Coordinates

We begin by illustrating a simple filter.

A Minimal Example: feOffset

WH=c(800, 160) # window rect
svgR( wh=WH,
  graphPaper(WH, dxy=c(40,40), labels=TRUE), 
  rect( xy=c(40,40), wh=c(120,40), stroke="black", fill="blue", fill.opacity=0.5,
         filter=filter(  wh=c(2,2), 
           feOffset(  dxy=c(80, 20), in1="SourceGraphic")
         )
  )
)
0 40 80 120 160 200 240 280 320 360 400 440 480 520 560 600 640 680 720 760 800 0 40 80 120 160

Dissecting the filter Coordinates

Examining the above example we note

  • The rectangle is declared to have an upper-left corner to be located at xy=c(40,40)
  • The rectange is rendered with the upper-left corner at xy=c(120,60)
  • The filter is assigned using the filter attribute filter=.
  • The filter is constructed using the filter(…), element.
  • The filter has one component feOffset
  • The feOffset has 2 attributes, dxy, in1
  • dxy is the displacement of dxy=c(80,20), causing the final location of the upper-left hand corner to be c(120,60)=c(40,40)+c(120,60)
  • in1 is assigned “SourceGraphics”, indicating that the input to feOffset is the original source.
  • The filter is assigned a wh=c(2,2)=c(“200%”,“200%”)

This last point is significant: the filter region must large enough to include the image to be rendered. Any portion outside will be clipped. By default, the wh is the size of the bounding box of the SourceGraphic. This means that we may need to resize the wh and xy filter attributes accordingly.

The Bounding Box

We can visualize this as 0 40 80 120 160 200 240 280 320 360 400 440 480 520 560 600 640 680 720 760 800 0 40 80 120 160 xy w h

The filter wh attribute is the width and height of the smallest rect containg both the original image (green) and the displaced image (blue). We call that rectangle the filter Boundingbox. The units for wh are called filterUnits and by default are expressed relative to “object BoundingBox”. That is, the filterUnits attribute is filterUnits=objectBoundingBox. The “object BoundingBox” is the smallest rectangle containing the SourceGraphic. When the filter attributes xy, wh are not specified, the default is assumed to be that of the “object BoundingBox” , that is wh=c(1,1) and xy=(0,0). In our case, this makes wh=c(2,2), and the xy=c(0,0).

An Alternative to filterUnits=“objectBoundingBox” is filterUnits=“userSpaceOnUse”. In that case, the filter coordintate attributes become xy=c(40,40), wh=c(200,80).

This is show as follows:

WH=c(800, 120) # window rect
svgR( wh=WH,
  graphPaper(WH, dxy=c(40,40), labels=TRUE), 
  rect( xy=c(40,40), wh=c(120,40), stroke="black", fill="none"), 
  rect( xy=c(40,40), wh=c(120,40), stroke="black", fill="blue", fill.opacity=0.2 ,
         filter=filter( wh=c(200,80), xy=c(40,40), 
                        filterUnits="userSpaceOnUse",
           feOffset(  dxy=c(80, 20), in1="SourceGraphic")
         )
  )
)
0 40 80 120 160 200 240 280 320 360 400 440 480 520 560 600 640 680 720 760 800 0 40 80 120

As a final illustration, consider the case when dxy is negative, for example, dxy=-c(40,20). For filterUnits=“userSpaceOnUse”, we need to set filter wh=c(160,60), xy=c(0,20) in order to cover both the both the original SourceGraphic and the final feOffset graphic result.

WH=c(800, 120) # window rect
svgR( wh=WH,
  graphPaper(WH, dxy=c(40,40), labels=TRUE), 
  rect( xy=c(40,40), wh=c(120,40), stroke="black", fill="none"), 
  rect( xy=c(40,40), wh=c(120,40), stroke="black", fill="blue", fill.opacity=0.2 ,
         filter=filter( wh=c(160,60), xy=c(0,20), 
                        filterUnits="userSpaceOnUse",
           feOffset(  dxy=c(-40, -20), in1="SourceGraphic")
         )
  )
)
0 40 80 120 160 200 240 280 320 360 400 440 480 520 560 600 640 680 720 760 800 0 40 80 120

For filterUnits=“objectBoundingBox”, we set wh=c(4/3,3/2), xy=(-1/3, -1/2)

WH=c(800, 120) # window rect
svgR( wh=WH,
  graphPaper(WH, dxy=c(40,40), labels=TRUE), 
  rect( xy=c(40,40), wh=c(120,40), stroke="black", fill="none"), 
  rect( xy=c(40,40), wh=c(120,40), stroke="black", fill="blue", fill.opacity=0.2 ,
         filter=filter( wh=c(4/3,3/2), xy=c(-1/3, -1/2), 
                        filterUnits="objectBoundingBox",
           feOffset(  dxy=c(-40, -20), in1="SourceGraphic")
         )
  )
)
0 40 80 120 160 200 240 280 320 360 400 440 480 520 560 600 640 680 720 760 800 0 40 80 120





Manipulating Colors

Color Flooding

The feFlood filter is probably the simplest filter. It is used to flood a region with a given color. The feFlood filter is often combined with other filters and rarely used alone. In this section we use feFlood alone, but later sections will use it in combination with other filters.

A simple example of feFlood is

WH=c(800, 100) # window rect
svgR( wh=WH,
  use(
    filter=filter( filterUnits="userSpaceOnUse",
                   feFlood(flood.color='yellow') )
  )
)

The filter as shown above, assigns an filter element to a filter attribute directly. Here we use filterUnits=“userSpaceOnUse” to flood the entire region.

This filter=filter(…) is a shortcut introduced in svgR for programming convienience.

The more traditional approach is to place the filter as a child of a defs element and use a reference to the filter when when assigning to an attribute.

For example

WH=c(800, 100) # window rect
svgR( wh=WH,
  defs(
    filter(id='myFlood', filterUnits="userSpaceOnUse",
           feFlood(flood.color='yellow')
    )
  ),
  use(
    filter="url(#myFlood)"
  )
)

The filter=filter(…) short cut in svgR does a similar construction in the output. However, for complex filters, where filter components are reused the tradional approach might be easier.

In this document we use the shortcut approach.

Our second example of feFlood is to illustrate that a filter has on a rectangular region on which it is effective. That region given by it’s xy, wh coordinates.

WH=c(800, 100) # window rect
svgR( wh=WH,
    text('10 semi-transparent floods', xy=c(5,20), stroke="black", font.size=20),
    rect(cxy=WH/2, wh=c(1,.2)*WH, fill='blue'),
    lapply(1:10, 
      function(i){g(
           filter=filter( xy=c(i*60,10),  wh=c(30,90), filterUnits="userSpaceOnUse",
                    feFlood(flood.color='yellow', flood.opacity='.5'))
           ) 
      }
      )
)
10 semi-transparent floods

Note: In all of the preceding examples we used filterUnits=“userSpaceOnUse”, however the default is filterUnits=“objectBoundingBox”. When applying a this feFlood to an object using the filterUnits=“objectBoundingBox”, the entire region defined by the bounding is flooded with color. To flood the interior of a graphics object, one can additionally employ an feComposite or feBlend for the desired effect.

Rotating Hues

The feColorMatrix is a filter primitive that can be used to rotate color hues

Begining withcollection of red rectangles, we apply a hue rotation to each. The first rectangle, (left most rectangle) is rotated by 0 (degrees) and retains it’s original color. The last rectangle is rotated by 360 (degrees) and so keeps it’s original color. But the intermediate rectangles have their colors remapped according to a color wheel.

WH=c(800, 100) # window rect
svgR( wh=WH,
    lapply(seq(from=0, to=360, by=16), 
      function(i){
        rect(xy=c(i*2,10), wh=c(30,32), fill='red',
           filter=filter( 
             feColorMatrix(
               type="hueRotate", values=i
             )  
           )
        )
      }
    )
)

This time we illustrate hue rotation applied to a rectangles filled with a linear gradient. The left most is the original, and each step to the right rotates that gradient by 16 degrees on the color wheel.

WH=c(800, 100) # window rect
svgR( wh=WH,
    lapply(seq(from=0, to=360, by=16), 
      function(i){
        rect(xy=c(i*2,10), wh=c(30,96), 
          fill=linearGradient( xy1=c(0,0), xy2=c(0,1),
            colors=c('red','yellow','green','blue')),
           filter=filter( filterUnits="userSpaceOnUse",
             feColorMatrix(
               type="hueRotate", values=i
             )  
           )
        )
      }
    )
)

Color Saturation

Color saturation is a measure of preceived color intensity. For example, light from a laser at high intensity having single wave-length as it’s color is considered to be saturated.

The filter primitive element, feColorMatrix that can be used to modify the saturation of a given color by desaturating the original image. (Remove color) The amount of desaturation is specified by a value between 0 and 1, with 0 being no desaturation and 1 being totally desaturated.

Applying satuate to a series of red rectangles we have:

WH=c(800, 100) # window rect
svgR( wh=WH,
    lapply(seq(from=1, to=0, by=-.05), 
      function(i){
        rect(xy=c(i*800,10), wh=c(32,32), fill="red",
           filter=filter( 
             feColorMatrix(
               type="saturate", values=i
             )  
           )
        )
      }
    )
)

The left-most rectanle was given a desaturation value of 1, and the right-most rectangle was given value of 0.

Again, applying hue saturate to a color spectrum generated by a linear gradient. we have

WH=c(800, 100) # window rect
svgR( wh=WH,
    lapply(seq(from=1, to=0, by=-.05),
      function(i){
        rect(xy=c(i*800,10), wh=c(32,96), fill=
          linearGradient( xy1=c(0,0), xy2=c(0,1),
            colors=c('red','yellow','green','blue')),
           filter=filter( 
             feColorMatrix(
               type="saturate", values=i
             ) 
           )
        )
      }
    )
)

Applying a color matrix

(!!To fix)

Applying satuate to a red rectangle

WH=c(800, 100) # window rect
svgR( wh=WH,
    lapply(seq(from=0, to=1, by=.05), 
      function(i){
        rect(xy=c(i*800,10), wh=c(32,32), fill="red",
           filter=filter( 
             feColorMatrix(
               type="matrix", 
               values=paste(runif(25,-1,1),collapse=" ")
             )  
           )
        )
      }
    )
)

feComponentTransfer

Colors can also be remapped using feComponentTransfer and the components feFuncR, feFuncG, feFuncB, feFuncA. The feFuncX’s correspond to the Red, Green, Blue, and Alpha channels. Each channel has a value between between 0 and 1 which can be reassinged using the component transfers. There are four types of color component transfers:

  • table: Reassigns the colors according to a piece-wise continous linear function that is specified by a “table”" values of points. The points are assumend to form a uniform partion of [0,1] so only the values need to be specified. (Using linear interpolation of the values between the points)
  • linear Similar to table, but with only 2 points. The function is specifed using the slope and intercept
  • discrete Reassigns the colors according to a step function that is specified by a “table”" of points. Again the points are assumed to be a uniform partition of [0,1], so only the values need be specified in the “table”." (Between points, the output is the value of the nearest point.
  • gamma The value is given by \(C_{out} = \alpha \times C_{in}^\gamma + \beta\)

feComponentTransfer: Table

We illustrate this with a simple example that interchanges the colors blue and red of a color spectrum.

WH=c(800, 200) # window rect
colors=c('red','blue')
svgR( wh=WH, stroke="black",
  rect(wh=c(.6,.3)*WH, xy=c(.2,0)*WH, 
       fill=linearGradient(colors=colors)
  ),
  rect(wh=c(.6,.3)*WH, xy=c(.2,.3)*WH, 
       fill=linearGradient(colors=colors),
       filter=filter( 
          feComponentTransfer( 
              feFuncR( type="table", tableValues="1 0"),
              feFuncB( type="table", tableValues="1 0 ")
          )
        )
  ),
  text('original', xy=c(20,1.5*WH[2])),
  text('transfered', xy=c(20,4.5*WH[2])),
  text('feComponentTransfer using type="table"', cxy=c(.5,.8)*WH)
)
original transfered feComponentTransfer using type=“table”

feComponentTransfer: Linear

WH=c(800, 200) # window rect
colors=c('red','blue')
svgR( wh=WH, stroke="black",
  rect(wh=c(.6,.3)*WH, xy=c(.2,0)*WH, 
       fill=linearGradient(colors=colors)
  ),
  rect(wh=c(.6,.3)*WH, xy=c(.2,.3)*WH, 
       fill=linearGradient(colors=colors),
       filter=filter( 
          feComponentTransfer( 
              feFuncR( type="linear", intercept=1, slope=-1),
              feFuncB( type="linear", intercept=1, slope=-1)
          )
        )
  ),
  text('original', xy=c(20,1.5*WH[2])),
  text('transfered', xy=c(20,4.5*WH[2])),
  text('feComponentTransfer using type="linear"', cxy=c(.5,.8)*WH)
)
original transfered feComponentTransfer using type=“linear”

feComponentTransfer: Discrete

WH=c(800, 200) # window rect
colors=c('red','blue')
svgR( wh=WH, stroke="black",
  rect(wh=c(.6,.3)*WH, xy=c(.2,0)*WH, 
       fill=linearGradient(colors=colors)
  ),
  rect(wh=c(.6,.3)*WH, xy=c(.2,.3)*WH, 
       fill=linearGradient(colors=colors),
       filter=filter( 
          feComponentTransfer( 
              feFuncR( type="discrete", tableValues="1 0"),
              feFuncB( type="discrete", tableValues="1 0 ")
          )
        )
  ),
  text('original', xy=c(20,1.5*WH[2])),
  text('transfered', xy=c(20,4.5*WH[2])),
  text('feComponentTransfer using type="discrete"', cxy=c(.5,.8)*WH)
)
original transfered feComponentTransfer using type=“discrete”

feComponentTransfer: Gamma exponent=1

WH=c(800, 200) # window rect
colors=c('red','blue')
svgR( wh=WH, stroke="black",
  rect(wh=c(.6,.3)*WH, xy=c(.2,0)*WH, 
       fill=linearGradient(colors=colors)
  ),
  rect(wh=c(.6,.3)*WH, xy=c(.2,.3)*WH, 
       fill=linearGradient(colors=colors),
       filter=filter( 
          feComponentTransfer( 
              feFuncR( type="gamma", offset=1, amplitude=-1, exponent=1),
              feFuncB( type="gamma", offset=1, amplitude=-1, exponent=1)
          )
        )
  ),
  text('original', xy=c(20,1.5*WH[2])),
  text('transfered', xy=c(20,4.5*WH[2])),
  text('feComponentTransfer using type="gamma"', cxy=c(.5,.8)*WH)
)
original transfered feComponentTransfer using type=“gamma”

feComponentTransfer: Gamma, exponent=10

WH=c(800, 200) # window rect
colors=c('red','blue')
svgR( wh=WH, stroke="black",
  rect(wh=c(.6,.3)*WH, xy=c(.2,0)*WH, 
       fill=linearGradient(colors=colors)
  ),
  rect(wh=c(.6,.3)*WH, xy=c(.2,.3)*WH, 
       fill=linearGradient(colors=colors),
       filter=filter( 
          feComponentTransfer( 
              feFuncR( type="gamma", offset=1, amplitude=-1, exponent=2),
              feFuncB( type="gamma", offset=1, amplitude=-1, exponent=2)
          )
        )
  ),
  text('original', xy=c(20,1.5*WH[2])),
  text('transfered', xy=c(20,4.5*WH[2])),
  text('feComponentTransfer using type="table"', cxy=c(.5,.8)*WH)
)
original transfered feComponentTransfer using type=“table”

feComponentTransfer: Inverting the grey scale

WH=c(800, 200) # window rect
colors=c('red','blue')
svgR( wh=WH, stroke="black",
  rect(wh=c(.6,.3)*WH, xy=c(.2,0)*WH, 
       fill=linearGradient(colors=colors)
  ),
  rect(wh=c(.6,.3)*WH, xy=c(.2,.3)*WH, 
       fill=linearGradient(colors=colors),
       filter=filter( 
          feComponentTransfer( 
              feFuncR( type="table", tableValues="1 0"),
              feFuncG( type="table", tableValues="1 0"),
              feFuncB( type="table", tableValues="1 0 ")
          )
        )
  ),
  text('original', xy=c(20,1.5*WH[2])),
  text('transfered', xy=c(20,4.5*WH[2])),
  text('feComponentTransfer using type="table"', cxy=c(.5,.8)*WH)
)
original transfered feComponentTransfer using type=“table”

feComponentTransfer Applied to a Spectrum

WH=c(800, 100) # window rect
svgR( wh=WH,
    lapply(seq(from=1, to=0, by=-.05),
      function(i){
        rect(xy=c(i*800,10), wh=c(32,96), fill=
          linearGradient( xy1=c(0,0), xy2=c(0,1),
            colors=c('red','yellow','green','blue')
          ),
            filter=filter( 
             feComponentTransfer(
               feFuncR(type="linear", slope=i, intercept=1-i),
               feFuncG(type="linear", slope=1, intercept=0),
               feFuncB(type="linear", slope=1-i, intercept=i),
               feFuncA(type="linear", slope=0, intercept=1)
             )
          )
        ) 
      }
    )
)





Combining Filter Primitives

Almost always, more than one filter component is required by the filter, and those components need to be combined. There are three elements that can be used to combine filter components.

feComposite (operator=over)

WH=c(800, 100) # window rect
svgR( wh=WH,
    circle(cxy=WH/2, r=30, stroke='black', fill='red' ,
      filter=filter(  wh=c('200%','100%'), xy=c(0,0), 
        feComposite(  
          operator="over", 
          in1="SourceGraphic",
          in2=feOffset(  dxy=c(20, 0), 
             in1=feColorMatrix( type="hueRotate", values=240, "SourceGraphic")
          )
       )
     )
  )
)

feComposite comparison (operator= over, in, out, atop, xor)

Compariong the operator options over, in, out, atop, xor for feComposite:

feComposite comparison with opacity=1

WH=c(800, 100) # window rect
operators=c("over","in","out","atop","xor")
svgR( wh=WH,
      lapply( 1:5, function(i){
        x=100+(i-1)*150
        g( 
          circle(cxy=c(x,40), r=30, stroke='black', fill='red',
            filter=filter(  wh=c('200%','100%'), xy=c(0,0), 
              feComposite(  
                operator=operators[i], 
                in1="SourceGraphic",
                in2=feOffset(  dxy=c(20, 0), 
                   in1=feColorMatrix( type="hueRotate", values=240, "SourceGraphic")
                )
             )
           )
          ),
          text( operators[i], xy=c(x,82), font.size=15 )
        )
    })
)
over in out atop xor

Here we compare feComposite for an source with opacity=50%

feComposite comparison with opacity=.5

WH=c(800, 100) # window rect
operators=c("over","in","out","atop","xor")
svgR( wh=WH,
      lapply( 1:5, function(i){
        x=100+(i-1)*150
        g( 
          circle(cxy=c(x,40), r=30, stroke='black', fill='red', fill.opacity=0.5,
            filter=filter(  wh=c('200%','100%'), xy=c(0,0), 
              feComposite(  
                operator=operators[i], 
                in1="SourceGraphic",
                in2=feOffset(  dxy=c(20, 0), 
                   in1=feColorMatrix( type="hueRotate", values=240, "SourceGraphic")
                )
             )
           )
          ),
          text( operators[i], xy=c(x,82), font.size=15 )
        )
    })
)
over in out atop xor

feComposite (operator=arithmetic)

The arithmetic operator combines the inputs by summing the individual inputs and their product, and then adds a constant.

The result is given by \[ result= \kappa_1 in_1 in_2 + \kappa_2 in_1 + \kappa_3 in_2 + \kappa_4\]

Thus - k1234=c(0,1,1,0) sums the two components \(in_1\) and \(in_2\). - k1234=c(0,0,0,1) produces white (essentially ignoring the inputs) - k1234=c(0,1,0,0) produces the \(in_1\) ( ignoring the \(in_2\)) - k1234=c(0,0,1,0) produces the \(in_2\) ( ignoring the \(in_1\))

The following example illustrates various combinations of values

WH=c(800, 800) # window rect
WH2=WH/4
kappaXX<-expand.grid( c(0,1),c(0,2),0:1,c(0,-.5))
pos<-expand.grid(0:3,0:3)
svgR( wh=WH,
      lapply( 1:16, function(i){
        xy=pos[i,]*WH2
        kap<-kappaXX[i,]
        svg( xy=xy, wh=WH2,
          #rect(xy=c(5,5), wh=WH2-c(10,10), fill='black'),
          #circle(cxy=WH2/2, r=WH2[1]/8, stroke='black', fill='red'),
          circle(cxy=WH2/2-c(10,0), r=WH2[1]/8, stroke='black', fill='red', #opacity=.5,
            filter=filter(  wh=c("200%","100%"), xy=c(0,0), 
              feComposite(  
                operator= "arithmetic", 
                k1234=as.numeric(kap),
                in1="SourceGraphic",
                in2=feOffset(  dxy=c(20, 0), 
                   in1=feColorMatrix( type="hueRotate", values=140, "SourceGraphic")
                )
              )
            )
          ),
          text( paste(kap, collapse=", "), xy=c(.1,.9)* WH2, font.size=15 , fill='black')
        )
    })
)->doc
cat("'",as.character(doc),"'")

0, 0, 0, 0 1, 0, 0, 0 0, 2, 0, 0 1, 2, 0, 0 0, 0, 1, 0 1, 0, 1, 0 0, 2, 1, 0 1, 2, 1, 0 0, 0, 0, -0.5 1, 0, 0, -0.5 0, 2, 0, -0.5 1, 2, 0, -0.5 0, 0, 1, -0.5 1, 0, 1, -0.5 0, 2, 1, -0.5 1, 2, 1, -0.5

feBlend

Next we consider feBlend

WH=c(800, 100) # window rect
modes=c("normal","multiply","screen","darken","lighten")
svgR( wh=WH,
      lapply( 1:5, function(i){
        x=100+(i-1)*150
        g( 
          circle(cxy=c(x,40), r=30, stroke='black', fill='red', 
            filter=filter(  wh=c('200%','100%'), xy=c(0,0), 
              feBlend(  
                mode=modes[i], 
                in1="SourceGraphic",
                in2=feOffset(  dxy=c(20, 0), 
                   in1=feColorMatrix( type="hueRotate", values=240, "SourceGraphic")
                )
             )
           )
          ),
          text( modes[i], xy=c(x,82), font.size=15 )
        )
    })
)
normal multiply screen darken lighten

feMerge

Finally feMerge

WH=c(800, 100) # window rect
svgR( wh=WH,
    circle(cxy=WH/2, r=30, stroke='black', fill='red' ,
      filter=filter(  wh=c('200%','100%'), xy=c(0,0), 
        feMerge(  
          feMergeNode(in1="SourceGraphic"),
          feMergeNode(in1=feOffset(  dxy=c(20, 0), 
             in1=feColorMatrix( type="hueRotate", values=240, in1="SourceGraphic")
          ))
       )
     )
  )
)

feMerge using defs

WH=c(800, 100) # window rect
svgR( wh=WH,
  defs( 
    filter(  id="myMergeFilter", wh=c('200%','100%'), xy=c(0,0), 
     feColorMatrix( in1='SourceGraphic', type="hueRotate", values=240,  result='myColor'),
     feOffset(dxy=c(20, 0), in1="myColor", result="offed" ),
     feMerge(  
        feMergeNode(in1="SourceGraphic"),
        feMergeNode(in1= "offed")
     )
    )
  ),
  circle(cxy=WH/2, r=50, stroke='black', fill='red' , 
    filter="url(#myMergeFilter)"
  )
)





DropShadows

Drop shadows of a figure can be made by applying alpha channel of an image with a small offset and then combining it with the original image. Specifically we

  1. Apply an offset to the original source using feOffset
  2. Take the result of 1 and apply feGaussianBlur to the Alpha channel of the original source to create the shadow portion.
  3. Combine using feComposite the original source with the result of 2

feBlend

WH=c(800, 120) # window rect
svgR( wh=WH,
  text( 'svgR', xy=c(50,70), font.size=50,  fill="lightblue", stroke="darkblue",
    filter = filter( xy=c(-10,-10), wh=c(200,60),
              feBlend( x=-10, width=200, 
                in1="SourceGraphic", 
                in2=feGaussianBlur( stdDeviation=3, in1=feOffset( dxy=c(3,3), in1="SourceAlpha") )
              )
      )   
  )
)
svgR

Note: Here, for feBlend, in1 is placed on top, in2 is placed below.

Alternatively, we can place the filter definition in a defs statement and refer to the filter by its id.

WH=c(600, 120) # window rect
svgR( wh=WH,
  defs( 
    filter(
      id="blendFilter2", xy=c(-10,-10), wh=c(200,100),
      feOffset( in1="SourceAlpha", dxy=c(3,3), result="offset2"), 
      feGaussianBlur(in1="offset2", stdDeviation=3, result="blur2"),
      feBlend( in1="SourceGraphic",  in2="blur2", x=-10, width=200)
    )         
  ),
  text( 'svgR', xy=c(50,70), font.size=50,  fill="lightblue", stroke="darkblue",
           filter="url(#blendFilter2)")
  
)
svgR

Now suppose that we want to replace the black shadow with a different colored shadow, for example ‘red’. Recall that our shadow came about by applying a feGaussianBlur to a SourceAlpha. But SourceAlpha gives us black. So we need to replace SourceAlpha with something else. One way is to apply a red flood to the source graphic. To limit the flood to SourceGraphic, we will feComposite the flood with the SourceGraphic.

The following demonstrates our approach for obtaining the red text using the flood filter:

#```{r, echo=T}
WH=c(800, 120) # window rect
svgR( wh=WH,
  text( 'svgR', xy=c(50,70), font.size=50,  fill="lightblue", stroke="darkblue",
    filter = filter(
      feComposite(
        operator="in",
        in1=feFlood(flood.color='red', flood.opacity=1),
        in2="SourceGraphic"
      )
    )
  )
)
svgR

Now we replace “SourceAlpha” with the feComposite in our original drop shadow to get

WH=c(800, 120) # window rect
svgR( wh=WH,
  text( 'svgR', xy=c(50,70), font.size=50,  fill="lightblue", stroke='darkblue',
    filter = filter(
      feBlend( x=-10, width=200,
        in1="SourceGraphic", 
        in2=feGaussianBlur( stdDeviation=3,
          in1=feOffset( dxy=c(3,3), 
              in1=feComposite( operator="in",
                  in1=feFlood(flood.color='red', flood.opacity=1), 
                  in2="SourceGraphic"
              )
          )
        )
      )
    )
  )
)
svgR

Some Fun with feComposite

Let’s return to our shadow example

Fun with Drop Shadows

Lets have some fun with drop shadows and feComposite.

First lets change the color again.

WH=c(800, 120) # window rect
svgR( wh=WH,
  text( 'svgR', xy=c(50,70), font.size=50,  fill="orange", stroke='brown',
    filter = filter(
      feBlend( x=-10, width=200,
        in1="SourceGraphic", 
        in2=feGaussianBlur( stdDeviation=3,
          in1=feOffset( dxy=c(3,3), 
              in1=feComposite( operator="in",
                  in1=feFlood(flood.color='blue', flood.opacity=1), 
                  in2="SourceGraphic"
              )
          )
        )
      )
    )
  )
)
svgR

Or stick with blue

WH=c(800, 120) # window rect
svgR( wh=WH,
  text( 'svgR', xy=c(50,70), font.size=50,  fill="lightblue", stroke='blue',
    filter = filter(
      feBlend( x=-10, width=200,
        in1="SourceGraphic", 
        in2=feGaussianBlur( stdDeviation=3,
          in1=feOffset( dxy=c(3,3), 
              in1=feComposite( operator="in",
                  in1=feFlood(flood.color='blue', flood.opacity=1), 
                  in2="SourceGraphic"
              )
          )
        )
      )
    )
  )
)
svgR

Or play with the in operator of feComposite

WH=c(800, 120) # window rect
operators=c("over","in","out","atop","xor")
svgR( wh=WH,
  lapply( 1:5, function(i){
    x=20+(i-1)*150
    g( 
      text( 'svgR', xy=c(x,55), font.size=50,  fill="lightblue", stroke='black',
        filter = filter(
          feComposite( x=x-10, width=200, 
            operator=operators[i],
            in1="SourceGraphic", 
            in2=feGaussianBlur( stdDeviation=5,
              in1=feOffset( dxy=c(3,3), in1="SourceAlpha")
            )
          )
        )
      ),
      text( operators[i], xy=c(x,92), font.size=15 )
    )
  })
)
svgR over svgR in svgR out svgR atop svgR xor

Next we modify the alpha channel

WH=c(800, 120) # window rect
operators=c("over","in","out","atop","xor")
svgR( wh=WH,
  lapply( 1:5, function(i){
    x=20+(i-1)*150
    g( 
      text( 'svgR', xy=c(x,55), font.size=50,  fill="blue", stroke='black',
        filter = filter(
          feComposite( x=x-10, width=200, 
            operator=operators[i],
            in1="SourceGraphic", 
            in2=feComponentTransfer(
             feFuncA( type='table', 
                       tableValues=c(0, .5, 0, .5, 0, .5, 0, .5) 
              ),
              in1=feGaussianBlur( stdDeviation=10, in1="SourceAlpha")
            )
          )
        )
      ),
      text( operators[i], xy=c(x,92), font.size=15 )
    )
  })
)
svgR over svgR in svgR out svgR atop svgR xor

feComponentTransfer

Colors can also be remapped using feComponentTransfer and the components feFuncR, feFuncG, feFuncB, feFuncA. The feFuncX’s correspond to the Red, Green, Blue, and Alpha channels. Each channel has a value between between 0 and 1 which can be reassinged using the component transfers. There are four types of color component transfers:

  • table: Reassigns the colors according to a piece-wise continous linear function that is specified by a “table”" values of points. The points are assumend to form a uniform partion of [0,1] so only the values need to be specified. (Using linear interpolation of the values between the points)
  • linear Similar to table, but with only 2 points. The function is specifed using the slope and intercept
  • discrete Reassigns the colors according to a step function that is specified by a “table”" of points. Again the points are assumed to be a uniform partition of [0,1], so only the values need be specified in the “table”." (Between points, the output is the value of the nearest point.
  • gamma The value is given by \(C_{out} = \alpha \times C_{in}^\gamma + \beta\)

feComponentTransfer: Table

We illustrate this with a simple example that interchanges the colors blue and red of a color spectrum.

WH=c(800, 200) # window rect
colors=c('red','blue')
svgR( wh=WH, stroke="black",
  rect(wh=c(.6,.3)*WH, xy=c(.2,0)*WH, 
       fill=linearGradient(colors=colors)
  ),
  rect(wh=c(.6,.3)*WH, xy=c(.2,.3)*WH, 
       fill=linearGradient(colors=colors),
       filter=filter( 
          feComponentTransfer( 
              feFuncR( type="table", tableValues="1 0"),
              feFuncB( type="table", tableValues="1 0 ")
          )
        )
  ),
  text('original', xy=c(20,1.5*WH[2])),
  text('transfered', xy=c(20,4.5*WH[2])),
  text('feComponentTransfer using type="table"', cxy=c(.5,.8)*WH)
)
original transfered feComponentTransfer using type=“table”

feComponentTransfer: Linear

WH=c(800, 200) # window rect
colors=c('red','blue')
svgR( wh=WH, stroke="black",
  rect(wh=c(.6,.3)*WH, xy=c(.2,0)*WH, 
       fill=linearGradient(colors=colors)
  ),
  rect(wh=c(.6,.3)*WH, xy=c(.2,.3)*WH, 
       fill=linearGradient(colors=colors),
       filter=filter( 
          feComponentTransfer( 
              feFuncR( type="linear", intercept=1, slope=-1),
              feFuncB( type="linear", intercept=1, slope=-1)
          )
        )
  ),
  text('original', xy=c(20,1.5*WH[2])),
  text('transfered', xy=c(20,4.5*WH[2])),
  text('feComponentTransfer using type="linear"', cxy=c(.5,.8)*WH)
)
original transfered feComponentTransfer using type=“linear”

feComponentTransfer: Discrete

WH=c(800, 200) # window rect
colors=c('red','blue')
svgR( wh=WH, stroke="black",
  rect(wh=c(.6,.3)*WH, xy=c(.2,0)*WH, 
       fill=linearGradient(colors=colors)
  ),
  rect(wh=c(.6,.3)*WH, xy=c(.2,.3)*WH, 
       fill=linearGradient(colors=colors),
       filter=filter( 
          feComponentTransfer( 
              feFuncR( type="discrete", tableValues="1 0"),
              feFuncB( type="discrete", tableValues="1 0 ")
          )
        )
  ),
  text('original', xy=c(20,1.5*WH[2])),
  text('transfered', xy=c(20,4.5*WH[2])),
  text('feComponentTransfer using type="discrete"', cxy=c(.5,.8)*WH)
)
original transfered feComponentTransfer using type=“discrete”

feComponentTransfer: Gamma exponent=1

WH=c(800, 200) # window rect
colors=c('red','blue')
svgR( wh=WH, stroke="black",
  rect(wh=c(.6,.3)*WH, xy=c(.2,0)*WH, 
       fill=linearGradient(colors=colors)
  ),
  rect(wh=c(.6,.3)*WH, xy=c(.2,.3)*WH, 
       fill=linearGradient(colors=colors),
       filter=filter( 
          feComponentTransfer( 
              feFuncR( type="gamma", offset=1, amplitude=-1, exponent=1),
              feFuncB( type="gamma", offset=1, amplitude=-1, exponent=1)
          )
        )
  ),
  text('original', xy=c(20,1.5*WH[2])),
  text('transfered', xy=c(20,4.5*WH[2])),
  text('feComponentTransfer using type="gamma"', cxy=c(.5,.8)*WH)
)
original transfered feComponentTransfer using type=“gamma”

feComponentTransfer: Gamma, exponent=10

WH=c(800, 200) # window rect
colors=c('red','blue')
svgR( wh=WH, stroke="black",
  rect(wh=c(.6,.3)*WH, xy=c(.2,0)*WH, 
       fill=linearGradient(colors=colors)
  ),
  rect(wh=c(.6,.3)*WH, xy=c(.2,.3)*WH, 
       fill=linearGradient(colors=colors),
       filter=filter( 
          feComponentTransfer( 
              feFuncR( type="gamma", offset=1, amplitude=-1, exponent=2),
              feFuncB( type="gamma", offset=1, amplitude=-1, exponent=2)
          )
        )
  ),
  text('original', xy=c(20,1.5*WH[2])),
  text('transfered', xy=c(20,4.5*WH[2])),
  text('feComponentTransfer using type="table"', cxy=c(.5,.8)*WH)
)
original transfered feComponentTransfer using type=“table”

feComponentTransfer: Inverting the grey scale

WH=c(800, 200) # window rect
colors=c('red','blue')
svgR( wh=WH, stroke="black",
  rect(wh=c(.6,.3)*WH, xy=c(.2,0)*WH, 
       fill=linearGradient(colors=colors)
  ),
  rect(wh=c(.6,.3)*WH, xy=c(.2,.3)*WH, 
       fill=linearGradient(colors=colors),
       filter=filter( 
          feComponentTransfer( 
              feFuncR( type="table", tableValues="1 0"),
              feFuncG( type="table", tableValues="1 0"),
              feFuncB( type="table", tableValues="1 0 ")
          )
        )
  ),
  text('original', xy=c(20,1.5*WH[2])),
  text('transfered', xy=c(20,4.5*WH[2])),
  text('feComponentTransfer using type="table"', cxy=c(.5,.8)*WH)
)
original transfered feComponentTransfer using type=“table”





Textures

A Simple Example

To create textures, we often employ the feTurbulance filter primitive. The feTurlence supports two types:

  • type=‘turbulence’ (default)
  • type =‘fractal’

A simple example of the former is:

WH=c(800, 100) # window rect
svgR( wh=WH,
      use(
        filter=filter( filterUnits="userSpaceOnUse",
          feTurbulence(baseFrequency=0.1, numOctaves=2 ) 
        )
      )
)

Here we have specified

  • baseFrequency=0.1
  • numOctaves=2

baseFrequency

The effect of base frequence is seen here

WH=c(800, 880) # window rect
N<-99
bfreq<-1:N * .003
svgR( wh=WH,
  text('Base frequencey Effects', xy=c(WH[1]/2, 20)),
  lapply( 1:N, function(i){
    x<-((i-1)%%11)*70
    y<-(floor((i-1)/11))*90+50
    g(
      use( filter=filter( xy=c(x,y), wh=c(60,60), filterUnits="userSpaceOnUse",
          feTurbulence(baseFrequency=bfreq[i], numOctaves=2) 
        ) ),
      text(bfreq[i], xy=c(x,y-10))

    )
  })
)
Base frequencey Effects 0.003 0.006 0.009 0.012 0.015 0.018 0.021 0.024 0.027 0.03 0.033 0.036 0.039 0.042 0.045 0.048 0.051 0.054 0.057 0.06 0.063 0.066 0.069 0.072 0.075 0.078 0.081 0.084 0.087 0.09 0.093 0.096 0.099 0.102 0.105 0.108 0.111 0.114 0.117 0.12 0.123 0.126 0.129 0.132 0.135 0.138 0.141 0.144 0.147 0.15 0.153 0.156 0.159 0.162 0.165 0.168 0.171 0.174 0.177 0.18 0.183 0.186 0.189 0.192 0.195 0.198 0.201 0.204 0.207 0.21 0.213 0.216 0.219 0.222 0.225 0.228 0.231 0.234 0.237 0.24 0.243 0.246 0.249 0.252 0.255 0.258 0.261 0.264 0.267 0.27 0.273 0.276 0.279 0.282 0.285 0.288 0.291 0.294 0.297

The base frequence can also be expressed along the x or y axis independently by seting baseFrequency=c(xFreq, yFreq)

WH=c(800, 800) # window rect
baseFeqRng=1:6
numOctRng=1:6
indx<-expand.grid(baseFeqRng, numOctRng)
xp<-function(i)(i-1)*130+50
yp<-function(j)(j-1)*100+50
svgR( wh=WH,
    text("x-baseFrequency", xy=c(WH[1]/2,10)),
    text("y-baseFrequency", xy=c(10,WH[2]/2), writing.mode='tb'),
    lapply(baseFeqRng, function(i){text(0.005*(i-1), xy=c(xp(i),35))}),
    lapply(numOctRng, function(j){text(0.005*(j-1), xy=c(20,yp(j)+50))}),
    apply(indx, 1, function(z){
      i=z[1];j=z[2]
      use(filter=filter( xy=c(xp(i),yp(j)),  wh=c(120,90), filterUnits="userSpaceOnUse",
            feTurbulence(baseFrequency=0.005*c(i-1,j-1), numOctaves=1) 
          )
        ) 
      })
)
x-baseFrequency y-baseFrequency 0 0.005 0.01 0.015 0.02 0.025 0 0.005 0.01 0.015 0.02 0.025

Octaves

To see the effect of baseFrequency vs numOctaves, we iterate

WH=c(800, 1200) # window rect
baseFeqRng=1:5
numOctRng=1:10
indx<-expand.grid(baseFeqRng, numOctRng)
xp<-function(i)(i-1)*140+50
yp<-function(j)(j-1)*100+50
svgR( wh=WH,
    text("baseFrequency", xy=c(WH[1]/2,10)),
    text("numOctaves", xy=c(10,WH[2]/2), writing.mode='tb'),
    lapply(baseFeqRng, function(i){text(.02*i, xy=c(xp(i),35))}),
    lapply(numOctRng, function(j){text(2*j-1, xy=c(20,yp(j)+50))}),
    apply(indx, 1, function(z){
      i=z[1];j=z[2]
      use(filter=filter( xy=c(xp(i),yp(j)),  wh=c(130,90), filterUnits="userSpaceOnUse",
            feTurbulence(baseFrequency=.02*i, numOctaves=2*j-1 )
          )
        ) 
      })
)
baseFrequency numOctaves 0.02 0.04 0.06 0.08 0.1 1 3 5 7 9 11 13 15 17 19

Types of feTurbulence

The type attribute of feTurbulence can have one of two values:

  • turbulence (the default)
  • fractalNoise

We have seen the turblance type in action, fractalNoise is similar, but the appears to be softer

WH=c(800, 150) # window rect
svgR( wh=WH,
      use(
        filter=filter( xy=c(50,70),wh=c(150,60), filterUnits="userSpaceOnUse",
          feTurbulence(type='turbulence', baseFrequency="0.1", numOctaves="2" ) 
        )
      ),
            use(
        filter=filter( xy=c(250,70),wh=c(150,60), filterUnits="userSpaceOnUse",
          feTurbulence(type='fractalNoise', baseFrequency="0.1", numOctaves="2" ) 
        )
      ),
      text("turbulence", xy=c(100,65)),
      text("fractalNoise", xy=c(300,65))

)
turbulence fractalNoise

Combining feTurbulance

By using feTurbulance, feComponentTransfer and feColorMatrix we can discover some intersting effects. For example, define the following filter

feTurbulance + feComponentTransfer

funkyFilter %<c-% function(id, baseFrequency=.01, numOctaves=3, slope=c(4,4,4), seed=100){
  return(filter( id=id, 
    feTurbulence(baseFrequency=baseFrequency, numOctaves=numOctaves, seed=100),
    feComponentTransfer(
      feFuncR(type="linear", slope=slope[1], intercept=-1),
      feFuncG(type="linear", slope=slope[2], intercept=-1),
      feFuncB(type="linear", slope=slope[3], intercept=-1),
      feFuncA(type="linear", slope=0, intercept=1)
    ),
    feColorMatrix(type="saturate") 
  ))
} 

To see this filter function at work:

bf=3 # This is a kludge to force bf
library(svgR)
WH=c(800, 800) # window rect
N<-4
dH<-WH[2]/(N+1)
y<-0:(N-1)*dH
ww<-WH[1]-40
svgR( wh=WH,
  lapply(1:N, function(i){
    id=paste0("funky1-",i/N)
    bf<<-.02/i # Very bad kludge
    txt<-paste0('funky filter base frequency=', bf)
    g(
      text(txt, xy=c(20, y[i]+35) ),
      rect(xy=c(20,y[i]+60), wh=c(ww, dH-60 ), 
         filter = funkyFilter(id,  bf))
    )      
  })   
)
funky filter base frequency=0.02 funky filter base frequency=0.01 funky filter base frequency=0.00666666666666667 funky filter base frequency=0.005

feTurbulance + feComponentTransfer + feConvolveMatrix

We can sharpen this using an feConvolveMatrix.

sharpAndFunkyFilter %<c-% function(baseFrequency=.01, numOctaves=3, slope=c(4,4,4), seed=100){
  filter( 
    feTurbulence(baseFrequency=baseFrequency, numOctaves=numOctaves, seed=100),
    feComponentTransfer(
      feFuncR(type="linear", slope=slope[1], intercept=-1),
      feFuncG(type="linear", slope=slope[2], intercept=-1),
      feFuncB(type="linear", slope=slope[3], intercept=-1),
      feFuncA(type="linear", slope=0, intercept=1)
    ),
    feColorMatrix(type="saturate"),
    feConvolveMatrix( order=3, kernelMatrix=matrix(c(1,-1,1,-1,-.1,-1,1,-1,1), 3,3))
  )
} 

And the sharpened result is

library(svgR)
WH=c(800, 800) # window rect
N<-4
dH<-WH[2]/(N+1)
y<-0:(N-1)*dH
ww<-WH[1]-40
svgR( wh=WH,
  lapply(1:N, function(i){
    id=paste0("funky2-",i/N)
    bf=.02/(i)
    g(
      text(paste0('sharp and funky filter base frequency=', bf), xy=c(20, y[i]+35) ),
      rect(xy=c(20,y[i]+60), wh=c(ww, dH-60 ), 
         filter = sharpAndFunkyFilter( baseFrequency = bf))
    )      
  })
    
)
sharp and funky filter base frequency=0.02 sharp and funky filter base frequency=0.01 sharp and funky filter base frequency=0.00666666666666667 sharp and funky filter base frequency=0.005

Messing with the base frequency gives a different texture

library(svgR)
WH=c(800, 800) # window rect
N<-4
dH<-WH[2]/(N+1)
y<-0:(N-1)*dH
ww<-WH[1]-40
svgR( wh=WH,
  lapply(1:N, function(i){
    id=paste0("funky2-",i/N)
    bf=c(.1,.1/(i))
    g(
      text(paste0('sharp and funky filter base frequency=', paste(bf, collapse=",") ), xy=c(20, y[i]+35) ),
      rect(xy=c(20,y[i]+60), wh=c(ww, dH-60 ), 
         filter = sharpAndFunkyFilter( baseFrequency = bf))
    )      
  })
    
)
sharp and funky filter base frequency=0.1,0.1 sharp and funky filter base frequency=0.1,0.05 sharp and funky filter base frequency=0.1,0.0333333333333333 sharp and funky filter base frequency=0.1,0.025

Wood

To create a wood texture we use

  • feDisplacementMap
  • feTurbulence
  • feFlood
  • feColorMatrix
WH=c(800, 200) # window rect
m<-rbind( c(1,0,0,0,0), c(0,-1,0,0,0), c(0,0,-1,0,0), c(1,-1,1,0,0))
svgR( wh=WH, 
  rect( cxy=WH/2, wh=WH, stroke.width=2, stroke='black',
    filter = filter(
      feDisplacementMap(  scale=9, xChannelSelector="R", yChannelSelector="B",
        in1=feMerge(
          feMergeNode(in1= feFlood(xy=c(0,0), wh=WH,
          flood.color="#CCAC6C") ),
          feMergeNode(
            in1=feColorMatrix( type="matrix", values=m , 
              in1=feTurbulence( baseFrequency=c(.008,.25), numOctaves=1)
            )
          )
        ),
        in2=feTurbulence( baseFrequency=c(.04,.2), numOctaves=1) 
      )
    ),
    clip.path=clipPath(
      text("Wood", font.size=250, cxy=WH/2, font.face='bold')
    )
  )
)
Wood



Stucco

WH=c(800, 200) # window rect
m<-rbind( c(3,0,0,0,0), c(0,0,0,0,0), c(0,0,-1,0,0), c(1,1,1,0,0))
svgR( wh=WH, 
  rect( cxy=WH/2, wh=WH, stroke.width=2, stroke='black',
    filter = filter(
      feDisplacementMap(  scale=9, xChannelSelector="R", yChannelSelector="B",
        in1=feMerge(
          feMergeNode(in1= feFlood(xy=c(0,0), wh=WH,
          flood.color="#CCDCDC"), flood.opacity=.5 ),
          feMergeNode(
            in1=feColorMatrix( type="matrix", values=m , 
              in1=feTurbulence( baseFrequency=c(.2,.3), numOctaves=1)
            )
          )
        ),
        in2=feTurbulence( baseFrequency=c(.006,.004), numOctaves=2) 
      )
    ),
    clip.path=clipPath(
      text("Stucco", font.size=250, cxy=WH/2, font.face='bold')
    )
  )
)
Stucco
WH=c(800, 200) # window rect
m<-rbind( c(3,0,0,0,0), c(0,0,0,0,0), c(0,0,-1,0,0), c(1,1,1,0,0))
svgR( wh=WH, 
  rect( cxy=WH/2, wh=WH, stroke.width=2, stroke='black',
    filter = filter(
      feDisplacementMap(  scale=9, xChannelSelector="R", yChannelSelector="B",
        in1=feMerge(
          feMergeNode(in1= feFlood(xy=c(0,0), wh=WH,
          flood.color="#FFDCDC"), flood.opacity=.5 ),
          feMergeNode(
            in1=feColorMatrix( type="matrix", values=m , 
              in1=feTurbulence( baseFrequency=c(.5,.1), numOctaves=4)
            )
          )
        ),
        in2=feTurbulence( baseFrequency=c(.006,.004), numOctaves=2) 
      )
    ),
    clip.path=clipPath(
      text("Stucco", font.size=250, cxy=WH/2, font.face='bold')
    )
  )
)
Stucco

Jade

WH=c(800, 200) # window rect
m<-rbind( c(3,0,0,0,0), c(0,0,0,0,0), c(0,0,-1,0,0), c(1,1,1,0,0))
svgR( wh=WH, 
  rect( cxy=WH/2, wh=WH, stroke.width=2, stroke='black',
    filter = filter(
      feDisplacementMap(  scale=9, xChannelSelector="R", yChannelSelector="B",
        in1=feMerge(
          feMergeNode(in1= feFlood(xy=c(0,0), wh=WH,
          flood.color="#CFFCDC"), flood.opacity=.5 ),
          feMergeNode(
            in1=feColorMatrix( type="matrix", values=m , 
              in1=feTurbulence( baseFrequency=c(.1,.1), numOctaves=4)
            )
          )
        ),
        in2=feTurbulence( baseFrequency=c(.06,.004), numOctaves=2) 
      )
    ),
    clip.path=clipPath(
      text("Jade", font.size=250, cxy=WH/2, font.face='bold')
    )
  )
)
Jade

Polished

WH=c(800, 300) # window rect
m<-rbind( c(3,0,0,0,0), c(0,0,0,0,0), c(0,0,-1,0,0), c(1,1,1,0,0))
svgR( wh=WH, 
  rect( cxy=WH/2, wh=WH, stroke.width=2, stroke='black',
    filter = filter(
      feDisplacementMap(  scale=9, xChannelSelector="R", yChannelSelector="B",
        in1=feMerge(
          feMergeNode(in1= feFlood(xy=c(0,0), wh=WH,
          flood.color="#CFFCDC"), flood.opacity=.5 ),
          feMergeNode(
            in1=feColorMatrix( type="matrix", values=m , 
              in1=feTurbulence( baseFrequency=c(.01,.01), numOctaves=1)
            )
          )
        ),
        in2=feTurbulence( baseFrequency=c(.006,.04), numOctaves=2) 
      )
    ),
    clip.path=clipPath(
      text("Shiny", font.size=250, cxy=WH/2, font.face='bold')
    )
  )
)
Shiny





Lighting

Lighting Overview

R R

To produce 3-D effect using lighting/shading, such as seem in the above letter, involves specifying the following three ingredients:

  • The Geometry, a 3-D representation of the objects to display.
  • The Reflectivity Properties, how shiny (or flat) is the object.
  • The Light Sources, the type and or location of the lights.
Each in turn, will be describe in this section. This will be followed by examples illustrating some of the differences.

Geometry

In this section we describe how we represent repesent a shape as 3-dimensional object.

Now SVG shape specifications naturally provide the x and y coordinates. To specify a z coordinate, a small trick is used, namely use the alpha channel of the pixel at the point x,y.

Specifically

\[ z(x,y)= surfaceScale \times I_\alpha(x,y) \]

This produces a 3-Dimensional surface.
(Recall, each pixel consists of 4 coordinates, the first 3 being Red, Green, Blue and the last being \(\alpha\) which is value between 0 and 1 repesenting the opacity of the pixel.)

As an example consider the gradient fill where we set the opacity:

library(svgR)
WH=c(800, 400) # window rect
R<-150
svgR( wh=WH, 
    circle( cxy=WH/2, r= R,  stroke="black" , stroke.width=5, 
        fill = radialGradient( fxy=c(.5,.5), r=1, spreadMethod='pad',
          stop( offset=1/6,    stop.opacity=1),
          stop( offset=1/3,    stop.opacity=0)
        )
    )
 )

Then profile of this surface going through the center would look like

surfaceScale

Since a Gaussion blur creates it’s effect by adjusting opacity, it too can be use to create a surface.

library(svgR)
WH=c(800, 400) # window rect
R<-150
svgR( wh=WH, 
    circle( cxy=WH/2, r= R/2,  
      filter=filter( xy=c(-20,-20), wh=c(R,R)+40, feGaussianBlur( stdDeviation=20 ))
    )
)

The profile is then surfaceScale

So specifying a surfaceScale together with an alpha channel of an image together provides a way to describe a surface in 3 dimensions.

The next step is to compute a unit normal vector \(\hat n\) at each point \((x,y)\). This is needed to compute reflectivity as described in the next section. The normal vector is approximated at a point \((x,y)\) by fitting a plane through a set of points in a neighborhood of (x,y). The set of points used generated from the grid \((x-dx, x, x+dx) \times (y-dy, y, y+dy)\). The values of \(dx, dy\) are specified through the kernelUnitLength attribute. The value can be either be specifed as kernelUnitLength=c(dx, dy) or kernelUnitLength=dd. In the later case dx, dy are both set to dd. If no kernelUnitLength is specified, then a small value is chosen as a default.

Both surfaces can be combined into a single image

library(svgR)
WH=c(800, 300) # window rect
R<-70
elevation<-40 #in degrees
azimuth<-270 # in degrees
svgR( wh=WH,
  g(
    rect(cxy=WH/2, wh=WH, opacity=0),
    circle( cxy=c(.35,.5)*WH, r= R,  stroke="black" , stroke.width=1, 
      fill = radialGradient( fxy=c(.5,.5), r=1, spreadMethod='pad',
        stop( offset=1/6,    stop.opacity=1),
        stop( offset=1/3,    stop.opacity=.0)
      )
    ),
    circle( cxy=c(.65,.5)*WH, r= R/2,  
      filter=filter( xy=c(-20,-20), wh=c(R,R)+40, feGaussianBlur( stdDeviation=20 ))
    ),
    filter=filter(
            feComposite( 
              operator="arithmetic", k1234=c(0,.9,1,0),
              in1=feSpecularLighting( lighting.color="white",  surfaceScale=15,
                  in1="SourceGraphic",
                  specularExponent=10,
                  feDistantLight(elevation=elevation, azimuth=azimuth)
              ),
              in2=  feFlood( flood.color='#357A55')
            )
    )
  ),
  text('Combining to create a single surface', cxy=c(.5,.1)*WH, font.size=25),
  text('Portion made using a Radial Gradient',cxy=c(.25,.9)*WH),
  text('Portion made using a Gaussian Blur',  cxy=c(.75,.9)*WH)
)
Combining to create a single surface Portion made using a Radial Gradient Portion made using a Gaussian Blur

Summarizing

The geometry of the surface requires

  • A specification of an alpha channel (opacity describing the surface)
  • A specification of a surfaceScale value
  • A kernelUnitLength specification (optional)

Reflective Properties for lighting/shading

Reflective properties are the second piece of the lighting/shading construction. Objects, such as a coffee cup, a book, or a dog, ar visible because of light reflected off of them. The light produced by these reflections can be decomposed into 3 types:

Type Description Main Assumption
Ambient Light Uniform Light is constant at all points
Diffused Light Flat Light reflected in all directions at a given point
Specular Light Shinny Light is reflected in a direction depending on source

In practice, a combination of all three components may be applied. Combining these can be accomplished by using feComposite, usually with operator=arithmetic.

R R Ambient R R Diffuse R R Pure Specular R R Combined

Now since ambient light is uniform, simply assigning a color to an object produces the ambient effect. So we only need to consider diffuse and specular.

Diffuse Reflection

The main assumption for diffuse lighting is that light from any direction is reflected uniformly in all directions.
Thus the only thing that affects the amount light recieved is how light is recieved by the surface of the object at that location. As an illustration, suppose we have a uniformly distribute collection of L parallel light rays (or photons if you prefer) impinging on flat surface at a fixed angle \(\theta\). The intensity of this source is simply the denisty, ie L/A where A is the area cross section perpendicular to the path of the incoming rays. The reflected output is uniform across the flat surface. The area of the flat surface is \(A cos(\theta)\), so the density impinging on the surface is \(\frac{L cos(\theta) }{A}\).

A I=K cos(θ)/A I=K/A l n θ θ

And since, by the main assumption, the reflection is uniform in all directions, at the observation point (the eyeball of the viewer) the reflected amount observed will be some constant \(\kappa\) fraction of
\(\frac{L cos(\theta) }{A}\). Thus \(I_{reflect}= cos(\theta)I_{source}\). Thus we have \[I= \kappa \times \max (0, \hat n \cdot \hat l ) L\] where \(\hat n\) is a unit normal to the surface, \(\hat l\) is a unit vector point at the light source and \(L\) is the intensity of the light source. Note: \(\kappa \in [0,1]\), where \(\kappa==0\) means no light is reflected.

In practice, a pixel has 4 components: Red, Green, Blue, Alpha. (Recall Alpha is opacity) So the above becomes

\[ I_x = \left\{ \begin{array}{ll} \kappa_x \times \max (0, \hat n \cdot \hat l) L_x & \text{ if } x \in RGB \\ 1 & \text{ if } x = \alpha \end{array} \right. \]

Thus for \(\vec \kappa=(1,0,0)\) we have red light is reflected, green and blue are completely absorbed.

The attributes specific to feDiffuseLighting element are

Attribute Value Default Description
surfaceScale numeric 1 height of surface when \(\alpha_{in}=1\)
diffuseConstant numeric 1 \(\kappa_s\)
kernelUintLength numeric vector of length 1 or 2 small granularity of facets

feDiffuseLighting: surface scale

library(svgR)
WH<-c(800, 300) # window rect
N=5
R<-50
elevation<-40 # in degrees
azimuth<-270 # in degrees
WH2<-c(1/N,1)*(WH-c(0,25)-c(N*10,0))
surfaceScale<-seq(10,90, length.out=5)
svgR(wh=WH,
    text("Varying surface scale for feDiffuseLighting (light at 0,0,90)", font.size=20, cxy=c(WH[1]/2,30)),
    lapply(1:5, function(i){
      xy=(i-1)*c(WH2[1]+10,0)
      svg(  xy=xy, wh=WH2,
        circle( cxy=WH2/2, r= R,  stroke="black" , stroke.width=5, 
            fill = "darkgreen",
            filter=filter(
                feComposite( 
                  operator="arithmetic", k1234=c(0,.4,1,0),
                  in1=feDiffuseLighting( lighting.color="white",  surfaceScale=surfaceScale[i],
                      in1=feGaussianBlur( stdDeviation=10),
                      fePointLight( xyz= c(0,0,90) ) 
                  ),
                  in2=  "SourceGraphic"
                )
            )
        ),
        text(paste("surfaceScale=",surfaceScale[i]), cxy=c(WH2[1]/2,WH2[2]-30),  font.size=14)
      )
    })
)->doc
cat("'",as.character(doc),"'")

Varying surface scale for feDiffuseLighting (light at 0,0,90) surfaceScale= 10 surfaceScale= 30 surfaceScale= 50 surfaceScale= 70 surfaceScale= 90

To eliminate the backGround shadowing, simply add a clipPath to the circle.

library(svgR)
WH<-c(800, 300) # window rect
N=5
R<-50
elevation<-40 # in degrees
azimuth<-270 # in degrees
WH2<-c(1/N,1)*(WH-c(0,25)-c(N*10,0))
surfaceScale<-seq(10,90, length.out=5)
svgR(wh=WH,
    text("Varying surface scale for feDiffuseLighting (clipping)", font.size=20, cxy=c(WH[1]/2,30)),
    lapply(1:5, function(i){
      xy=(i-1)*c(WH2[1]+10,0)
      svg(  xy=xy, wh=WH2,
        circle( cxy=WH2/2, r= R,  stroke="black" , stroke.width=5, 
            fill = "darkgreen",
            filter=filter(
                feComposite( 
                  operator="arithmetic", k1234=c(0,.4,1,0),
                  in1=feDiffuseLighting( lighting.color="white",  surfaceScale=surfaceScale[i],
                      in1=feGaussianBlur( stdDeviation=10),
                      fePointLight( xyz= c(0,0,90) ) 
                  ),
                  in2=  "SourceGraphic"
                )
            ),
            clip.path=clipPath(circle(cxy=WH2/2,r=R))
        ),
        text(paste("surfaceScale=",surfaceScale[i]), cxy=c(WH2[1]/2,WH2[2]-30),  font.size=14)
      )
    })
)->doc
cat("'",as.character(doc),"'")

Varying surface scale for feDiffuseLighting (clipping) surfaceScale= 10 surfaceScale= 30 surfaceScale= 50 surfaceScale= 70 surfaceScale= 90

feDiffuseLighting: diffusionConstant

library(svgR)
WH<-c(800, 300) # window rect
N=5
R<-50
elevation<-40 # in degrees
azimuth<-270 # in degrees
WH2<-c(1/N,1)*(WH-c(0,25)-c(N*10,0))
diffuseConstant<-seq(0,2, length.out=5)
svgR(wh=WH,
    text("feDiffuseLighting Varying diffusion constant", font.size=20, cxy=c(WH[1]/2,30)),
    lapply(1:5, function(i){
      xy=(i-1)*c(WH2[1]+10,0)
      svg(  xy=xy, wh=WH2,
        circle( cxy=WH2/2, r= R,  stroke="black" , stroke.width=5, 
            fill = "darkgreen",
            filter=filter(
                feComposite( 
                  operator="arithmetic", k1234=c(0,.4,1,0),
                  in1=feDiffuseLighting( lighting.color="white",  surfaceScale=50, 
                      diffuseConstant=diffuseConstant[i],
                      in1=feGaussianBlur( stdDeviation=10),
                      fePointLight( xyz= c(0,0,90) ) 
                  ),
                  in2=  "SourceGraphic"
                )
            ),
            clip.path=clipPath(circle(cxy=WH2/2,r=R))
        ),
        text(paste("diffuseConstant=",diffuseConstant[i]), cxy=c(WH2[1]/2,WH2[2]-30),  font.size=14)
      )
    })
)->doc
cat("'",as.character(doc),"'")

feDiffuseLighting Varying diffusion constant diffuseConstant= 0 diffuseConstant= 0.5 diffuseConstant= 1 diffuseConstant= 1.5 diffuseConstant= 2

Specular Reflection (The Blin-Phong Model)

Recall from Phyics, for a perfect mirror, the angle of incidence equals the angle for reflection, where angle is measured from a vector normal vector to the surface. Now consider a single ray of light striking the surface at point p with angle \(\theta\) from the normal.

source reflection eye of viewer r n i v

Here, \(\hat l\) is a unit vector in the direction of the light source, \(\hat r\) is the unit vector in the direction of the reflected light at point p, \(\hat n\) is the unit normal vector to the surface, and \(\hat v\) is a unit vector pointing to the viewer. With a little algebra, we see \[ \hat r = (2 \hat n \cdot \hat l ) \hat n - \hat l\]

But according to this model, the point p will appear dark unless \(\hat v == \hat r\) precisely. i.e. \[ I = \left\{ \begin{array}{ll} \kappa \times L & \text{ if } \hat v = \hat r \\ 0 & otherwise \end{array} \right. \]

In practice, this is too restrictive, what is needed is to spread out the reflection. One approach is to consider the vector \[ \hat h = \frac{ \hat v + \hat l}{ \| \hat v + \hat l \| } \] Note, \(\hat h=\hat n\) if and only if \(\hat v = \hat r\). This suggests that we use a similarity measure of \(\hat h\) to \(\hat n\) as a coeffienct in our model. One such measure is the dot product, \(\hat h \cdot \hat n\). In practice, we use the dot product raised to a power s.

i.e. \[I = \kappa \times ( \hat h \cdot \hat n )^s L \]

source reflection eye of viewer r n i v h h.n

The exponent s allows us to control the degree of shininess, i.e. \(s=0\) is essentially ambient light, and as \(s\) approaches \(\infty\) the reflection becomes more mirror like.

Implict in our argument is the assumption that the viewer is at infinity in the z direction (i.e., the unit vector in the eye direction is (0,0,1) everywhere).

In terms of the 4 pixel components: Red, Green, Blue, Alpha. This becomes

\[ \begin{array}{ll} I_R & = \kappa_R \times ( \hat n \cdot \hat h )^s \times L_R \\ I_G & = \kappa_G \times ( \hat n \cdot \hat h )^s \times L_G \\ I_B & = \kappa_B \times ( \hat n \cdot \hat h )^s \times L_B \\ I_\alpha & = \max ( I_R, I_G, I_B) \end{array} \]

The result of a specular filter is meant to be added ( possibly to a textured image), so the \(\alpha\) channel depends on the max intensity of components reflected. In particular, if IR,IG,IB is black at a given point, then the specular filter result is transparent at that point. If IR,IG,IB is white , then the specular filter result opaque. In this way we can layer the specular result on top to produce the desired shiny highlights without destroying the image.

The feSpecularLighting filter can be used to provide shinyness to an object.

The attributes specific to feSpecularLighting element are

Attribute Value Default Description
surfaceScale numeric 1 height of surface when \(\alpha_{in}=1\)
specularConstant numeric 1 \(\kappa_s\) from the Phong model
specularExponent numeric in range 1-128 1 Exponent of specular term
kernelUintLength numeric vector pair small granularity of facets

feSpecularLighting: surfaceScale

library(svgR)
WH<-c(800, 300) # window rect
N=5
R<-50
elevation<-40 # in degrees
azimuth<-270 # in degrees
WH2<-c(1/N,1)*(WH-c(0,25)-c(N*10,0))
surfaceScale<-seq(5,25, length.out=5)
svgR(wh=WH,
    text("Varying surface scale for feSpecularLighting", font.size=20, cxy=c(WH[1]/2,30)),
    lapply(1:5, function(i){
      xy=(i-1)*c(WH2[1]+10,0)
      svg(  xy=xy, wh=WH2,
        circle( cxy=WH2/2, r= R,  stroke="black" , stroke.width=5, 
            fill = "darkgreen",
            filter=filter(
                feComposite( 
                  operator="arithmetic", k1234=c(0,1,1,0),
                  in1=feSpecularLighting( lighting.color="white",  
                      surfaceScale=surfaceScale[i],
                      specularExponent=4,
                      in1=feGaussianBlur( stdDeviation=10),
                      fePointLight( xyz= c(0,0,90) ) 
                  ),
                  in2=  "SourceGraphic"
                )
            ),
            clip.path=clipPath(circle(cxy=WH2/2,r=R))
        ),
        text(paste("surfaceScale=",surfaceScale[i]), cxy=c(WH2[1]/2,WH2[2]-30),  font.size=14)
      )
    })
)->doc
cat("'",as.character(doc),"'")

Varying surface scale for feSpecularLighting surfaceScale= 5 surfaceScale= 10 surfaceScale= 15 surfaceScale= 20 surfaceScale= 25

feSpecularLighting: specularConstant

library(svgR)
WH<-c(800, 300) # window rect
N=5
R<-50
elevation<-40 # in degrees
azimuth<-270 # in degrees
WH2<-c(1/N,1)*(WH-c(0,25)-c(N*10,0))
specularConstant<-seq(.1,1, length.out=5)
svgR(wh=WH,
    text("Varying specularConstant for feSpecularLighting", font.size=20, cxy=c(WH[1]/2,30)),
    lapply(1:5, function(i){
      xy=(i-1)*c(WH2[1]+10,0)
      svg(  xy=xy, wh=WH2,
        circle( cxy=WH2/2, r= R,  stroke="black" , stroke.width=5, 
            fill = "darkgreen",
            filter=filter(
                feComposite( 
                  operator="arithmetic", k1234=c(0,1,1,0),
                  in1=feSpecularLighting( lighting.color="white",  
                      surfaceScale=15,
                      specularExponent=4,
                      specularConstant=specularConstant[i],
                      in1=feGaussianBlur( stdDeviation=10),
                      fePointLight( xyz= c(0,0,90) ) 
                  ),
                  in2=  "SourceGraphic"
                )
            ),
            clip.path=clipPath(circle(cxy=WH2/2,r=R))
        ),
        text(paste("specularConstant=",specularConstant[i]), cxy=c(WH2[1]/2,WH2[2]-30),  font.size=14)
      )
    })
)->doc
cat("'",as.character(doc),"'")

Varying specularConstant for feSpecularLighting specularConstant= 0.1 specularConstant= 0.325 specularConstant= 0.55 specularConstant= 0.775 specularConstant= 1

feSpecularLighting: specularExponent

library(svgR)
WH<-c(800, 300) # window rect
N=5
R<-50
elevation<-40 # in degrees
azimuth<-270 # in degrees
WH2<-c(1/N,1)*(WH-c(0,25)-c(N*10,0))
specularExponent<-seq(.5,5, length.out=5)
svgR(wh=WH,
    text("Varying specularExponent for feSpecularLighting", font.size=20, cxy=c(WH[1]/2,30)),
    lapply(1:5, function(i){
      xy=(i-1)*c(WH2[1]+10,0)
      svg(  xy=xy, wh=WH2,
        circle( cxy=WH2/2, r= R,  stroke="black" , stroke.width=5, 
            fill = "darkgreen",
            filter=filter(
                feComposite( 
                  operator="arithmetic", k1234=c(0,1,1,0),
                  in1=feSpecularLighting( lighting.color="white",  
                      surfaceScale=15,
                      specularExponent=specularExponent[i],
                      specularConstant=.6,
                      in1=feGaussianBlur( stdDeviation=10),
                      fePointLight( xyz= c(0,0,90) ) 
                  ),
                  in2=  "SourceGraphic"
                )
            ),
            clip.path=clipPath(circle(cxy=WH2/2,r=R))
        ),
        text(paste("s=",specularExponent[i]), cxy=c(WH2[1]/2,WH2[2]-30),  font.size=14)
      )
    })
)->doc
cat("'",as.character(doc),"'")

Varying specularExponent for feSpecularLighting s= 0.5 s= 1.625 s= 2.75 s= 3.875 s= 5

Summary

The feDiffuseLighting and feSpecularLighting filters will often be applied together. An implementation may detect this and calculate both maps in one pass, instead of two.

  • feDiffuseLighting and feSpecularLighting
    • often applied togther
    • surfaceScale is required for both to provide the depth for the image
    • both use a constant to regulate the intensity, but with different names
  • feDiffuseLighting creates non-glossy effects
    • diffuseConstant (a single number) regulates the intensity of the diffuse light
    • produces an opaque results
  • feSpecularLighting creates glossy effects
    • specularConstant (a single number) regulates the intensity of the specular light
    • produces an non-opaque results, where the specular light is zero, no additional coverage is added to the image and a fully white highlight will add opacity

Light Sources

There are 3 possible of light sources:

  • feDistantLight, which emulates a distant light source
  • fePointLight which emulates a point light source
  • feSpotLight which emulates a spot light.
library(svgR)
WH<-c(800, 200) # window rect
cxy<-rbind(seq(0,WH[1],length.out=2*4+1)[2*(1:4)],WH[2]/2)
txt<-c("No Light","feDistantLight","fePointLight","feSpotLight")
lighting %<c-% function(lightsource){
  filter( feComposite( operator="arithmetic", k1234=c(1,0,0,0),
    in1="SourceGraphic",
    in2=feDiffuseLighting(in1="SourceGraphic", light.color="white", lightsource)
  ))
}
svgR( wh=WH,
    circle(cxy=cxy[,1], r=50, fill="green"), 
    circle(cxy=cxy[,2], r=50, fill="green", 
      filter=lighting(feDistantLight(azimuth=240, elevation=20) )
    ),     
    circle(cxy=cxy[,3], r=50, fill="green", 
      filter=lighting(fePointLight( xyz=20*c(-1,-1,1)+ c(cxy[,3],0) ) )
    ),     
    circle(cxy=cxy[,4], r=50, fill="green", 
      filter=lighting( feSpotLight(  xyz=20*c(-1,-1,1) + c(cxy[,4],0), 
        limitingConeAngle=20,pointsAtXYZ=20*c(1,1,0) + c(cxy[,4],0)
      ))
    ),
    lapply(1:4, function(i)text(txt[i], cxy=c(cxy[1,i], WH[2]-20)))
)
No Light feDistantLight fePointLight feSpotLight

feDistantLight

A distant light is assumed to be very bright and distant, so that the light rays striking the surface are essentially parallel. In particular, it is assumed that the location of the light source spherical coordinates has a large fixed value for \(\rho\). Thus to specify the location of a distant light source only two coordinates, \(\theta\), \(\phi\) are required. However, instead of standard mathematical terminology, it was decided feDistantLight to use azimuth (=\(\theta\)) and elevation (=\(\pi/2 -\phi\)). Also, in deference to the Babylonians, degrees are used instead of radians.

Thus the attributes for feDistantLight are

Attribute Value Description
elevation numeric angle in degrees from the xy-plane
azimuth numeric 1

Varying DistantLight Positions: Diffuse

library(svgR)
WH<-c(800, 1100) # window rect
Gdim=c(5,5) # num cols abd cil
marg<-c(10,40)# spaceing between
XY<-expand.grid(
  seq(marg[1],WH[1],length.out=Gdim[1]+1)[1:Gdim[1]], 
  seq(marg[2],WH[2],length.out=Gdim[2]+1)[1:Gdim[2]] 
)
WH2<-XY[Gdim[1]+2,]-XY[1,]-marg*c(1,0)
ccxy<-c(.5,.4)*WH2
R<-70
diffuseConstant<-1
ea<-expand.grid(
  seq(0,  75, length.out=Gdim[1]), #elevation
  seq( 0, 330, length.out=Gdim[2]) #azimuth
)
svgR(wh=WH,
    text("Varying feDistantLight Position with feDiffuseLighting", font.size=20, cxy=c(WH[1]/2,30)),
    lapply(1:nrow(XY), function(i){
      svg(  xy=XY[i,], wh=WH2,
        circle( cxy=ccxy, r= R,  stroke="black" , stroke.width=5, 
            fill = "darkgreen",
            filter=filter(
                feComposite( 
                  operator="arithmetic", k1234=c(0,.4,1,0),
                  in1=feDiffuseLighting( lighting.color="white",  surfaceScale=50, 
                      diffuseConstant=diffuseConstant,
                      in1=feGaussianBlur( stdDeviation=10),
                      feDistantLight( elevation= ea[i,1], azimuth=ea[i,2] ) 
                  ),
                  in2=  "SourceGraphic"
                )
            ),
            clip.path=clipPath(circle(cxy=ccxy,r=R+3))
        ),
        text(paste("elevation=", ea[i,1] ), xy=c(.1,.85)* WH2,  font.size=14),
        text(paste("azimuth=",   ea[i,2] ), xy=c(.1, .95)* WH2,  font.size=14)
      )
    })
)->doc
cat("'",as.character(doc),"'")

Varying feDistantLight Position with feDiffuseLighting elevation= 0 azimuth= 0 elevation= 18.75 azimuth= 0 elevation= 37.5 azimuth= 0 elevation= 56.25 azimuth= 0 elevation= 75 azimuth= 0 elevation= 0 azimuth= 82.5 elevation= 18.75 azimuth= 82.5 elevation= 37.5 azimuth= 82.5 elevation= 56.25 azimuth= 82.5 elevation= 75 azimuth= 82.5 elevation= 0 azimuth= 165 elevation= 18.75 azimuth= 165 elevation= 37.5 azimuth= 165 elevation= 56.25 azimuth= 165 elevation= 75 azimuth= 165 elevation= 0 azimuth= 247.5 elevation= 18.75 azimuth= 247.5 elevation= 37.5 azimuth= 247.5 elevation= 56.25 azimuth= 247.5 elevation= 75 azimuth= 247.5 elevation= 0 azimuth= 330 elevation= 18.75 azimuth= 330 elevation= 37.5 azimuth= 330 elevation= 56.25 azimuth= 330 elevation= 75 azimuth= 330

Varying DistantLight Positions: Specular

library(svgR)
WH<-c(800, 1100) # window rect
Gdim=c(5,5) # num cols abd cil
marg<-c(10,40)# spaceing between
XY<-expand.grid(
  seq(marg[1],WH[1],length.out=Gdim[1]+1)[1:Gdim[1]], 
  seq(marg[2],WH[2],length.out=Gdim[2]+1)[1:Gdim[2]] 
)
WH2<-XY[Gdim[1]+2,]-XY[1,]-marg*c(1,0)
ccxy<-c(.5,.4)*WH2
R<-70
specularConstant<-1
specularExponent<-5
ea<-expand.grid(
  seq(0,  55, length.out=Gdim[1]), #elevation
  seq( 0, 330, length.out=Gdim[2]) #azimuth
)
#xyz<-expand.grid(-1:1, 1:0, 1:3)
svgR(wh=WH,
    text("Varying feDistantLight Position with feSpecularLighting", font.size=20, cxy=c(WH[1]/2,30)),
    lapply(1:nrow(XY), function(i){
      svg(  xy=XY[i,], wh=WH2,
        circle( cxy=ccxy, r= R,  stroke="black" , stroke.width=5, 
            fill = "darkgreen",
            filter=filter(
                feComposite( 
                  operator="arithmetic", k1234=c(0,.4,1,0),
                  in1=feSpecularLighting( lighting.color="white",  surfaceScale=50,
                      specularExponent=specularExponent,
                      specularConstant=specularConstant,
                      in1=feGaussianBlur( stdDeviation=10),
                      feDistantLight( elevation= ea[i,1], azimuth=ea[i,2] ) 
                  ),
                  in2=  "SourceGraphic"
                )
            ),
            clip.path=clipPath(circle(cxy=ccxy,r=R+3))
        ),
        text(paste("elevation=", ea[i,1] ), xy=c(.1,.85)* WH2,   font.size=14),
        text(paste("azimuth=",   ea[i,2] ), xy=c(.1, .95)* WH2,  font.size=14)
      )
    })
)->doc
cat("'",as.character(doc),"'") 

Varying feDistantLight Position with feSpecularLighting elevation= 0 azimuth= 0 elevation= 13.75 azimuth= 0 elevation= 27.5 azimuth= 0 elevation= 41.25 azimuth= 0 elevation= 55 azimuth= 0 elevation= 0 azimuth= 82.5 elevation= 13.75 azimuth= 82.5 elevation= 27.5 azimuth= 82.5 elevation= 41.25 azimuth= 82.5 elevation= 55 azimuth= 82.5 elevation= 0 azimuth= 165 elevation= 13.75 azimuth= 165 elevation= 27.5 azimuth= 165 elevation= 41.25 azimuth= 165 elevation= 55 azimuth= 165 elevation= 0 azimuth= 247.5 elevation= 13.75 azimuth= 247.5 elevation= 27.5 azimuth= 247.5 elevation= 41.25 azimuth= 247.5 elevation= 55 azimuth= 247.5 elevation= 0 azimuth= 330 elevation= 13.75 azimuth= 330 elevation= 27.5 azimuth= 330 elevation= 41.25 azimuth= 330 elevation= 55 azimuth= 330

PointLight

A point light is a light source located at a single point in space. As a result, the light rays are not parallel, but rather spread out. Hence, even across a flat surface, the light is not distributed uniformly.

Three coordinates are required to locate a point light source, x, y, z.

Varying PointLight Positions: Diffuse
library(svgR)
W<-800
NXYZ<-c(4,2,3)
GDIM=c(NXYZ[1], NXYZ[2]* NXYZ[3]) # = num cols , num rows
WH=c(W, 40+1.3*W*GDIM[2]/GDIM[1])  # compute WH of svg
R<-.5*(W/GDIM[1])
X<-seq(0, WH[1], length.out=GDIM[1]+1)
Y<-seq(40,WH[2], length.out=GDIM[2]+1)
CXY<-expand.grid( 
  apply(rbind(X[1:(length(X)-1)], X[2:length(X)] ), 2, mean),
  apply(rbind(Y[1:(length(Y)-1)], Y[2:length(Y)] ), 2, mean)
)
XYZ<-expand.grid(
  seq(-R,R, length.out=NXYZ[1]), # X coords
  seq(-R,0, length.out=NXYZ[2]), #Y coords
  seq(1.2*R, 1.5*R, length.out=NXYZ[3]) #Z occords
)
svgR(wh=WH+c(0,60),
    text("Varying fePointLight Position with feDiffuseLighting", font.size=20, cxy=c(WH[1]/2,30)),
    text("(Coordinates of light source relative to object center)", font.size=16, cxy=c(WH[1]/2,55)),
    lapply(1:nrow(CXY), function(i){
      xyz=XYZ[i,,]
      g( transform=list(translate=CXY[i,]),
        circle( cxy=c(0,0), r= R-20,  stroke="black" , stroke.width=5, 
            fill = "darkgreen",
            filter=filter(
                feComposite( 
                  operator="arithmetic", k1234=c(0,.4,1,0),
                  in1=feDiffuseLighting( lighting.color="white",  surfaceScale=R, 
                      diffuseConstant=diffuseConstant,
                      in1=feGaussianBlur( stdDeviation=10),
                      fePointLight( xyz= xyz) 
                  ),
                  in2=  "SourceGraphic"
                )
            ),
            clip.path=clipPath(circle(cxy=c(0,0),r=R-18))
        ),
        lapply(1:3, function(i){ 
          text(sprintf(c("x= %.1f", "y= %.1f", "z= %.1f")[i], xyz[i]), cxy=c(0, R+i*16),  font.size=14)
        })
      )
    })
)
Varying fePointLight Position with feDiffuseLighting (Coordinates of light source relative to object center) x= -100.0 y= -100.0 z= 120.0 x= -33.3 y= -100.0 z= 120.0 x= 33.3 y= -100.0 z= 120.0 x= 100.0 y= -100.0 z= 120.0 x= -100.0 y= 0.0 z= 120.0 x= -33.3 y= 0.0 z= 120.0 x= 33.3 y= 0.0 z= 120.0 x= 100.0 y= 0.0 z= 120.0 x= -100.0 y= -100.0 z= 135.0 x= -33.3 y= -100.0 z= 135.0 x= 33.3 y= -100.0 z= 135.0 x= 100.0 y= -100.0 z= 135.0 x= -100.0 y= 0.0 z= 135.0 x= -33.3 y= 0.0 z= 135.0 x= 33.3 y= 0.0 z= 135.0 x= 100.0 y= 0.0 z= 135.0 x= -100.0 y= -100.0 z= 150.0 x= -33.3 y= -100.0 z= 150.0 x= 33.3 y= -100.0 z= 150.0 x= 100.0 y= -100.0 z= 150.0 x= -100.0 y= 0.0 z= 150.0 x= -33.3 y= 0.0 z= 150.0 x= 33.3 y= 0.0 z= 150.0 x= 100.0 y= 0.0 z= 150.0

Varying PointLight Positions: Diffuse , constant dist.

Here we repeat, but with a constant distance from the center

Varying PointLight Positions: Diffuse

library(svgR)
W<-800
GDIM=c(5,3) # = num cols , num rows
vMargin<-90 #spacing for Title, and spaceing at bottom
WH=c(W, 2*vMargin + 1.3*W*GDIM[2]/GDIM[1])  # compute WH of svg
R<-.5*(W/GDIM[1]) 
X<-seq(0, WH[1], length.out=GDIM[1]+1)
Y<-seq(vMargin,WH[2]-vMargin, length.out=GDIM[2]+1)
diffuseConstant<-.6
CXY<-expand.grid( 
  apply(rbind(X[1:(length(X)-1)], X[2:length(X)] ), 2, mean),
  apply(rbind(Y[1:(length(Y)-1)], Y[2:length(Y)] ), 2, mean)
)
phitheta<-expand.grid(
  seq( pi/6,  pi/2, length.out=GDIM[1]),  # phi
  seq( 0, -pi/2,  length.out=GDIM[2])    # theta
)
rho<-2*R
svgR(wh=WH,
    text("Varying fePointLight Position with feDiffuseLighting", font.size=22, cxy=c(WH[1]/2,40)),
    text("(Spherical Coord, angles in radians, origin at object center)", font.size=16, cxy=c(WH[1]/2,65)),
    text(paste0("(diffuseConstant=",diffuseConstant, ")"), font.size=12, cxy=c(WH[1]/2,85)),
    lapply(1:nrow(CXY), function(i){
      xyz=rho*c(
        sin(phitheta[i,1])*cos(phitheta[i,2]), 
        sin(phitheta[i,1])*sin(phitheta[i,2]), 
        cos(phitheta[i,1])
      )
      g( transform=list(translate=CXY[i,]),
        circle( cxy=c(0,0), r= R-10,  stroke="black" , stroke.width=5, 
            fill = "darkgreen",
            filter=filter(
                feComposite( 
                  operator="arithmetic", k1234=c(0,1,1,0),
                  in1=feDiffuseLighting( lighting.color="white",  surfaceScale=(5/7)*R, 
                      diffuseConstant=diffuseConstant,
                      in1=feGaussianBlur( stdDeviation=10),
                      fePointLight( xyz= xyz) 
                  ),
                  in2=  "SourceGraphic"
                )
            ),
            clip.path=clipPath(circle(cxy=c(0,0),r=R-8))
        ),
        lapply(1:3, function(j){ 
          text(
            paste0( mathSymbol(c("\\phi", "\\theta", "\\rho")[j]), '= ',
            format(c(phitheta[i,], rho)[j],digits=3)), cxy=c(0, R-14+j*16),  font.size=12
          )
        })
      )
    })
)
Varying fePointLight Position with feDiffuseLighting (Spherical Coord, angles in radians, origin at object center) (diffuseConstant=0.6) ϕ= 0.524 θ= 0 ρ= 160 ϕ= 0.785 θ= 0 ρ= 160 ϕ= 1.05 θ= 0 ρ= 160 ϕ= 1.31 θ= 0 ρ= 160 ϕ= 1.57 θ= 0 ρ= 160 ϕ= 0.524 θ= -0.785 ρ= 160 ϕ= 0.785 θ= -0.785 ρ= 160 ϕ= 1.05 θ= -0.785 ρ= 160 ϕ= 1.31 θ= -0.785 ρ= 160 ϕ= 1.57 θ= -0.785 ρ= 160 ϕ= 0.524 θ= -1.57 ρ= 160 ϕ= 0.785 θ= -1.57 ρ= 160 ϕ= 1.05 θ= -1.57 ρ= 160 ϕ= 1.31 θ= -1.57 ρ= 160 ϕ= 1.57 θ= -1.57 ρ= 160
Varying PointLight Positions: Specular
library(svgR)
W<-800
GDIM=c(5,3) # = num cols , num rows
vMargin<-90 #spacing for Title, and spaceing at bottom
WH=c(W, 2*vMargin + 1.3*W*GDIM[2]/GDIM[1])  # compute WH of svg
R<-.5*(W/GDIM[1]) 
X<-seq(0, WH[1], length.out=GDIM[1]+1)
Y<-seq(vMargin,WH[2]-vMargin, length.out=GDIM[2]+1)
specularConstant<-.6
specularExponent<-10
CXY<-expand.grid( 
  apply(rbind(X[1:(length(X)-1)], X[2:length(X)] ), 2, mean),
  apply(rbind(Y[1:(length(Y)-1)], Y[2:length(Y)] ), 2, mean)
)
phitheta<-expand.grid(
  seq( pi/6,  pi/2, length.out=GDIM[1]),  # phi
  seq( 0, -pi/2,  length.out=GDIM[2])    # theta
)
rho<-2*R
svgR(wh=WH,
    text("Varying fePointLight Position with feSpecularLighting", font.size=22, cxy=c(WH[1]/2,40)),
    text("(Spherical Coord, angles in radians, origin at object center)", font.size=16, cxy=c(WH[1]/2,65)),
    text(paste0("(specularConstant=",specularConstant,", specularExponent=",specularExponent), font.size=12, cxy=c(WH[1]/2,85)),
    lapply(1:nrow(CXY), function(i){
      xyz=rho*c(
        sin(phitheta[i,1])*cos(phitheta[i,2]), 
        sin(phitheta[i,1])*sin(phitheta[i,2]), 
        cos(phitheta[i,1])
      )
      g( transform=list(translate=CXY[i,]),
        circle( cxy=c(0,0), r= R-10,  stroke="black" , stroke.width=5, 
            fill = "darkgreen",
            filter=filter(
                feComposite( 
                  operator="arithmetic", k1234=c(0,1,1,0),
                  in1=feSpecularLighting( lighting.color="white",  surfaceScale=(5/7)*R, 
                      specularExponent=specularExponent,
                      specularConstant=specularConstant,
                      in1=feGaussianBlur( stdDeviation=10),
                      fePointLight( xyz= xyz) 
                  ),
                  in2=  "SourceGraphic"
                )
            ),
            clip.path=clipPath(circle(cxy=c(0,0),r=R-8))
        ),
        lapply(1:3, function(j){ 
          text(
            paste0( mathSymbol(c("\\phi", "\\theta", "\\rho")[j]), '= ',
            format(c(phitheta[i,], rho)[j],digits=3)), cxy=c(0, R-14+j*16),  font.size=12
          )
        })
      )
    })
)
Varying fePointLight Position with feSpecularLighting (Spherical Coord, angles in radians, origin at object center) (specularConstant=0.6, specularExponent=10 ϕ= 0.524 θ= 0 ρ= 160 ϕ= 0.785 θ= 0 ρ= 160 ϕ= 1.05 θ= 0 ρ= 160 ϕ= 1.31 θ= 0 ρ= 160 ϕ= 1.57 θ= 0 ρ= 160 ϕ= 0.524 θ= -0.785 ρ= 160 ϕ= 0.785 θ= -0.785 ρ= 160 ϕ= 1.05 θ= -0.785 ρ= 160 ϕ= 1.31 θ= -0.785 ρ= 160 ϕ= 1.57 θ= -0.785 ρ= 160 ϕ= 0.524 θ= -1.57 ρ= 160 ϕ= 0.785 θ= -1.57 ρ= 160 ϕ= 1.05 θ= -1.57 ρ= 160 ϕ= 1.31 θ= -1.57 ρ= 160 ϕ= 1.57 θ= -1.57 ρ= 160

feSpotLight

A spot light is a directed light source. It is modeled as a point light source whose rays a restricted to a cone. Thus light resulting on a surface will be either an elliptic or a parabola.

The feSpotLight consists of a source position, a a target position, and an a limiting cone.

X Y Z

The revelant attributes for feSpotLight are:

Attribute Value Description
xyz numeric x, y, z postions of light source
pointsAtXYZ numeric position of where the light source is pointing
limitingConeAngle numeric value in degrees describing the limiting cone

The spotlight is best understood by observing it’s behaviour on a flat surface.

Diffuse SpotLight on Flat surface Rotated About Boundry, Pointing at Center

Here we move a spot light about a circle while keeping the the spotlight pointed at the circles center.

library(svgR)
WH=c(800, 300) # window rect
N=4
sp<-20
X=seq(sp, WH[1], length.out=N+1)
SWH<-c(X[2]-X[1]-sp, WH[2]-30)
R<-.4*SWH[1]
theta<-seq(-pi,0, length.out=N)
phi<-seq(.2*pi,.4*pi, length.out=N)
lightPos<-.8*R*cbind( sin(phi)*cos(theta), sin(phi)*sin(theta), cos(phi) )

svgR(wh=WH, 
  lapply(1:4, function(i){
    svg( wh=SWH, x=X[i], y=0,
      circle( cxy=SWH/2, r= R, fill="green", stroke="black",  stroke.width=5,
        filter=filter(
          feComposite( 
            operator="arithmetic", k1234=c(0,1,1,0),
            in1="SourceGraphic",
            in2= feDiffuseLighting( lighting.color="white", diffuseConstant=1.2,
                in1="SourceGraphic",
                feSpotLight( limitingConeAngle=20, #this is in degrees
                             xyz=lightPos[i,]+.5*c(SWH,0), #spotLightPos,
                             pointsAtXYZ=.5*c(SWH,0) #ptAtPos[[i]]
                             ) 
            )
           )
        )
      ),
      text(paste(mathSymbol("\\phi"), '=',format(phi[i], digits=3)), xy=c(10,SWH[2]-80), fill='white'),
      text(paste(mathSymbol("\\theta"), '=',format(theta[i], digits=3)), xy=c(10,SWH[2]-60), fill="white")
    )
  }),
  text('Diffuse: SpotLight point at c(0,0,0)', cxy=c(400,250), font.size=20)
)->doc
cat("'",as.character(doc),"'")

ϕ = 0.628 θ = -3.14 ϕ = 0.838 θ = -2.09 ϕ = 1.05 θ = -1.05 ϕ = 1.26 θ = 0 Diffuse: SpotLight point at c(0,0,0)

Diffuse Spot Light Located Above Center, Point Along Boundry

Here we the light source as spot light centered at c(0,0,R) and move the focus (where it points to)

library(svgR)
WH=c(800, 300) # window rect
N=4
sp<-20
X=seq(sp, WH[1]+sp, length.out=N+1)
SWH<-c(X[2]-X[1]-sp, WH[2]-30)
R<-.4*SWH[1]
theta<-seq(-pi,0, length.out=N)
lightPos<-.5*c(SWH,20)
pointsAt<-R*cbind(cos(theta), sin(theta))
svgR(wh=WH, 
  lapply(1:4, function(i){
    svg( wh=SWH, x=X[i], y=0,
      circle( cxy=SWH/2, r= R, fill="green", stroke="black",  stroke.width=5,
        filter=filter(
          feComposite( 
            operator="arithmetic", k1234=c(0,1,1,0),
            in1="SourceGraphic",
            in2= feDiffuseLighting( lighting.color="white", diffuseConstant=1.2,
                in1="SourceGraphic",
                feSpotLight( limitingConeAngle=20, #this is in degrees
                             xyz=lightPos, #spotLightPos,
                             pointsAtXYZ=c(pointsAt[i,]+SWH/2, 0) #ptAtPos[[i]]
                             ) 
            )
           )
        )
      ),
      text(paste("x", '=',format(pointsAt[i,1], digits=3)), xy=c(10,SWH[2]-80), fill='white'),
      text(paste("y", '=',format(pointsAt[i,2], digits=3)), xy=c(10,SWH[2]-60), fill="white")
    )
  }),
  text('Diffuse: SpotLight point at c(0,0,0)', cxy=c(400,280), font.size=20)
)->doc
cat("'",as.character(doc),"'")

x = -72 y = -8.82e-15 x = -36 y = -62.4 x = 36 y = -62.4 x = 72 y = 0 Diffuse: SpotLight point at c(0,0,0)

Varying feSpotLight Positions: Diffuse

library(svgR)
W<-800
GDIM=c(5,3) # = num cols , num rows
vMargin<-90 #spacing for Title, and spaceing at bottom
WH=c(W, 2*vMargin + 1.3*W*GDIM[2]/GDIM[1])  # compute WH of svg
R<-.5*(W/GDIM[1]) 
X<-seq(0, WH[1], length.out=GDIM[1]+1)
Y<-seq(vMargin,WH[2]-vMargin, length.out=GDIM[2]+1)
diffuseConstant<-.6
CXY<-expand.grid( 
  apply(rbind(X[1:(length(X)-1)], X[2:length(X)] ), 2, mean),
  apply(rbind(Y[1:(length(Y)-1)], Y[2:length(Y)] ), 2, mean)
)
phitheta<-expand.grid(
  seq( pi/6,  pi/4, length.out=GDIM[1]),  # phi
  seq( 0, -pi/2,  length.out=GDIM[2])    # theta
)
rho<-2*R
surfaceScale<-(5/7)*R
svgR(wh=WH,
    text("Varying feSpotLight Position with feDiffuseLighting", font.size=22, cxy=c(WH[1]/2,40)),
    text("(Spherical Coord, angles in radians, origin at object center)", font.size=16, cxy=c(WH[1]/2,65)),
    text(paste0("(diffuseConstant=",diffuseConstant, ") pointing at center"), font.size=12, cxy=c(WH[1]/2,85)),
    lapply(1:nrow(CXY), function(i){
      xyz=rho*c(
        sin(phitheta[i,1])*cos(phitheta[i,2]), 
        sin(phitheta[i,1])*sin(phitheta[i,2]), 
        cos(phitheta[i,1])
      )
      g( transform=list(translate=CXY[i,]),
        circle( cxy=c(0,0), r= R-10,  stroke="black" , stroke.width=5, 
            fill = "darkgreen",
            filter=filter(
                feComposite( 
                  operator="arithmetic", k1234=c(0,1,1,0),
                  in1=feDiffuseLighting( lighting.color="white",  surfaceScale=surfaceScale, 
                      diffuseConstant=diffuseConstant,
                      in1=feGaussianBlur( stdDeviation=10),
                      feSpotLight( xyz= xyz, pointsAtXYZ= -c(1,1,0)*xyz, #+
                      #c(0,0,surfaceScale), 
                      limitingConeAngle=15 ) 
                  ),
                  in2=  "SourceGraphic"
                )
            ),
            clip.path=clipPath(circle(cxy=c(0,0),r=R-8))
        ),
        lapply(1:3, function(j){ 
          text(
            paste0( mathSymbol(c("\\phi", "\\theta", "\\rho")[j]), '= ',
            format(c(phitheta[i,], rho)[j],digits=3)), cxy=c(0, R-14+j*16),  font.size=12
          )
        })
      )
    })
)
Varying feSpotLight Position with feDiffuseLighting (Spherical Coord, angles in radians, origin at object center) (diffuseConstant=0.6) pointing at center ϕ= 0.524 θ= 0 ρ= 160 ϕ= 0.589 θ= 0 ρ= 160 ϕ= 0.654 θ= 0 ρ= 160 ϕ= 0.72 θ= 0 ρ= 160 ϕ= 0.785 θ= 0 ρ= 160 ϕ= 0.524 θ= -0.785 ρ= 160 ϕ= 0.589 θ= -0.785 ρ= 160 ϕ= 0.654 θ= -0.785 ρ= 160 ϕ= 0.72 θ= -0.785 ρ= 160 ϕ= 0.785 θ= -0.785 ρ= 160 ϕ= 0.524 θ= -1.57 ρ= 160 ϕ= 0.589 θ= -1.57 ρ= 160 ϕ= 0.654 θ= -1.57 ρ= 160 ϕ= 0.72 θ= -1.57 ρ= 160 ϕ= 0.785 θ= -1.57 ρ= 160
Varying feSpotLight Positions: Specular
library(svgR)
W<-800
GDIM=c(5,3) # = num cols , num rows
vMargin<-90 #spacing for Title, and spaceing at bottom
WH=c(W, 2*vMargin + 1.3*W*GDIM[2]/GDIM[1])  # compute WH of svg
R<-.5*(W/GDIM[1]) 
X<-seq(0, WH[1], length.out=GDIM[1]+1)
Y<-seq(vMargin,WH[2]-vMargin, length.out=GDIM[2]+1)
specularConstant<-.6
specularExponent<-10
CXY<-expand.grid( 
  apply(rbind(X[1:(length(X)-1)], X[2:length(X)] ), 2, mean),
  apply(rbind(Y[1:(length(Y)-1)], Y[2:length(Y)] ), 2, mean)
)
phitheta<-expand.grid(
  seq( pi/6,  pi/4, length.out=GDIM[1]),  # phi
  seq( 0, -pi/2,  length.out=GDIM[2])    # theta
)
rho<-2*R
surfaceScale<-(5/7)*R
svgR(wh=WH,
    text("Varying feSpotLight Position with feSpecularLighting", font.size=22, cxy=c(WH[1]/2,40)),
    text("(Spherical Coord, angles in radians, origin at object center)", font.size=16, cxy=c(WH[1]/2,65)),
    text(paste0("(specularConstant=",specularConstant,", specularExponent=",specularExponent), font.size=12, cxy=c(WH[1]/2,85)),
    lapply(1:nrow(CXY), function(i){
      xyz=rho*c(
        sin(phitheta[i,1])*cos(phitheta[i,2]), 
        sin(phitheta[i,1])*sin(phitheta[i,2]), 
        cos(phitheta[i,1])
      )
      g( transform=list(translate=CXY[i,]),
        circle( cxy=c(0,0), r= R-10,  stroke="black" , stroke.width=5, 
            fill = "darkgreen",
            filter=filter(
                feComposite( 
                  operator="arithmetic", k1234=c(0,1,1,0),
                  in1=feSpecularLighting( lighting.color="white",  surfaceScale=surfaceScale, 
                      specularExponent=specularExponent,
                      specularConstant=specularConstant,
                      in1=feGaussianBlur( stdDeviation=10),
                      feSpotLight( xyz= xyz, pointsAtXYZ= -c(1,1,0)*xyz, limitingConeAngle=15) 
                  ),
                  in2=  "SourceGraphic"
                )
            ),
            clip.path=clipPath(circle(cxy=c(0,0),r=R-8))
        ),
        lapply(1:3, function(j){ 
          text(
            paste0( mathSymbol(c("\\phi", "\\theta", "\\rho")[j]), '= ',
            format(c(phitheta[i,], rho)[j],digits=3)), cxy=c(0, R-14+j*16),  font.size=12
          )
        })
      )
    })
)
Varying feSpotLight Position with feSpecularLighting (Spherical Coord, angles in radians, origin at object center) (specularConstant=0.6, specularExponent=10 ϕ= 0.524 θ= 0 ρ= 160 ϕ= 0.589 θ= 0 ρ= 160 ϕ= 0.654 θ= 0 ρ= 160 ϕ= 0.72 θ= 0 ρ= 160 ϕ= 0.785 θ= 0 ρ= 160 ϕ= 0.524 θ= -0.785 ρ= 160 ϕ= 0.589 θ= -0.785 ρ= 160 ϕ= 0.654 θ= -0.785 ρ= 160 ϕ= 0.72 θ= -0.785 ρ= 160 ϕ= 0.785 θ= -0.785 ρ= 160 ϕ= 0.524 θ= -1.57 ρ= 160 ϕ= 0.589 θ= -1.57 ρ= 160 ϕ= 0.654 θ= -1.57 ρ= 160 ϕ= 0.72 θ= -1.57 ρ= 160 ϕ= 0.785 θ= -1.57 ρ= 160

A case study in combining Light Sources

Step 1

Gaussian blur to create depth

library(svgR)
WH=c(800,400)
R<-.4*WH[2]
svgR(wh=WH+c(0,30),
    circle( cxy=WH/2, r= R,
      filter=filter(
        feGaussianBlur( in1="SourceAlpha", stdDeviation=R/2)
      )
    ),
    text("Step 1: A Gaussian blur", cxy=c(.5,1)*WH-c(0,15), stroke='black')
)
Step 1: A Gaussian blur

Step 2

Clip to circle

library(svgR)
WH=c(800,400)
R<-.4*WH[2]
svgR(wh=WH+c(0,30),
    circle( cxy=WH/2, r= R,
      filter=filter(
        feGaussianBlur( in1="SourceAlpha", stdDeviation=R/2)
      ),
      clip.path=clipPath(circle(cxy=WH/2, r=R))
    ),
    text("Step 2: Add clip path", cxy=c(.5,1)*WH-c(0,15), stroke='black')
)
Step 2: Add clip path

Step 3 Add diffuse colored light.

library(svgR)
WH=c(800,400)
R<-.4*WH[2]
svgR(wh=WH,
    circle( cxy=WH/2, r= R,  
      filter=filter( 
        feDiffuseLighting( lighting.color="#44BBFF",  
          diffuseConstant=1,
          surfaceScale=R,
          in1=feGaussianBlur(in1="SourceAlpha", stdDeviation=.5*R),
          feDistantLight( elevation= 60, azimuth=90 )
        )
      ),
      clip.path=clipPath(circle(cxy=WH/2,r=R))
    ),
    text("Step 3: Add diffuse colored light", cxy=c(.5,1)*WH-c(0,15), stroke='black')
  )
Step 3: Add diffuse colored light

Step 4 Reset filter viewPort

library(svgR)
WH=c(800,400)
R<-.4*WH[2]
svgR(wh=WH,
    circle( cxy=WH/2, r= R,  
      filter=filter( 
        xy=-.4*c(1,1), wh=1.8*c(1,1), #in percentage
        feDiffuseLighting( lighting.color="#44BBFF",  
          diffuseConstant=1,
          surfaceScale=R,
          in1=feGaussianBlur(in1="SourceAlpha", stdDeviation=.5*R),
          feDistantLight( elevation= 60, azimuth=90 )
        )
      ),
      clip.path=clipPath(circle(cxy=WH/2,r=R))
    ),
    text("Step 4: Add diffuse colored light", cxy=c(.5,1)*WH-c(0,15), stroke='black')
  )
Step 4: Add diffuse colored light

Step 4

But this is too grainy! so lets smooth it a little :)

library(svgR)
WH=c(800,400)
R<-.4*WH[2]
svgR(wh=WH,
    circle( cxy=WH/2, r= R,  
      filter=filter( 
        xy=-.4*c(1,1), wh=1.8*c(1,1), #in percentage
        feGaussianBlur(
          stdDeviation=5,
          in1=feDiffuseLighting( lighting.color="#44BBFF",  
            diffuseConstant=1,
            surfaceScale=R,
            in1=feGaussianBlur(in1="SourceAlpha", stdDeviation=.5*R),
            feDistantLight( elevation= 60, azimuth=90 )
          )        
        )
      ),
      clip.path=clipPath(circle(cxy=WH/2,r=R))
    ),
    text("Step 4: Smooth with Gaussian Blur", cxy=c(.5,1)*WH-c(0,15), stroke='black')
  )
Step 4: Smooth with Gaussian Blur

Step 5

Add a spot light with white light and combine with feComposite

library(svgR)
WH=c(800,400)
R<-.4*WH[2]
phi<-pi/4
spot.xyz<- c(WH/2,0)+ 2*R*c(0,-sin(phi),cos(phi))
spot.ptsAt<-c(WH/2,0)+ R*c(0,-sin(phi),cos(phi))
svgR(wh=WH,
    circle( cxy=WH/2, r= R,  stroke="none",  fill = "#000068",
      filter=filter( 
        xy=-.4*c(1,1), wh=1.8*c(1,1), #in percentage
        feGaussianBlur(
          stdDeviation=5,
          in1=feComposite( operator="arithmetic", k1234=c(0,1,1,0),
            in1=feDiffuseLighting( lighting.color="#44BBFF",  
                diffuseConstant=1,
                surfaceScale=R,
                in1=feGaussianBlur(in1="SourceAlpha", stdDeviation=.5*R),
                feDistantLight( elevation= 60, azimuth=90 )
            ),
            in2= feDiffuseLighting( lighting.color="#white",  
                diffuseConstant=.8,
                surfaceScale=R,
                in1=feGaussianBlur(in1="SourceAlpha", stdDeviation=.5*R),
                feSpotLight( xyz=spot.xyz, pointsAtXYZ=spot.ptsAt, limitingConeAngle=30 )
            )
          )
        ) 
      ),
      clip.path=clipPath(circle(cxy=WH/2,r=R))
    ),
    text("Step 5: A Spot Light", cxy=c(.5,1)*WH-c(0,15), stroke='black')
  )
Step 5: A Spot Light

Step 6 Add a shadow

library(svgR)
WH=c(800,400)
R<-.4*WH[2]
phi<-pi/4
spot.xyz<- c(WH/2,0)+ 2*R*c(0,-sin(phi),cos(phi))
spot.ptsAt<-c(WH/2,0)+ R*c(0,-sin(phi),cos(phi))
RXY<-c(1,.2)*R
svgR(wh=WH,
    ellipse(cxy=WH/2+c(0,R), rxy=RXY, opacity=.5,
      filter=filter( feGaussianBlur(in1="SourceAlpha", stdDeviation=5))
    ),
    circle( cxy=WH/2, r= R,  stroke="none",  fill = "#000068",
      filter=filter( 
        xy=-.4*c(1,1), wh=1.8*c(1,1), #in percentage
        feGaussianBlur(
          stdDeviation=5,
          in1=feComposite( operator="arithmetic", k1234=c(0,1,1,0),
            in1=feDiffuseLighting( lighting.color="#44BBFF",  
                diffuseConstant=1,
                surfaceScale=R,
                in1=feGaussianBlur(in1="SourceAlpha", stdDeviation=.5*R),
                feDistantLight( elevation= 60, azimuth=90 )
            ),
            in2= feDiffuseLighting( lighting.color="#white",  
                diffuseConstant=.8,
                surfaceScale=R,
                in1=feGaussianBlur(in1="SourceAlpha", stdDeviation=.5*R),
                feSpotLight( xyz=spot.xyz, pointsAtXYZ=spot.ptsAt, limitingConeAngle=30 )
            )
          )
        ) 
      ),
      clip.path=clipPath(circle(cxy=WH/2,r=R))
    ),
    text("Step 6: Shadow", cxy=c(.5,1)*WH-c(0,15), stroke='black')
  )
Step 6: Shadow

Step 7 Add a Final Touch

library(svgR)
WH=c(800,400)
R<-.4*WH[2]
phi<-pi/4
spot.xyz<- c(WH/2,0)+ 2*R*c(0,-sin(phi),cos(phi))
spot.ptsAt<-c(WH/2,0)+ R*c(0,-sin(phi),cos(phi))
RXY<-c(1,.2)*R
svgR(wh=WH,
    ellipse(cxy=WH/2+c(0,R), rxy=RXY, fill="#888888", 
      filter=filter( feGaussianBlur(in1="SourceGraphic", stdDeviation=5))
    ),
    circle( cxy=WH/2, r= R,  stroke="none",  fill = "#000068",
      filter=filter( 
        xy=-.4*c(1,1), wh=1.8*c(1,1), #in percentage
        feGaussianBlur(
          stdDeviation=5,
          in1=feComposite( operator="arithmetic", k1234=c(0,1,1,0),
            in1=feDiffuseLighting( lighting.color="#44BBFF",  
                diffuseConstant=1,
                surfaceScale=R,
                in1=feGaussianBlur(in1="SourceAlpha", stdDeviation=.5*R),
                feDistantLight( elevation= 60, azimuth=90 )
            ),
            in2= feDiffuseLighting( lighting.color="#white",  
                diffuseConstant=.8,
                surfaceScale=R,
                in1=feGaussianBlur(in1="SourceAlpha", stdDeviation=.5*R),
                feSpotLight( xyz=spot.xyz, pointsAtXYZ=spot.ptsAt, limitingConeAngle=30 )
            )
          )
        ) 
      ),
      clip.path=clipPath(circle(cxy=WH/2,r=R))
    ),
    text("R",cxy=WH/2, stroke="white", font.size=200, fill="white")
  )
R

An Alternate Approach

One thing you might notice in the proceding sections was that we only used light filtering. The original shape was not even filled!

In practice, we get better results by first filling the shape. In this we

  • first combine the SourceImage with the distantlight
  • then combine that result with the spotlight.

The following an example, where the shape is filled with a darkblue, the spotlight is white, and the distant light is lightgreen.

library(svgR)
WH=c(800,300)
R<-.4*WH[2]
phi<-pi/4
spot.xyz<- c(WH/2,0)+ 2*R*c(0,-sin(phi),cos(phi))
spot.ptsAt<-c(WH/2,0)+ R*c(0,-sin(phi),cos(phi))
svgR(wh=WH,
    ellipse(cxy=WH/2+c(0,R), rxy=RXY, fill="#888888", 
      filter=filter( feGaussianBlur(in1="SourceGraphic", stdDeviation=5))
    ),
    circle( cxy=WH/2, r= R,  stroke="none",  fill = "darkblue",
      filter=filter(
        xy=-.4*c(1,1), wh=1.8*c(1,1), #in percentage
        feComposite( operator="arithmetic", k1234=c(0,1,1,0),
          in1=feGaussianBlur(stdDeviation=2,
                in1=feDiffuseLighting( lighting.color="white",  
                  diffuseConstant=.75,
                  surfaceScale=R,
                  in1=feGaussianBlur( stdDeviation=.5*R),
                  feSpotLight( 
                    xyz=spot.xyz, 
                    pointsAtXYZ=spot.ptsAt, 
                    limitingConeAngle=30 
                  )
                )
          ),
          in2=feComposite( operator="arithmetic", k1234=c(0,1,1,0),
            in2=feGaussianBlur(stdDeviation=10,
              in1=feDiffuseLighting( lighting.color="lightgreen",  
                diffuseConstant=1,
                surfaceScale=R,
                in1=feGaussianBlur( stdDeviation=.5*R),
                feDistantLight( elevation= 20, azimuth=90 )
              )
            ),
            in2="SourceGraphic"
          )
        )
      ),
    clip.path=clipPath(circle(cxy=WH/2,r=R))
    ),
    text("R",cxy=WH/2, stroke="white", font.size=R, fill="white")
  )
R





Creating Shiny Bevels

Shiny beveling is a popular example of what can be accomplished using the right lighting.

Shining with Lighting

We can shiny bevel

WH=c(800, 200) # window rect
svgR( wh=WH,
  text( "R Rocks!", cxy=c(400,100), fill="darkblue", font.size=190, font.face='bold',
    filter = filter(
      feMerge( 
         feMergeNode(in1="SourceGraphic"),
         feMergeNode(
           in1=feComposite( operator='in',
                in1=feSpecularLighting( surfaceScale=6,
                                        specularConstant=1,
                                        specularExponent=30,
                                        lighting.color="white",
                                        in1=feGaussianBlur(
                                          stdDeviation=5,
                                          in1="SourceAlpha"),
                                        fePointLight(xyz=c(40,-30,200))
                                        ),
                in2="SourceAlpha"
                )
           )
         )
    )
  )
)
R Rocks!

Wrapping a Filter

But we can also wrap this filter as a user defined filter element by

sbevelFilter %<c-% function(lighting.color="white", xyz=c(40,-30,200) ){
  filter(
      feMerge( 
         feMergeNode(in1="SourceGraphic"),
         feMergeNode(
           in1=feComposite( operator='in',
                in1=feSpecularLighting( surfaceScale=6,
                                        specularConstant=1,
                                        specularExponent=30,
                                        lighting.color=lighting.color,
                                        in1=feGaussianBlur(
                                          stdDeviation=5,
                                          in1="SourceAlpha"),
                                        fePointLight(xyz=xyz)
                                        ),
                in2="SourceAlpha"
                )
           )
         )
    )
}

and then use as like so

WH=c(800, 200) # window rect
svgR( wh=WH,
  text( "R Rocks!", cxy=c(400,100), fill="darkblue", font.size=190, font.face='bold',
    filter = sbevelFilter()
  )
)
R Rocks!

Shing S

In this section we create several shiny letters S using the sbevelFilter

Silver Metallic Shiny S

For a shiny bevel letter S, with grey scale we simple add a circle and change the text to “S” , both with a black stroke.

WH=c(800, 250) # window rect
svgR( wh=WH,
  g( 
    text( "S", cxy=WH/2, fill="black", font.size=100, 
          font.family="serif", stroke.width=3),
    circle(cxy=WH/2, r=39, stroke.width=10, fill='none'  ),
     stroke='black', filter=sbevelFilter()
  )
)
S

Metallic Shiny S’s

For variety, we can vary color of the text stroke

WH=c(800, 250) # window rect
colors<-c("purple", "darkblue", "darkgreen", "brown", "darkorange")
svgR( wh=WH,
  lapply(1:5, function(i){
    cxy=WH*c(i*.15,.5)
    g( 
      text( "S", cxy=cxy, fill=colors[i], font.size=100, 
          font.family="serif", stroke.width=3),
      circle(cxy=cxy, r=39, stroke.width=10, fill='none'  ),
        stroke='black', filter=sbevelFilter()
    )
  })
)
S S S S S

Metallic Shiny S’s Lighting

And, we can also vary color of the light

WH=c(800, 250) # window rect
colors<-c("purple", "darkblue", "darkgreen", "brown", "darkorange")
lighting.color<-c("lightblue", "yellow", "pink", "orange", "lightgreen")

svgR( wh=WH,
  lapply(1:5, function(i){
    cxy=WH*c(i*.15,.5)
    g( 
      text( "S", cxy=cxy, fill=colors[i], font.size=100, 
          font.family="serif", stroke.width=3),
      circle(cxy=cxy, r=39, stroke.width=10, fill='none'  ),
        stroke='black', filter=sbevelFilter(lighting.color=lighting.color[i])
    )
  })
)
S S S S S

Looking at the above, we see that the position of reflection varies from S to S. This is because the position of the S varies, while the lights is fixed. (There are 5 colored lights: “lightblue”, “yellow”, “pink”, “orange”, “lightgreen”)

Metallic Shiny S’s Lighting Compare

To compare each S, we probably want to require the same light relative light posistion be applied to each letter. Here we maintain the same that relative position. This causes the highlights to appear in the same position across all letters S. Now we can compare, however, as a complete image, the effect is poor. This is because the human eye assumes a light source at a fixed posistion and wants the reflections to vary accordingly.

WH=c(800, 250) # window rect
colors<-c("purple", "darkblue", "darkgreen", "brown", "darkorange")
lighting.color<-c("lightblue", "yellow", "pink", "orange", "lightgreen")

svgR( wh=WH,
  lapply(1:5, function(i){
    cxy=WH*c(i*.15,.5)
    g( 
      text( "S", cxy=cxy, fill=colors[i], font.size=100, 
          font.family="serif", stroke.width=3),
      circle(cxy=cxy, r=39, stroke.width=10, fill='none'  ),
        stroke='black', filter=sbevelFilter(lighting.color=lighting.color[i])
    )
  })
)
S S S S S

A Logo for a User Guide

A Metallic svgR

Of course we do use any text, with any size for the same filter.

shiny bevel

WH=c(800, 250) # window rect
svgR( wh=WH,
  g(
    text( "svgR", cxy=WH/2, fill="darkred", font.size=100, 
          font.family="san serif", stroke.width=3),
    circle(cxy=WH/2, r=99, stroke.width=10, fill='none'  ),
     stroke='black', filter=sbevelFilter()
  )
)
svgR

Enhancing

Adding a little more text, and setting the lighting.color to “yellow” produces something could be used as a logo on title page of a document:)

WH=c(800, 250) # window rect
svgR( wh=WH,     
  g(
    text( "svgR", cxy=WH*c(.25,.5), fill="darkblue", font.size=100, 
          font.family="san serif", stroke.width=3),
    text( "Users Guide", cxy=WH*c(.7,.25), fill="darkblue", font.size=60, 
          font.family="fantasy", stroke.width=3),
    circle(cxy=WH*c(.25,.5), r=99, stroke.width=10, fill='none'  ),
     stroke='black',
    filter = sbevelFilter(lighting.color="yellow")
  )
)
svgR Users Guide





Shape Shifting

Clipping

With a defs clipPath works seamlessly, but

#```{r, echo=T}
WH=c(600, 100) # window rect
svgR( wh=WH,
    defs(
    clipPath(
      id="clip0",
      rect(cxy=c(0,50), wh=c(600,50))
    )    
    ),
    g(  clip.path="url(#clip0)",
      circle( cxy=c(50,50), r=30, fill='darkblue'),
      circle( cxy=c(150,50), r=50, fill='red'  )
    )
    
)
#```{r, echo=T}
WH=c(600, 100) # window rect
svgR( wh=WH,
    g(
      clip.path=clipPath( rect(cxy=c(0,50), wh=c(600,50))),
      circle( cxy=c(50,50), r=30, fill='darkblue'),
      circle( cxy=c(150,50), r=50, fill='red'  )
    )
)

Note removing the defs, we see that it works in firefox, chromium, but not in the RStudio browser on Ubuntu.

#```{r, echo=T}
WH=c(600, 100) # window rect
svgR( wh=WH,
    #defs(
    clipPath(
      id="clip1",
      rect(cxy=c(0,50), wh=c(600,50))
    #)    
    ),
    g(  clip.path="url(#clip1)",
      circle( cxy=c(50,50), r=30, fill='green'),
      circle( cxy=c(150,50), r=50, fill='orange')
    )
)

Masking

Using explicit defs

WH=c(600, 100) # window rect
svgR( wh=WH,
  defs(
    mask(
      id="mask0",
      rect(cxy= WH/2, wh=c(600,40), fill='white'),
      rect(cxy= WH/2, wh=c(600,10), fill='black')
    )
  ),
  g(
    circle( cxy=c(50,50), r=30, fill='darkblue'),
    circle( cxy=c(150,50), r=30, fill='red'),
    mask="url(#mask0)"
  )
)

Using implied defs

WH=c(600, 100) # window rect
svgR( wh=WH,
    g( 
      circle( cxy=c(50,50), r=30, fill='darkblue'),
      circle( cxy=c(150,50), r=30, fill='red'),
      mask=mask(
        rect(cxy= WH/2, wh=c(600,40), fill='white'),
        rect(cxy= WH/2, wh=c(600,10), fill='black')
        )
    )
)
#```{r, echo=T}
WH=c(600, 100) # window rect
svgR( wh=WH,
    defs(
        mask(
        id="mask1", 
        xy=c(0,0), wh=c(100,100),
        text(xy=c(0,50),'hello all you brilliant data scientists', 
             stroke='white',fill='black')
        )
    ),
    #rect(xy=c(0,0), wh=WH, fill='yellow'),
    circle( cxy=c(50,50), r=30, fill='darkblue', mask="url(#mask1)"),
    circle( cxy=c(150,50), r=30, fill='red',     mask="url(#mask1)")
)
hello all you brilliant data scientists
#```{r, echo=T}
WH=c(600, 100) # window rect
svgR( wh=WH,
    defs(
        mask(
        id="mask2", xy=c(0,0), wh=c(100,100),
        rect( xy=c(0,0),  wh=c(50,100), fill='white', stroke='none'),
        rect( xy=c(50,0), wh=c(50,100), fill='grey', stroke='none')
        )
    ),
    #rect(xy=c(0,0), wh=WH, fill='yellow'),
    text(xy=c(0,50),'hello all you brilliant data scientists', stroke='red',fill='white'),
    circle( cxy=c(50,50), r=39, fill='darkblue', mask="url(#mask2)"
             )
)
hello all you brilliant data scientists
#```{r, echo=T}
WH=c(600, 100) # window rect
svgR( wh=WH,   
    mask(
    id="mask2-5", xy=c(0,0), wh=c(100,100),
    rect( xy=c(0,0),  wh=c(50,100), fill='white', stroke='none'),
    rect( xy=c(50,0), wh=c(50,100), fill='grey', stroke='none')
    ),
    #rect(xy=c(0,0), wh=WH, fill='yellow'),
    text(xy=c(0,50),'hello all you brilliant data scientists', stroke='red',fill='white'),
    circle( cxy=c(50,50), r=39, fill='darkblue', mask="url(#mask2-5)"
             )
)
hello all you brilliant data scientists
#```{r, echo=T}
WH=c(600, 100) # window rect
svgR( wh=WH,
    defs(
        mask(
        id="mask3", xy=c(0,0), wh=c(100,100),
        rect( xy=c(0,0),  wh=c(50,100), fill='rgb(190,190,130)', stroke='none'),
        rect( xy=c(50,0), wh=c(50,100), fill='grey', stroke='none')
        )
    ),
    #rect(xy=c(0,0), wh=WH, fill='yellow'),
    text(xy=c(0,50),'hello all you brilliant data scientists', stroke='red',fill='white'),
    circle( cxy=c(50,50), r=39, fill='darkblue', mask="url(#mask3)"
             )
)
hello all you brilliant data scientists
#```{r, echo=T}
WH=c(600, 100) # window rect
svgR( wh=WH,
  mask(
    id="mask4", xy=c(0,0), wh=c(100,100),
    rect( xy=c(0,0),  wh=c(50,100), fill='rgb(190,190,130)', stroke='none'),
    rect( xy=c(50,0), wh=c(50,100), fill='grey', stroke='none')
  ),
  #rect(xy=c(0,0), wh=WH, fill='yellow'),
  text(xy=c(0,50),'hello all you brilliant data scientists', stroke='red',fill='white'),
  circle( cxy=c(50,50), r=39, fill='darkblue', mask="url(#mask4)"
           )
)
hello all you brilliant data scientists
#```{r, echo=T}
WH=c(600, 100) # window rect
svgR( wh=WH,
  pattern(
    id="pattern2", xy=c(5,5), wh=c(10,10), patternUnits="userSpaceOnUse" ,
    circle(cxy=c(5,5), r=5, fill='white')
  ),  
  mask( id="mask5", xy=c(0,0), wh=WH,
    rect(xy=c(0,0), wh=WH, fill="black"),
    rect(xy=c(0,0), wh=WH, fill="url(#pattern2)")
  ),
  text(xy=c(0,50),'hello all you brilliant data scientists', stroke='black',font.size=30,
        mask="url(#mask5)"
       )
)
hello all you brilliant data scientists
#```{r, echo=T}
WH=c(600, 100) # window rect
svgR( wh=WH,
  mask( id="mask5", xy=c(0,0), wh=WH,
    rect(xy=c(0,0), wh=WH, fill="black"),
    lapply(seq(1,90,5), function(i){rect(xy=c(0,i), wh=c(600,3), fill='white')})
  ),
  text(xy=c(0,50),'hello all you brilliant data scientists', stroke='black',font.size=30,
        mask="url(#mask5)"
       )
)
hello all you brilliant data scientists
#```{r, echo=T}
WH=c(600, 200) # window rect
svgR( wh=WH,
  pattern(
      id="pattern3", xy=c(10,10), wh=c(20,20), patternUnits="userSpaceOnUse" ,
      circle(cxy=c(10,10), r=10, fill='green')
  ),
  g(
    rect(xy=c(0,0), wh=WH, fill='orange'),
    ellipse( cxy=c(55,60), rxy=c(55,25), fill="url(#pattern3)")
    )
)

Morpolgy

feMorphology allows thickening and thinning of graphics. To thicken we use the dilate attribute, to thin we use the erode attribute. The radius specifies the amount, which can be different for x and y, ie. dilate=c(3,1) will dilate in the x direction, but not in the y. Also, dilate=3 is equivalent to dilate=c(3,3)

Dilation

In this example we dialate with a value of 3.

WH=c(800, 220) # window rect
svgR( wh=WH,
  graphPaper(WH, dxy=c(40,40), labels=TRUE), 
  text( 'svgR without feMorphology', xy=c(20,80), font.size=60, stroke="red", fill="none"), 
  text( 'svgR feMorphology dilate=3', xy=c(20,160), font.size=60, stroke="red", fill="none", 
        filter=filter(
          feMorphology(radius=3, operator='dilate')
        )
  )
)
0 40 80 120 160 200 240 280 320 360 400 440 480 520 560 600 640 680 720 760 800 0 40 80 120 160 200 svgR without feMorphology svgR feMorphology dilate=3

feMorphology is does not simply change the stroke-width, rather it smears the source graphics. This is exibited by changing the fill from ‘none’ to ‘blue’.

WH=c(800, 220) # window rect
svgR( wh=WH,
  graphPaper(WH, dxy=c(40,40), labels=TRUE), 
  text( 'svgR without feMorphology', xy=c(20,80), font.size=60, stroke="red", fill="blue"), 
  text( 'svgR feMorphology dilate=3', xy=c(20,160), font.size=60, stroke="red", fill="blue", 
        filter=filter(
          feMorphology(radius=2, operator='dilate')
        )
  )
)
0 40 80 120 160 200 240 280 320 360 400 440 480 520 560 600 640 680 720 760 800 0 40 80 120 160 200 svgR without feMorphology svgR feMorphology dilate=3

Erosion

Erosion is the opposite of dilation. Here we erode with a value of 3

WH=c(800, 220) # window rect
svgR( wh=WH,
  graphPaper(WH, dxy=c(40,40), labels=TRUE), 
  text( 'svgR without feMorphology', xy=c(20,80), font.size=60, stroke="red", fill="blue"), 
  text( 'svgR feMorphology erode=3', xy=c(20,160), font.size=60, stroke="red", fill="blue", 
        filter=filter(
          feMorphology(radius=2, operator='erode')
        )
  )
)
0 40 80 120 160 200 240 280 320 360 400 440 480 520 560 600 640 680 720 760 800 0 40 80 120 160 200 svgR without feMorphology svgR feMorphology erode=3

Displacing (aka. Warping)

title: “ChangingShapes” output: html_document: toc: true theme: united —

Another approach to shape modification is to perform some kind of warping of a image shape. feDisplacementMap allows for this displacing an image according to the colors of a second image.

Here we illustrate using a linear gradient for the second image.

library(svgR)
WH=c(800, 200) # window rect
svgR( wh=WH, 
      symbol(
        rect( id="spec", xy=c(0,0), wh=WH, 
          fill=linearGradient( 
            xy1=c("40%",0), xy2=c("60%","0%"),
            colors=rep(c("red","green"),5) 
          )
        )
      ),
      use(xlink.href="#spec"),
      text("feDisplacementMap: linear gradient", xy=c(20,20), stroke='white', fill='white', font.size=20),
      line(xy1=c(100,WH[2]/2),xy2=c(700,WH[2]/2), 
           stroke='yellow', stroke.width=20,
           filter=
             filter( xy=c(0,0), filterUnits='userSpaceOnUse',
                feDisplacementMap(
                  in1="SourceGraphic",
                  in2=feImage(xlink.href='#spec'),
                  scale=50,
                  yChannelSelector="R"
                )
          )
      )
)
feDisplacementMap: linear gradient

Our second example uses a radial gradient as the second image

library(svgR)
WH=c(400, 400) # window rect
svgR( wh=WH, 
      symbol(
        rect( id="radDispl", xy=c(0,0), wh=WH, 
          fill=radialGradient( 
            xy1=c(0,0), xy2=c(1,1), 
            colors=rep(c("red","blue"),5) 
          )
        )
      ),
      use(xlink.href="#radDispl"),
       text("feDisplacementMap: radial gradient", xy=c(20,20), stroke='white', fill='white', font.size=20),
      line(xy1=c(0,150),xy2=c(400,150), 
           stroke='yellow', stroke.width=20,
           filter=
             filter( xy=c(0,0), filterUnits='userSpaceOnUse',
                feDisplacementMap(
                  in1="SourceGraphic",
                  in2=feImage(xlink.href='#radDispl'),
                  scale=50,
                  xChannelSelector="R",
                  yChannelSelector="R"
                )
          )
        ),
        line(xy1=c(0,260),xy2=c(800,260), 
         stroke='yellow', stroke.width=20,
         filter=
           filter( xy=c(0,0), filterUnits='userSpaceOnUse',
              feDisplacementMap(
                in1="SourceGraphic",
                in2=feImage(xlink.href='#radDispl'),
                scale=50,
                xChannelSelector="B",
                yChannelSelector="B"
              )
            )
        )
)
feDisplacementMap: radial gradient



Next we try a different approach: use the text as the displacement template and the image is a simple radial gradient

library(svgR)
WH=c(800, 600) # window rect
svgR( wh=WH, 
      symbol(
        g( id="radDisp2",  
           text("svgR", fill='red', cxy=c(.65,.45)*WH, font.size=200),
            text("in Action", fill='red', cxy=c(.65,.75)*WH, font.size=100)
        )
      ),
      rect(cxy=WH/2, wh=WH, 
           fill=radialGradient( colors=c("white","black") ),
           filter=filter(
                feDisplacementMap(
                  in1="SourceGraphic",
                  in2=feImage(xlink.href='#radDisp2'),
                  scale=50,
                  xChannelSelector="B",
                  yChannelSelector="R"
                )
           )
      )
)
svgR in Action

Next we try a different approach: use the text as the displacement template and the image is a simple radial gradient

library(svgR)
WH=c(800, 600) # window rect
svgR( wh=WH, 
      symbol(
        g( id="radDisp2",  
           text("svgR", fill='red', cxy=c(.65,.45)*WH, font.size=200),
            text("in Action", fill='red', cxy=c(.65,.75)*WH, font.size=100)
        )
      ),
      rect(cxy=WH/2, wh=WH, 
           fill=radialGradient( colors=c("grey","white","grey","black") ),
           filter=filter(
                feDisplacementMap(
                  in1="SourceGraphic",
                  in2=feImage(xlink.href="./IMG_4703.JPG"),
                  scale=50,
                  xChannelSelector="B",
                  yChannelSelector="R"
                )
           )
      )
)
svgR in Action

Finally we apply a displayment to an image.

library(svgR)
WH=c(800, 600) # window rect
svgR( wh=WH, 
      symbol(
        g( id="radDisp3",  
           text("Seville", fill='red', cxy=c(.8,.5)*WH, font.size=100)
        )
      ),
      image(cxy=WH/2, wh=WH, xlink.href="./IMG_4703.JPG",
           filter=filter(
                feDisplacementMap(
                  in1="SourceGraphic",
                  in2=feImage(xlink.href='#radDisp3'),
                  scale=150,
                  yChannelSelector="R"
                )
           )
      )
)
Seville

Finally, we use a filter as the second image.

library(svgR)
WH=c(800, 600) # window rect
svgR( wh=WH, 
      symbol(
        rect( id="symFunkyRect1", xy=c(0,0), wh=WH, 
          filter=funkyFilter('funky4Dispacement1')
        )
      ),
      #use(xlink.href="#symFunkyRect1"),
      text("feDisplacementMap: linear gradient", xy=c(20,20), stroke='white', fill='white', font.size=20),
#      text('Data Denier 4 Pres'
rect( cxy=WH/2, wh=c(600,20), fill='lightblue', 
           stroke='blue', stroke.width=2,
           filter=
             filter( xy=c(0,0), filterUnits='userSpaceOnUse',
                feDisplacementMap(
                  in1="SourceGraphic",
                  in2=feImage(xlink.href='#symFunkyRect1'),
                  scale=50,
                  yChannelSelector="R",
                  yChannelSelector="B"
                )
          )
      )
)
feDisplacementMap: linear gradient

Data Corruption

library(svgR)
WH=c(800, 400) # window rect
svgR( wh=WH, 
      symbol( 
        rect( id="dispy", xy=c(0,0)*WH,  wh=WH, fill='blue',
                filter = funkyFilter("funky2", baseFrequency = .005)
        )
      ),
      g(  lapply(1:50, function(i)text("Data", cxy=runif(2)*.8*WH, 
                          fill=rrgb(),  font.size=sample(10:30,1))),
          filter=filter( xy=c(0,0), filterUnits='userSpaceOnUse',
              feDisplacementMap(
                in1="SourceGraphic",
                in2=feImage( xlink.href='#dispy'),
                scale=50,
                yChannelSelector="R",
                xChannelSelector="B"
              )
          )
      ),
      text("Data corruption", xy=c(20,30), font.size=26)
)
Data Data Data Data Data Data Data Data Data Data Data Data Data Data Data Data Data Data Data Data Data Data Data Data Data Data Data Data Data Data Data Data Data Data Data Data Data Data Data Data Data Data Data Data Data Data Data Data Data Data Data corruption





>

Animating

Animation Overview

Animatinon can be broke into two basic types:

  • discrete: animation in which an attribute value changes instaneously at a given point in time.
  • continous: animation in which an attribute value changes or evolves over a period of time.

Discrete Animation is accomplished by using the set element, Discrete Animation is discussed further in the section Discrete Animation

Continous animition is accomplished using one of three elemens:

  • animate: transitions most attributes (or a CSS property) from one value to another over a period of time.
  • animateTransform: transitions transform attribute from one value to another over a period of time.
  • animateMotion: moves a graphical object according to a specified path description

Another way of putting this is

  • if no movement is involved: use animate
  • if movement is involved:
    • if the movement is defined by simple coordinates: use animate
    • if the movement is defined by the transfrom attribute (such as rotation): use animateTransform
    • if the movement is to be specified by a path, use animateMotion*

Continous Animations using the animate element are discussed further in the section Continous Animation Continous transistioning of the transform attribute is discussed in the section Continous Movement via the Transform Attribute Continous Movement by path specificaton is discussed in the section Continous Movement along a Path.

For any animation, whether discrete or continuous, that animation begins by being triggered by an event. That event might be a button press, or page loading, or completion of some animation. Events are discussed in Animation Events

For ease of presentation, all animation examples outside of sections Animation Events section and
One Line Teasers, are triggered using a button at the bottom right hand corner of the svg. These buttons are constructed by invoking either the toggle or playBar function. The code for both functions are provided in the utilities section. However, in practice one may want to trigger an events with other customes functions.

For better understanding of we begin by next exploring is Animation Events

Note: We do not discuss - animateColor, which specifies a color transformation over time. This is a deprecated element, since animate can perform the same task . We strongly recommend using animate in place of animateColor.

Animation Events

This section explains triggering animation events. By examining this section, one can discover how to create custom event triggers.

For simplicity, the animations here will be restricted to discreted animations, thus the set element will be used.

Onload

The simplest animation triggering is to trigger the animation upon loading the document (web page).
This was illustrated in the short teasers section, however I repeat the code here.

# not run
svgR( wh=c(600, 100), 
  line( xy1=c(0,50), xy2=c(600,50), 
    stroke.width=20, stroke.dasharray=8, stroke.dashoffset=16, stroke="lightblue",   
    animate(attributeName="stroke.dashoffset", from=16, to=0 , dur=0.5,   repeatCount="indefinite")
  )
)

As second illustration, consider the problem of filling a circle with an orange color 10 seconds after loading,. We do this by simply setting the begin attribute to 10. For the circle to revert back to green, we can provide either

  • a dur attribute to indicate the duration (how long the orange is to remain)
  • a stop attribute to indicate when to stop ( when the orange is to terminate)

This is an examle of the set animation which is discussed in the Set Animation Section

setAnimation

WH<-c(800,300)
txt<-c(
  "All circles will turn orange after 10 seconds",
  "The first will go back to green after 1 second",
  "The second will go back to green after 2 seconds",
  "The third will remain orange"
)
labs<-c("orange 1 sec","orange 2 sec","forever orange")
cxy<-rbind(WH[1]*c(.3,.5,.7), WH[2]*c(.7,.7,.7))
svgR( wh=WH,
      text("RELOAD BROWSER:", xy=c(0,40), font.size=25),
      lapply(1:4, function(i){ text(txt[i], xy=c(250,i*20+20), font.size=16)}),
      circle(id="setCir1e1", r=70, fill='lightgreen', cxy=cxy[,1],
          set( attributeName="fill", to="orange", begin=10, dur=1 )),
      circle(id="setCir1e1", r=70, fill='lightgreen', cxy=cxy[,2],
          set( attributeName="fill", to="orange", begin=10, end=12 )),
      circle(id="setCir1e1", r=70, fill='lightgreen', cxy=cxy[,3],
          set( attributeName="fill", to="orange", begin=10 )),
      lapply(1:3, function(i)text( labs[i], cxy=cxy[,i], font.size=16 ))
)
RELOAD BROWSER: All circles will turn orange after 10 seconds The first will go back to green after 1 second The second will go back to green after 2 seconds The third will remain orange orange 1 sec orange 2 sec forever orange

However, for performance reasons, it is unwise to have too many animations are running concurrently. For that reason, all animations in this section are initiated using the mouse or chaining.

MouseOver

WH<-c(800,200)
svgR( wh=WH,
    text("Circle turns orange on mouse over", xy=c(20,20), font.size=20),
    circle( cxy=WH/2, r=70, stroke="black", fill="lightgreen",
      set( attributeName="fill", 
           to="orange", begin="mouseover", end="mouseout") 
    )
)
Circle turns orange on mouse over

MouseOut

WH<-c(800,200)
svgR( wh=WH,
    text("Circle turns orange on mouse out", xy=c(20,20), font.size=20),
         circle( cxy=WH/2, r=70, stroke="black", fill="lightgreen",
      set( attributeName="fill", 
           to="orange", begin="mouseout", end="mouseover") 
    )
)
Circle turns orange on mouse out

MouseClick

WH<-c(800,200)
svgR( wh=WH,
    text("Circle turns orange for 1 second on mouse click", xy=c(20,20), font.size=20),
    circle( cxy=WH/2, r=70, stroke="black", fill="lightgreen",
      set( attributeName="fill", 
           to="orange", begin="click", dur=1) 
    )
)
Circle turns orange for 1 second on mouse click

A Simple Mouse Button

WH<-c(800,200)
svgR( wh=WH,
  text("Circle turns orange for 1 second on button press", xy=c(20,20), font.size=20),
  g( id='greenButton',
    rect( cxy=c(60,WH[2]/2), wh=c(100,20) , stroke="black", fill="green", 
    set(  attributeName="fill", 
           to="lightgreen", begin="greenButton.click", dur=.25) 
    ),
    text('Press Me', cxy=c(60,WH[2]/2), stroke="white")
  ),
    #
    circle( cxy=WH/2, r=70, stroke="black", fill="lightgreen",
      set( attributeName="fill", 
           to="orange", begin="greenButton.click", dur=1) 
    )
)
Circle turns orange for 1 second on button press Press Me

Chaining Events

WH<-c(800,200)
svgR( wh=WH,
   circle( cxy=c(.1,.5)*WH, r=20, stroke="black", fill="lightgreen",
      set( id = 'circEvent0', attributeName="fill", 
           to="orange", begin="click", dur=.1) 
    ),
   circle( cxy=c(.3,.5)*WH, r=20, stroke="black", fill="lightgreen",
      set( id = 'circEvent1', attributeName="r", 
           to="50", begin="circEvent0.end", dur=.1) 
    ),
   circle( cxy=c(.5,.5)*WH, r=20, stroke="black", fill="lightgreen",
      set( id = 'circEvent2', attributeName="r", 
           to="50", begin="circEvent1.end", dur=.1) 
    ),
   circle( cxy=c(.7,.5)*WH, r=20, stroke="black", fill="lightgreen",
      set( id = 'circEvent3', attributeName="r", 
           to="50", begin="circEvent2.end", dur=.1) 
    ),
    text("Click left most circle to start chain reaction", xy=c(20,20), font.size=20
    )
     

)
Click left most circle to start chain reaction
WH<-c(800,200)
svgR( wh=WH,
    text("Click circle to start chain reaction", xy=c(20,20), font.size=20),
    circle( cxy=c(.1,.5)*WH, r=20, stroke="black", fill="lightgreen",
      set( id = 'eventchain0', attributeName="fill", 
           to="orange", begin="click", dur=.05) 
    ),
    lapply( 1:20, function(i){
    polygon(
      points=c(0,20, 20,0, 0,-20),
      transform=paste0("translate(", 80+i*30,",100)"),
      fill="lightblue",stroke="black",
      set( id=paste0("eventchain",i),attributeName="fill", 
           to="red",  dur=.05,
           begin=paste0("eventchain",i-1,".end")
      )      
    )})

)
Click circle to start chain reaction

Java Script

Alert on Button Press

WH<-c(800,200)
rectid<-autoId()
svgR( wh=WH,
  script(
paste0(
'function showColor() {
                  alert("Color of the Rectangle is: "+
                  document.getElementById("',rectid,'").getAttributeNS(null,"fill"));
               }
'      
)),
  text("Alert on button press", xy=c(20,20), font.size=20),
  g( 
    circle(id=rectid, cxy=c(60,WH[2]/2), r=60 , stroke="black", fill="brown"),
    text('Press Me', cxy=c(60,WH[2]/2), stroke="white", fill='white', font.size=20),
    onClick="showColor()"
  )
)
Alert on button press Press Me

Animation Start-Stop on Button Press

WH<-c(800,200)
goStopCirAni<-autoId()
goStopButton<-autoId()
textStart<-autoId()
textStop<-autoId()
scriptGS<-paste0(
'var gsOn=false; ',
'function goStop() {
  var animation=document.getElementById("',goStopCirAni,'");
  var button=document.getElementById("',goStopButton,'");
  var textStart=document.getElementById("',textStart,'");
  var textStop=document.getElementById("',textStop,'");
  if(gsOn){
    gsOn=false;
    animation.endElement();
    button.setAttribute("fill","green");
    textStart.setAttribute("visibility","visible");
    textStop.setAttribute("visibility","hidden");
  } else{
    gsOn=true;
    animation.beginElement();
    button.setAttribute("fill","red")
    textStart.setAttribute("visibility","hidden");
    textStop.setAttribute("visibility","visible");
  }
};
')
svgR( wh=WH,
  script(scriptGS),
  text("Controling Animation by a Single Button Press", xy=c(20,20), font.size=20),
  g( # button
    rect( id=goStopButton, cxy=c(.2,.5)*WH, wh=c(140,60), rxy=c(30,30), 
          stroke="black", fill="green"),
    rect(  cxy=c(.2,.5)*WH, wh=c(130,50), rxy=c(30,30), 
          stroke="black", fill="white", opacity=.3),
    text('Start', id=textStart,
         cxy=c(.2,.5)*WH, fill="white", font.size=24, visibility='visible'),
    text('Stop',  id=textStop,
         cxy=c(.2,.5)*WH, fill="white", font.size=24, visibility='hidden'),
    onClick="goStop()"
  ),
  g(
      rect(cxy=WH/2, wh=WH/6, fill='lightblue', stroke='blue', stroke.width=3),
      animateTransform( id=goStopCirAni, attributeName="transform", 
                        type="rotate", from=c(0,WH/2), to=c(360,WH/2), 
                        dur=1, begin='indefinite', repeatCount="indefinite") 
  )
)
Controling Animation by a Single Button Press Start Stop

Animation Start-Stop on Button Press

WH<-c(800,200)
startButton<-autoId()
stopButton<-autoId()

button %<c-% function( ...){
  args<-list(...)
  stopifnot("id" %in% names(args) & "text" %in% names(args))
  defaults<-list( cxy=c(100,100), wh=c(140,60), rxy=c(30,30), font.size=24, text.fill='white', fill='green')
  args<-c(args, defaults[sapply(args[names(defaults)], is.null)])
  indx<-c("id","wh","cxy", "rxy", "font.size","fill","text", "text.fill") 
  sargs<-args[indx]
  args[indx]<-NULL
  g( id=sargs$id,
    rect(  cxy=sargs$cxy, wh=sargs$wh, rxy=sargs$rxy, 
          stroke="black", fill=sargs$fill),
    rect(  cxy=sargs$cxy, wh=sargs$wh-c(10,10), rxy=sargs$rxy,
          stroke="black", fill="white", opacity=.3),
    text(sargs$text,
         cxy=sargs$cxy, fill=sargs$text.fill, font.size=sargs$font.size),
    args
  )
}

svgR( wh=WH,
  script(scriptGS),
  text("Controling Animation by a Single Button Press", xy=c(20,20), font.size=20),
  button(id=startButton, text='start', fill='green',
      set(attributeName="visibility", to="hidden",  
          begin=paste0(startButton,".click"),
          end  =paste0(stopButton,".click" ))
  ),
  button(id=stopButton, text='stop', fill='red', visibility='hidden',
       set(attributeName="visibility", to="visible", 
           begin=paste0(startButton,".click" ),
           end  =paste0(stopButton,".click" ))
  ),
  rect(cxy=WH/2, wh=WH/6, fill='lightblue', stroke='blue', stroke.width=3,
      animateTransform( attributeName="transform", 
                        type="rotate", from=c(0,WH/2), to=c(360,WH/2), 
                        dur=1, 
                        begin=paste0(startButton,".click" ),
                        end  =paste0(stopButton,".click" ),
                        repeatCount="indefinite"
                        )
  )
)
Controling Animation by a Single Button Press start stop





Discrete Animation

Overview

The examples in the Chaining Events section use the set command to achieve animation. The set command can change a graphics attribute, but that change is instaneous. The set command is discrete, not continous.

Attributes that are to be changed are specified by attributeName, and the value to change to is given by values Each set element can have only one attribute value specification. Thus for multiple attributes, multiple set elements are required, although they can still use the same event to begin, and hence can run concurrently. A see Animate Onload

Specifying a duration (dur attribute) within a set element, determines how long the new value persists. Specifying an end attribute within a set element determines a time when the new value expires.

If a dur or end is not specified, then the new attribute value persists indefinitely. In all examples in this section, an end or a dur will be specified.

Fill

The fill attribute can be reset using the set command.

In this example we toggle the fill color of some text.

WH<-c(800,200)
svgR( wh=WH,
   toggleBar(WH),
   text( 'svgR', xy=c(40,120),  font.size=100, stroke="black", fill="lightgreen",
      set(attributeName="fill", to="green",
          begin=toggleButton.click('left'), 
          end=toggleButton.click('right')
          )
    )
)
svgR

Visibility & Opacity

The Opacity attributes specifies a level of transparncy, with 1 being opaque, and 0 being essentially invisible, and .5 being mid-way between. In contrast, the visibility attribute is either visible or hidden.

Set Visibility

Our first example toggles visibility

WH<-c(800,200)
svgR( wh=WH,
    toggleBar(WH),
   text( 'svgR', xy=c(40,120),  font.size=100, stroke="black", fill="lightgreen",
      set(attributeName="visibility", to="hidden",
          begin=toggleButton.click('left'), 
          end=toggleButton.click('right')
          )
    )
)
svgR

Set Opacity

This second example toggles Opacitiy between opaque and semi-transparent.

WH<-c(800,200)
svgR( wh=WH,
    toggleBar(WH),
   text( dataScience, cxy=WH/2-c(0,10),  font.size=100, stroke="black", fill="lightgreen"),
   ellipse(cxy=WH/2-c(0,30), rxy=WH/2-c(0,40), fill='lightblue',
           stroke='blue',
      set(attributeName="opacity", to=.5,
          begin=toggleButton.click('left'), 
          end=toggleButton.click('right')
          )
    )
)
数据科学

Positioning

Set Position x

WH<-c(800,200)
svgR( wh=WH,
   toggleBar(WH),
   text( 'svgR', xy=c(50,120),  font.size=100, stroke="black", fill="lightgreen",
      set(attributeName="x", to="550",
          begin=toggleButton.click('left'), 
          end=toggleButton.click('right')
          )
    )
)
svgR

Set Position xy

 WH<-c(800,200)
 svgR( wh=WH,
    toggleBar(WH),
    text( 'svgR', xy=c(50,120),  font.size=50, stroke="black", fill="lightgreen",
       set(attributeName="xy", to=c(200,50),
           begin=toggleButton.click('left'), 
           end=toggleButton.click('right')
           )
     )
)
svgR

Set Dimensions wh

 WH<-c(800,200)
 svgR( wh=WH,
     toggleBar(WH),
    rect(  xy=c(50,50),  wh=c(600,50), stroke="black", fill="lightgreen",
       set(attributeName="wh", to=c(200,100),
           begin=toggleButton.click('left'), 
           end=toggleButton.click('right')
           )
     )
 )

Set ViewBox (Zooming)

WH<-c(800,300)
svgR( wh=WH,
    toggleBar(WH),
    svg(xy=c(0,0),wh=c(200,200), viewBox=c(0,0,40,40),
      fill='lightgreen',
      rect(xy=c(1,1), wh=c(2,2), fill='red', stroke='black'),
      set(attributeName="viewBox", to=paste(0,0,4,4),
            begin=toggleButton.click('left'), 
            end=toggleButton.click('right')
      )
    )
)

Text Setting

Set Font Size

In this example letters change size sequentially. This effect is achieved by using set on each tspan element and triggering them using an offset to the button click.

WH<-c(800,160)
svgR( playBar(wh=WH),
      text( xy=c(40,100), lapply(1:26, function(i){
        tspan( LETTERS[i] , font.size=30, fill=rrgb(),
            set(attributeName='font.size', from=20,to=50,
                dur=.1, begin=paste0(playButton.click(),' + ',.1*i))
        )}
      ))
)
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z

Set Font Family

WH<-c(800,200)
svgR( wh=WH,
   toggleBar(WH),
   text( 'svgR', xy=c(40,100),  font.size=100,  fill="lightgreen", stroke='black',
      set(attributeName="font.family", to="fantasy",
          begin=toggleButton.click('left'), 
          end=toggleButton.click('right')
          )
    )
)
svgR





Continous Animation

Recall, animation can be broken up as discrete or continous.

Animate Element

The animate element is considered the general work-horse for continuous animation since it encompasses everything except transforms and motions along a path.

Stroke Attributes

Stroke Width

WH<-c(800,200)
svgR( 
    playBar(wh=WH),
    circle( cxy=WH/2, r=40, stroke="black", fill="yellow",
      animate(attributeName='stroke.width', from=0, to=40, 
                    begin=playButton.click(), dur=2, fill="remove") 
    )
)

Stroke dashoffset

WH<-c(800,200)
soffset<-40*7
svgR( 
    playBar(wh=WH),
    ellipse( cxy=WH/2, rxy=c(40,40), stroke="blue", fill="none",
             stroke.width=10, stroke.dasharray=soffset,
             stroke.dashoffset=soffset,
      animate(attributeName='stroke.dashoffset', from=soffset, to=0, 
                    begin=playButton.click(), dur=.5, fill="remove") 
    )
)

The Fill Attribute

Fill color

WH<-c(800,200)
svgR( 
    playBar(wh=WH),
    ellipse( cxy=WH/2, rxy=c(40,40), stroke="black", fill="red",
      animate(attributeName='fill', from='red', to='yellow', 
                    begin=playButton.click(), dur=2, fill="remove") 
    )
)

Animating Linear Gradient Stop Colors

WH=c(450,300)
svgR(
    playBar(wh=WH),
    defs(
      linearGradient(id="myLinGradAnimate1",
        xy1=c(.1,.1), xy2=c(.9,.9),             
       stop(offset=0,stop.color='green'), #stop is the stop element
       stop(offset=0.1,stop.color='black',
            animate( attributeName='stop.color',
              from='black',to='yellow',
              begin=playButton.click(), dur=3) 
       ),
       stop(offset=0.9,stop.color='red')
      )
    ),
    rect( cxy=c(200,150), 
          fill= "url(#myLinGradAnimate1)", stroke="blue",
          wh=c(200,200)
    )
)

Linear Gradient

WH=c(800,300)
svgR(
    playBar(wh=WH),
    defs(
      linearGradient(id="myLinGradAnimate2",
      colors=c('red','white','blue'),
      xy1=c(.5,0), xy2=c(0,1),             
        animate( attributeName='x2',
                from=0,to=1,
                begin=playButton.click(), dur=3
        )
      )
    ),
    rect( xy=c(20,20), wh=WH-c(40,80), 
          fill= "url(#myLinGradAnimate2)", stroke="blue"
    )
)

Animating Linear Gradient Offsets

WH=c(800,300)
colors=c('blue','white','red')
svgR(
    playBar(wh=WH),
    defs(
      linearGradient(id="myLinGradAnimate3",
        xy1=c(0,0), xy2=c(1,0),   
        lapply( 1:3, function(i){
          stop(offset=0, stop.color=colors[i], #stop is the stop element
            animate(attributeName='offset',
              from=.1*(i),to=.7+.1*(i), dur=3,
              begin=playButton.click())        
          )
        })
      )
    ),
    rect( xy=c(20,20), wh=WH-c(40,80), 
          fill= "url(#myLinGradAnimate3)", stroke="blue"
    )
)

Animating Linear Gradient Offset Beneath a Mask

WH=c(800,300)
colors=c('darkblue','white','black')
svgR(
    playBar(wh=WH),
    defs(
      linearGradient(id="myLinGradAnimate4",
        xy1=c(0,0), xy2=c(1,0),   
        lapply( 1:3, function(i){
          stop(offset=0, stop.color=colors[i], #stop is the stop element
            animate(attributeName='offset',
              from=.1*(i),to=.7+.1*(i), dur=.4, repeatCount=5,
              begin=playButton.click())        
          )
        })
      )
    ),
    text( "svgR",  cxy=c(.5,.4)*(WH-c(0,40)), font.size=150, 
          fill= "url(#myLinGradAnimate4)", stroke="blue"
    )
)
svgR

Animating the Radial Gradient Radius (r)

WH=c(800,300)
svgR(
    playBar(wh=WH),
    defs(
      radialGradient(id="myRadGradAnimate1",            
       cxy=c(.5,.5), fxy=c(.5,.5), r=.5,
       colors=c('red','white','green'),
             animate( attributeName='r',
               from=.5,to=.1,
               begin=playButton.click(), dur=3
             ) 
      )
    ),
    rect( xy=c(20,20), wh=WH-c(40,80), 
          fill= "url(#myRadGradAnimate1)", stroke="blue",
          wh=c(200,200)
    )
)

Animating the Radial Gradient Focus (fxy)

WH=c(800,300)
rgid<-autoId()
rgUrl<-sprintf('url(#%s)',rgid)
svgR(
    playBar(wh=WH),
    defs(
      radialGradient(id=rgid,            
       cxy=c(.2,.2), fxy=c(.5,.5), r=.5,
       colors=c('red','white','green'),
             animate( attributeName='cxy',
               from=c(.2,.2),to=c(.8,.8),
               begin=playButton.click(), dur=3
             ) 
      )
    ),
    rect( xy=c(20,20), wh=WH-c(40,80), 
          fill= rgUrl, stroke="blue",
          wh=c(200,200)
    )
)

Animating the Radial Gradient Center (cxy)

WH=c(800,300)
rgid<-autoId()
rgUrl<-sprintf('url(#%s)',rgid)
svgR(
    playBar(wh=WH),
    defs(
      radialGradient(id=rgid,            
       cxy=c(.5,.5), fxy=c(.2,.2), r=.5,
       colors=c('red','white','green'),
             animate( attributeName='fxy',
               from=c(.2,.2),to=c(.8,.8),
               begin=playButton.click(), dur=3
             ) 
      )
    ),
    rect( xy=c(20,20), wh=WH-c(40,80), 
          fill= rgUrl, stroke="blue",
          wh=c(200,200)
    )
)

Animating the Radial Gradient Stops

WH=c(800,300)
colors=c('red','white','green')
rgid<-autoId()
rgUrl<-sprintf('url(#%s)',rgid)
svgR(
    playBar(wh=WH),
    defs(
      radialGradient(id=rgid,           
        lapply( 1:3, function(i){
          stop(offset=0, stop.color=colors[i], #stop is the stop element
            animate(attributeName='offset',
              from=.1*(i),to=.7+.1*(i), dur=3,
              begin=playButton.click())        
          )
        })
    )),
    rect( xy=c(20,20), wh=WH-c(40,80), 
          fill= rgUrl, stroke="blue",
          wh=c(200,200)
    )
)

Coordinates Movement and Size Changes

Recall continous animition can be divide into animation that

  • involves movement:
  • animation that which does not.

Continous animation that does not involve movement is implement with the animate element.

The movement specication is one of the following 3:

  1. specification of beginning and ending coordinate values (xy or cxy) of an object to be changed (this section)
  2. specification of begining and ending transform attribute values of an object to be changed
  3. specification of path along which an object is to move. (

Here we describe the first. See the sections Continous Movement via the Transform Attribute and Continous Movement along a Path for 2 and 3.

Radius

WH<-c(800,200)
svgR( 
    playBar(wh=WH),
    ellipse( cxy=WH/2, rxy=c(40,40), stroke="black", fill="blue",
      animate(attributeName='rx', from=10, to=300, 
                    begin=playButton.click(), dur=2, fill="remove") 
    )
)

Coordinate: cx

WH<-c(800,200)
svgR( 
    playBar(wh=WH),
    circle( cxy=c(40,100), r=30, stroke="black", fill="lightgreen",
      animate(attributeName='cx', from='40', to='760', 
                    begin=playButton.click(), dur=1, fill="remove") 
    )
)

Coordinates: cxy

library(svgR)
WH<-c(800,300)
pt<-c(30,30)
svgR( 
    playBar(wh=WH),
    circle( cxy=pt, r=20, stroke="black", fill="lightgreen",
      animate(attributeName='cxy', from=pt, to=WH-pt, 
                    begin=playButton.click(), dur=2, fill="remove") 
    )
)

Circle changing size, fill color and location

WH<-c(800,200)
svgR( 
    playBar(wh=WH),
    circle( cxy=c(40,100), r=30, stroke="black", fill="lightgreen",
      animate(attributeName='cx', from='40', to='760', 
                    begin=playButton.click(), dur=1, fill="freeze"), 
      animate(attributeName='r', from='30', to='1', 
                    begin=playButton.click(), dur=1, fill="freeze"), 
      animate(attributeName='fill', from='red', to='red', 
                    begin=playButton.click(), dur=1, fill="freeze") 
    )
)

Text

Vertically Scrolling Text

WH<-c(800,200)
txt<-c(
'An ounce of algebra is worth of a ton of verbal argument.',' -JBS Haldane',
'Data sets do not give up their secrets easily.', 'They must be tortured to confess.', '-Jeff Hopper, Bell Labs',
'79.48% of all statistics are made up on the spot.','-John Paulos, Prof of Math',
'All models are wrong','but some are useful.', '-George E. P. Box','In God we trust.', 'All others must bring data.',
'-W. Edwards Deming','I guess I think of lotteries as a tax', 'on the mathematically challenged',
'― Roger Jones','Statisticians are like artists','they have a bad habit',' of falling in love with their models.',
'-- George Box')
dx<-30
WH2<- c(WH[1]-2*dx, dx)
svgR( 
    playBar(wh=WH),
    text("Stat Quotes", xy=c(30,30), font.size=30, stroke='blue'),
    svg(xy=c(dx,60), wh=WH2, viewBox=c(0,0, WH2),
      lapply(1:length(txt), function(i){
        text(txt[i], xy=c(0,i*dx-3), stroke='purple', stroke.width=2, font.size=dx-5)
      }),
      animate(attributeName='viewBox', from=c(0,0, WH2), to=c(0,dx*length(txt), WH2), 
          begin=playButton.click(), dur=1*length(txt), fill="remove")
    )
)
Stat Quotes An ounce of algebra is worth of a ton of verbal argument. -JBS Haldane Data sets do not give up their secrets easily. They must be tortured to confess. -Jeff Hopper, Bell Labs 79.48% of all statistics are made up on the spot. -John Paulos, Prof of Math All models are wrong but some are useful. -George E. P. Box In God we trust. All others must bring data. -W. Edwards Deming I guess I think of lotteries as a tax on the mathematically challenged ― Roger Jones Statisticians are like artists they have a bad habit of falling in love with their models. – George Box

Horizontally Scrolling Text

WH<-c(800,200)
txt<-c(
'Pure mathematics is, in its way, the poetry of logical ideas.  ~Albert Einstein',
'Mathematics is no more computation than typing is literature.—John Allen Paulos',
'A mathematician is a device for turning coffee into theorems. ~Paul Erdos', 
#'Anyone who cannot cope with mathematics is not fully human.',' At best he is a tolerable subhuman who has learned to wear shoes, bathe,',' and not make messes in the house. ~Robert Heinlein, Time Enough for Love',
"If there is a God, he's a great mathematician.  ~Paul Dirac",
'The laws of nature are but the mathematical thoughts of God.  ~Euclid', 
'The purpose of computing is insight, not numbers!—R. W. Hamming',
'Truth is ever to be found in the simplicity, and not in the multiplicity and confusion of things.—Newton')
dx<-30
WH2<- c(WH[1]-2*dx, dx)
svgR( 
    playBar(wh=WH),
    text("Math Quotes", xy=c(30,30), font.size=30, stroke='blue'),
    svg(xy=c(dx,60), wh=WH2, viewBox=c(0,0, WH2),
      lapply(1:length(txt), function(i){
        text(txt[i], xy=c(1.0*(i-1)*WH2[1],25), stroke='purple', stroke.width=2, font.size=dx/2)
      }),
      animate(attributeName='viewBox', from=c(0,0, WH2), to=c(WH2[1]*length(txt), 0, WH2), 
          begin=playButton.click(), dur=4*length(txt), fill="remove")
    )
)
Math Quotes Pure mathematics is, in its way, the poetry of logical ideas. ~Albert Einstein Mathematics is no more computation than typing is literature.—John Allen Paulos A mathematician is a device for turning coffee into theorems. ~Paul Erdos If there is a God, he’s a great mathematician. ~Paul Dirac The laws of nature are but the mathematical thoughts of God. ~Euclid The purpose of computing is insight, not numbers!—R. W. Hamming Truth is ever to be found in the simplicity, and not in the multiplicity and confusion of things.—Newton

Gausian Filter Primitive Animations

Gaussian Blur

This example illustrates a simple Gaussian Blur on the alpha channel where the standard deviation (in both the x and y directions) is the attribute being animated.

WH=c(800, 200) # window rect
svgR( 
  playBar(wh=WH),
  text('Animating Simple Gaussing Blur', xy=c(20,30), font.size=20),
  text( 'svgR', cxy=c(.5,.4)*WH, font.size=100, fill="blue",
    filter=filter( y=-5, height=100,
        feGaussianBlur(in1="SourceAlpha", stdDeviation=0,
          animate(attributeName='stdDeviation',
        from=0,to=20, dur=1, 
        begin=playButton.click())      
    ))
  )
)
Animating Simple Gaussing Blur svgR

Gaussian Blur \(\sigma_x\)

This example illustrates a simple Gaussian Blur on the alpha channel where the standard deviation in the x direction is the attribute being animated.

WH=c(800, 200) # window rect
svgR( 
  playBar(wh=WH),
  text('horizontal blurring repeated twice', xy=c(20,30), font.size=20),
  text( 'svgR', cxy=c(.5,.3)*WH, font.size=100, fill="blue",
       filter=filter( y=-5, height=100,
          feGaussianBlur(in1="SourceGraphic", stdDeviation=c(0,0), 
            animate(attributeName='stdDeviation',
          from=c(0,0),to=c(20,0), dur=1, repeatCount=2,
          begin=playButton.click() )      
      ))
  )
)
horizontal blurring repeated twice svgR

Depth Illusion

This example illustrates the illusion of depth, by animating both the x-position of the text and the x postion of the Guaussian blur is animated. The back and forth motion is achieved by chaining the animations.

WH=c(800, 240) # window rect
dur=.5
svgR(
  playBar(wh=WH),
  text("animate with chaining both text and shadow", xy=c(20,30), font.size=20),
  text( 'svgR', xy=c(200,150), font.size=150,  fill="lightblue", stroke="darkblue",
    filter = filter( xy=c(-10,-10), wh=c(800,120),
      feBlend( x=-10, width=800, 
        in1="SourceGraphic", 
        in2=feGaussianBlur( stdDeviation=3, 
          in1=feOffset( dxy=c(10,10), in1="SourceAlpha",
            animate(id='animateShadow1', attributeName='dx', from=10, to=-10, dur=dur,
              begin=playButton.click(), fill='freeze'
            ),
            animate(attributeName='dx', from=-10, to=10, dur=dur,
              begin="animateShadow1.end+0.5", fill='freeze'
            )
          ) 
        )
      )
    ),
    animate( attributeName="x", from=200, to=200+10, dur=dur,
               begin=playButton.click(), fill='freeze'
            ),
    animate(attributeName="x", from=200+10, to=200, dur=dur,
               begin="animateShadow1.end+0.5", fill='freeze'
            )
    )
)
animate with chaining both text and shadow svgR

Pattern

WH=c(600, 600) # window rect
svgR( 
  playBar(wh=WH),
  circle( cxy=WH/2, r=.3*WH[2], fill=
      pattern( xy=c(0,0), wh=c(40,40), patternUnits="userSpaceOnUse",
        rect(cxy=c(20,20), wh=c(30,30), fill='blue',
          animate( attributeName="fill", from='red', to='green', dur=.5,
               begin=playButton.click(), repeatCount=3
            )                  
          )
         )
  )
)

Radius

WH=c(600, 600) # window rect
svgR( 
  playBar(wh=WH),
  circle( cxy=WH/2, r=.3*WH[2], stroke='red', stroke.width=5,
      fill=
      pattern( xy=c(10,10), wh=c(40,40), patternUnits="userSpaceOnUse",
        circle(cxy=c(10,10), r=5, fill='blue',
          animate( attributeName="r", from=5, to=20, dur=.5,
               begin=playButton.click(), repeatCount=8
            )                  
          )
         )
  )
)

Radius

WH=c(800, 400) # window rect
svgR( 
  playBar(wh=WH),
  text('svgR', cxy=WH/2, font.size=250, stroke.width=2, stroke='blue', fill=
      pattern( xy=c(10,10), wh=c(40,40), patternUnits="userSpaceOnUse",
        circle(cxy=c(10,10), r=5, fill='blue',
          animate( attributeName="r", from=5, to=30, dur=.5,
               begin=playButton.click(), repeatCount=8
            )                  
          )
         )
  )
)
svgR

Color Rotation

hueRotate 1

WH=c(800, 400) # window rect
svgR( 
  playBar(wh=WH),
  rect(cxy=WH/2-c(0,15), wh=WH-c(40,100), 
    fill=linearGradient( xy1=c(0,0), xy2=c(1,1),
      colors= c('red','yellow','darkblue','brown','orange','green','pink','blue')),
      filter= filter( 
      feColorMatrix(type="hueRotate", values=0,
          animate( attributeName="values", from=0, to=360, dur=1,
              begin=playButton.click(), repeatCount=5
          )                  
      )  
    )
  )
)

hueRotate 2

WH<-c(800, 400) # window rect
colors<-c('red','yellow','darkblue','orange','green','blue')
svgR( 
  playBar(wh=WH),
  rect(cxy=WH/2-c(0,15), wh=WH-c(40,100), 
    fill=radialGradient( 
      colors= colors),
      filter= filter( 
      feColorMatrix(type="hueRotate", values=0,
          animate( attributeName="values", from=0, to=360, dur=1,
              begin=playButton.click(), repeatCount=5
          )                  
      )  
    )
  )
)

Shape Shifting

Morphology

Here we animate using feMorphology.

WH=c(800, 220) # window rect
svgR( 
  playBar(wh=WH), 
  text( 'feMorphology', cxy=WH/2-c(0,15), font.size=100, stroke="red", fill="none",
        filter=filter(
          feMorphology(radius=1, operator='dilate',
             animate( attributeName="radius", from=1, to=10, dur=2,
              begin=playButton.click(), repeatCount=5
             )
          )
        )
  )
)
feMorphology

This illustration uses both feTurbulence and feDisplacementMap to create a dissolve effect.

feTurbulence and feDisplacementMap

This illustration uses both feTurbulence and feDisplacementMap to create a dissolve effect.

library(svgR)
WH=c(800, 400) # window rect
svgR( 
      rect(xy=c(0,0), wh=WH, fill='lightblue'),
      playBar(wh=WH), 
      symbol(
        rect( id="feTurbulence2", xy=c(0,0), wh=WH, 
          filter=filter( 
            feMerge(
              feMergeNode(in1=feTurbulence(baseFrequency=0.005, numOctaves=1 )),
              feMergeNode(in1=feTurbulence(baseFrequency=c(.001,.01), numOctaves=2 )),              
              feMergeNode(in1=feTurbulence(baseFrequency=0.1, numOctaves=2 ))
          )
        )
      )),
      g(
      text("svgR", cxy=WH/2-c(0,40), stroke='black', font.size=180, stroke.width=2, fill='none'),
      text("feDisplacementMap", cxy=WH/2+c(0,80), stroke='red', font.size=50, stroke.width=2, fill='none'),
           filter=
             filter( xy=c(0,0), filterUnits='userSpaceOnUse',
                feDisplacementMap(
                  in1="SourceGraphic",
                  in2=feImage(xlink.href='#feTurbulence2'),
                  scale=150,
                  yChannelSelector="R",
                  xChannelSelector="B",
                  animate(id='animateFilter2', attributeName='scale', from=150, to=1,
                    begin=playButton.click(), dur=3, fill='freeze'),
                  animate(id='animateFilter2', attributeName='scale', from=1, to=150,
                    begin='animateFilter2.end + 1', dur=2, fill='freeze')
                )
          )
      )
)
svgR feDisplacementMap

Warping Text

library(svgR)
WH=c(800, 400) # window rect
x<-c(.3,.4,.5,.6)
dx<-.1
y<-.3
sc<-50
col=c("black","gold","red")
XWH<-WH-c(0,100)
svgR( 
      playBar(wh=WH), 
      symbol(
        rect( id="dispGradAni36", xy=c(0,0)*WH,  wh=XWH, fill='blue',
                filter=filter(
                  feTurbulence(baseFrequency=.005, numOctaves=3, seed=100),
                  feComponentTransfer(
                    feFuncR(type="linear", slope=3, intercept=-1),
                    feFuncG(type="linear", slope=2, intercept=-1),
                    feFuncB(type="linear", slope=4, intercept=-1),
                    feFuncA(type="linear", slope=0, intercept=1)
                  ),
                  feColorMatrix(type="saturate")
                )
        )
      ),
      text("A Data Flow", xy=c(30,30), font.size=25, fill="blue"),
      g(
        lapply(1:30, function(i)text(cxy=runif(2)*.7*WH+c(50,50), 
                                     fill=rrgb(), "Flowing Data", font.size=sample(10:30,1)
        )),
      filter=
        filter( xy=c(0,0), filterUnits='userSpaceOnUse',
          feDisplacementMap(
            in1="SourceGraphic",
            in2=feImage(xlink.href='#dispGradAni36'),
            scale=50,
            yChannelSelector="R",
            xChannelSelector="B",
            animate(id='animateFilter5', attributeName='scale', from=sc, to=1,
              begin=playButton.click(), dur=4, fill='freeze'),
            animate(id='animateFilter6', attributeName='scale', from=1, to=sc,
              begin='animateFilter5.end + 3', dur=4, fill='freeze')
          )
        )
      )
)
A Data Flow Flowing Data Flowing Data Flowing Data Flowing Data Flowing Data Flowing Data Flowing Data Flowing Data Flowing Data Flowing Data Flowing Data Flowing Data Flowing Data Flowing Data Flowing Data Flowing Data Flowing Data Flowing Data Flowing Data Flowing Data Flowing Data Flowing Data Flowing Data Flowing Data Flowing Data Flowing Data Flowing Data Flowing Data Flowing Data Flowing Data

Controling the Animation Rates

Using keyTimes and a Linear calcMode

KeyTimes is a vector of strictly increasing numeric values starting with 0 and ending with 1. Each keyTime value represents a point in time scaled by the duration, dur.

Values is vector (or list) describing the value of the attribute at each keyTime. With calcMode = linear, the values between each keyTime interval is interpolated linearly.

In the example below, the values are the x coordinate of the center of the circle, cx, so the circle appears to move at a constant velocity as the cx varies linearly within both intervals (The keyTime intervals are \([0 , 0.95]\) and \([ 0.95, 1]\), and represent in seconds the \([0, 2.85]\) and \([ 2.85, 3]\) )

WH<-c(800,150)
d<-c("m", WH*c(0,.0), "l", WH*c(1,0))
aniMotion<-autoId()
svgR( 
    playBar(wh=WH),
    rect(xy=c(0,0),wh=WH, fill="#AAFFAA"),
    rect(xy=c(0,0),wh=WH*c(0.2,1), fill="#FFAAAA"),  
    text("Using animate for attribute='cx' with calcMode='linear'", xy=c(30,30), font.size=25, fill="blue"),
    text("Slow", xy=c(30,WH[2]-30), font.size=20, fill="red"),
    text("Fast", xy=c(WH[1]/2,WH[2]-30), font.size=20, fill="green"),
        
    circle( cxy=c(0,.5)*WH, r=10, stroke="black", fill="yellow",
      animate( attributeName='cx', values=c(40, 144, 760),
                    keyTimes=c(0,0.95,1),  
                    begin=playButton.click(), dur=5)
    )
)
Using animate for attribute=‘cx’ with calcMode=‘linear’ Slow Fast

The rate changes for calcMode Linear are step functions.

Note: The default calcMode for animate is linear so we can omit it. (Unlike animateMotion, where the default calcMode is paced )

Using keySplines with animate for a Gradual Rate Change

Fast at the beginning and Slow at the end

  WH<-c(800,200)
  svgR( 
      playBar(wh=WH),
      rect(xy=c(0,0),wh=WH, fill=linearGradient(x12=c(0,1),colors=c("lightgreen","pink"))),
      text("Using animate for attribute='cx' with calcMode='spline'", xy=c(30,30), font.size=25, fill="blue"),
      circle( cxy=c(40,100), r=30, stroke="black", fill="yellow",
        animate(attributeName='cx', values=c(40,760), calcMode="spline",
              keyTimes=c(0,1), keySplines="0 0.75 0.25 1",
                      begin=playButton.click(), dur=5, fill="remove") 
      )
  )
Using animate for attribute=‘cx’ with calcMode=‘spline’

Slow at the beginning and Fast at the end

  WH<-c(800,200)
  svgR( 
      playBar(wh=WH),
      rect(xy=c(0,0),wh=WH, fill=linearGradient(x12=c(0,1),colors=c("pink","lightgreen"))),
      text("Using animate for attribute='cx' with calcMode='spline'", xy=c(30,30), font.size=25, fill="blue"),
      circle( cxy=c(40,100), r=30, stroke="black", fill="yellow",
        animate(attributeName='cx', values=c(40,760), calcMode="spline",
              keyTimes=c(0, 1), keySplines=".75 0 1 .25",
                      begin=playButton.click(), dur=5, fill="remove") 
      )
  )
Using animate for attribute=‘cx’ with calcMode=‘spline’

Slow at the beginning and at the end

  WH<-c(800,200)
  svgR( 
      playBar(wh=WH),
      rect(xy=c(0,0),wh=WH, fill=linearGradient(x12=c(0,1),colors=c("pink","lightgreen","pink"))),
      text("Using animate for attribute='cx' with calcMode='spline'", xy=c(30,30), font.size=25, fill="blue"),
      circle( cxy=c(40,100), r=30, stroke="black", fill="yellow",
        animate(attributeName='cx', values=c(40,760), calcMode="spline",
              keyTimes=c(0, 1), keySplines="0 0.5 0.5 1",
                      begin=playButton.click(), dur=5, fill="remove") 
      )
  )
Using animate for attribute=‘cx’ with calcMode=‘spline’

Fast at the beginning and at the end

  WH<-c(800,200)
  svgR( 
      playBar(wh=WH),
      rect(xy=c(0,0),wh=WH, fill=linearGradient(x12=c(0,1),colors=c("lightgreen","pink","lightgreen"))),
      text("Using animate for attribute='cx' with calcMode='spline'", xy=c(30,30), font.size=25, fill="blue"),
      circle( cxy=c(40,100), r=30, stroke="black", fill="yellow",
        animate(attributeName='cx', values=c(40,760), calcMode="spline",
              keyTimes=c(0, 1), 
              keySplines=c(0,.5,1,.5),
                      begin=playButton.click(), dur=5, fill="remove") 
      )
  )
Using animate for attribute=‘cx’ with calcMode=‘spline’

Two slow downs

  WH<-c(800,200)
  svgR( 
      playBar(wh=WH),
      rect(xy=c(0,0),wh=WH, fill=linearGradient(x12=c(0,1),colors=c("lightgreen","pink","lightgreen", "pink","lightgreen"))),
      text("Using animate for attribute='cx' with calcMode='spline'", xy=c(30,30), font.size=25, fill="blue"),
      circle( cxy=c(40,100), r=30, stroke="black", fill="yellow",
        animate(attributeName='cx', values=c(40,(40+760)/2, 760), calcMode="spline",
              keyTimes=c(0, .5, 1), 
              keySplines=rep(c(0,.5,1,.5),2),
                      begin=playButton.click(), dur=5, fill="remove") 
      )
  )
Using animate for attribute=‘cx’ with calcMode=‘spline’

Animating Path-Defined Shapes

Spike and Twist

WH<-c(800,300)
M<-30; K<-4
N<-M*K
IM<-seq.int(K,N,K)
theta<-seq(0,2*pi, length.out=N+1)
theta<-theta[-1]
pts <-rbind(cos(theta),sin(theta))
r0<-matrix(80,2,N)
r1<-matrix(20,2,N)
r1[,IM]<-140
pts0<-r0*pts + WH/2
pts1<-r1*pts + WH/2
phi<-pi/4
m2<-matrix(c(cos(phi),sin(phi),-sin(phi),cos(phi)),2,2)
pts2<-pts
pts2[,IM]<-m2%*%pts[,IM]
pts2<-r0*pts2+WH/2
as.path<-function(ptsX){
  c("M",ptsX[,1],"C",ptsX[,2:(dim(ptsX)[2]) ],"Z")
}
goodFill<-'#DDDDFF'
badFill<-'#CC0000'
svgR(
  playBar(wh=WH),
  path(fill=goodFill, stroke='blue', d=as.path(pts0),
    animate( attributeName='d', 
      values=list(as.path(pts0),as.path(pts1),as.path(pts2)),
      begin=playButton.click(), dur=5, fill="remove"
    ),
    animate( attributeName='fill',
      values=c(goodFill,badFill,goodFill),
      begin=playButton.click(), dur=5, fill="remove"
    )
  )
)

Path Animation

WH<-c(800,300)
M<-30; K<-4
N<-M*K
IM<-seq.int(K,N,K)
theta<-seq(0,2*pi, length.out=N+1)
theta<-theta[-1]
pts <-rbind(cos(theta),sin(theta))
r0<-matrix(90,2,N)
r1<-matrix(70,2,N)
r1[,IM]<-140
pts0<-r0*pts + WH/2
pts1<-r1*pts + WH/2
as.path<-function(ptsX){
  c("M",ptsX[,1],"L",ptsX[,2:(dim(ptsX)[2]) ],"Z")
}
svgR(
  playBar(wh=WH),
  path(fill='red', stroke='blue', d=as.path(pts0),
    animate( attributeName='d', 
      values=list(as.path(pts0),as.path(pts1)),
      begin=playButton.click(), dur=2, fill="remove"
    )
  ), 
  lapply(1:N, function(i){
    circle(cxy=pts0[,i], r=5, fill='green')
  }),
  lapply(1:N, function(i){
    circle(cxy=pts1[,i], r=5, fill='blue')
  })
)

Stab

WH<-c(800,300)
N<-8
xMarg<-50
NX<-160
X<-seq(0,N*pi, length.out=NX)
sx<-(WH[1]-2*xMarg)/(N*pi)
x<-X*sx+xMarg
wave<-function(phase){
  sy<- - WH[2]/6
  y<-sin(X+phase)
  y<-y*sy+WH[2]/2
  rbind(x,y)
}
as.path<-function(ptsX){
  c("M",ptsX[,1],"C",ptsX[,1:(dim(ptsX)[2]) ])
}
values=lapply(seq(0,4*pi,length.out=24), function(alpha){
  as.path(wave(alpha))
})
svgR(
  playBar(wh=WH),
  text("sin wave", xy=c(20,40), fill='blue', font.size=30),
  path(
    fill='none', stroke='blue', 
    d=as.path( rbind(x, rep(WH[2]/2,NX))),
    animate( attributeName='d', 
      values=values,
      begin=playButton.click(), dur=10, fill="remove"
    )
  )
)
sin wave

Sin Wave

WH<-c(800,300)
N<-8
xMarg<-50
NX<-160
X<-seq(0,N*pi, length.out=NX)
sx<-(WH[1]-2*xMarg)/(N*pi)
x<-X*sx+xMarg
wave<-function(alpha){
  sy<- - WH[2]/6
  y<-sin(alpha)*sin(X)
  y<-y*sy+WH[2]/2
  rbind(x,y)
}
as.path<-function(ptsX){
  c("M",ptsX[,1],"C",ptsX[,1:(dim(ptsX)[2]) ],"Z")
}

values=lapply(seq(0,8*pi,length.out=24), function(alpha){
  as.path(wave(alpha))
})
svgR(
  playBar(wh=WH),
  text("standing wave", xy=c(20,40), fill='blue', font.size=30),
  path(
    fill='red', stroke='blue', 
    d=as.path( rbind(x, rep(WH[2]/2,NX))),
    animate( attributeName='d', 
      values=values,
      begin=playButton.click(), dur=5, fill="remove"
    )
  )
)
standing wave

Standing Wave

WH<-c(800,300)
pts<-c(1,1)

N<-8
xMarg<-50
NX<-160
X<-seq(0,N*pi, length.out=NX)
sx<-(WH[1]-2*xMarg)/(N*pi)
x<-X*sx+xMarg
wave<-function(alpha){
  sy<- - WH[2]/6
  y<-sin(alpha)*sin(X)
  y<-y*sy+WH[2]/2
  rbind(x,y)
}
as.path<-function(ptsX){
  c("M",ptsX[,1],"C",ptsX[,1:(dim(ptsX)[2]) ],"Z")
}

values=lapply(seq(0,8*pi,length.out=24), function(alpha){
  as.path(wave(alpha))
})
svgR(
  playBar(wh=WH),
  text("standing wave", xy=c(20,40), fill='blue', font.size=30),
  path(
    fill='red', stroke='blue', 
    d=as.path( rbind(x, rep(WH[2]/2,NX))),
    animate( attributeName='d', 
      values=values,
      begin=playButton.click(), dur=5, fill="remove"
    )
  )
)
standing wave

Animating Light Positions

Diffuse PointLight Positioning Using x, y, z Attribute Specification

Here we rotate the light source about the center of the circle, starting slightly above 0 to \(\pi/2\) using Diffuse + pointlight Specifying both x and y animation positions seperately.

WH<-c(800,200)
rho<-90
r=100
theta<-seq(0,2*pi,length.out=20)
x=rho*cos(theta)+ WH[1]/2
y=rho*sin(theta)+ WH[2]/2
z=rep(10,length(theta))

svgR( 
  playBar(wh=WH),
  svg(wh=WH,
    circle( cxy=WH/2, r= r, fill="green", stroke="black" , 
      filter=filter(
        feComposite( 
          operator="arithmetic", k1234=c(0,1,1,0),
          in1="SourceGraphic",
          in2= feDiffuseLighting( lighting.color="white", diffuseConstant=1.2,
            in1="SourceGraphic",
            fePointLight( xyz=c(WH/2,10), #lightPos(pi/2),
              animate( attributeName='x', values=x, dur=5, begin=playButton.click(),fill="remove"),
              animate( attributeName='y', values=y, dur=5, begin=playButton.click(),fill="remove")
            ) 
          )
        )
      )
    )
  )
)

Diffuse PointLight Postioning Using the xyz Attribute Specification

Again we rotate the light source about the center of the circle, starting slightly above 0 to \(\pi/2\) using Diffuse + pointlight.

This time we use the combo attribute xyz when specifying the positioning of the light.

WH<-c(800,200)
rho<-90
r=100
rho<-r
lightPos<-mapply( 
function(theta, phi){
      c(
      x=rho*cos(phi)*cos(theta)+ WH[1]/2,
      y=rho*cos(phi)*sin(theta)+ WH[2]/2,
      z=rho*sin(phi)/10
      )
},
seq(0,2*pi,length.out=20), 
seq(pi/2,pi/8,length.out=20) 
)
svgR( 
  playBar(wh=WH),
  svg(wh=WH,
    circle( cxy=WH/2, r= r, fill="green", stroke="black" , 
      filter=filter(
        feComposite( 
          operator="arithmetic", k1234=c(0,1,1,0),
          in1="SourceGraphic",
          in2= feDiffuseLighting( lighting.color="white", diffuseConstant=1.2,
            in1="SourceGraphic",
            fePointLight( xyz=c(WH/2,10), #lightPos(pi/2),
              animate(attributeName='xyz',
              values=lightPos,
              dur=5, 
              begin=playButton.click(),fill="remove"
              )
            ) 
          )
        )
      )
    )
  )
)





Continous Movement via the Transform Attribute

AnimateTransform

AnimateTransform is used to animate the “transform” attribute. Thus animate transform applies to shapes (and groups) and can animate any combination of the following:

  • translate
  • scale
  • rotate
  • skewX
  • skewY

We start by showing some basic operations:

AnimateTransform Translate

WH<-c(800,200)
dur=5
svgR( 
  playBar(wh=WH), 
  graphPaper(wh=WH, dxy=c(40,40), labels=TRUE),
    text( "Animate Transform:  type=translate", xy=c(30,40), fill='darkblue', font.size=20),
    rect( xy=c(80,80), wh=c(40,40), fill='lightblue', stroke='blue', opacity=0.5,
      animateTransform(attributeName="transform", type='translate',
              from=c(0,0), to=c(600,0), 
              begin=playButton.click(), dur=dur
      )
    )    
)
0 40 80 120 160 200 240 280 320 360 400 440 480 520 560 600 640 680 720 760 800 0 40 80 120 160 200 Animate Transform: type=translate

AnimateTransform Rotate

WH<-c(800,250)
svgR( 
  playBar(wh=WH), 
  graphPaper(wh=WH, dxy=c(20,20), labels=TRUE),
  text( "Animate Transform Rotation", xy=c(25,25), fill='blue', font.size=20),
  polygon( points=c( c(.5,.5), c(.55,.6), c(.45,.6))*(WH+c(0,-20)),
    fill='#80FFFF',stroke='red',
    animateTransform(attributeName="transform", type='rotate',
            from=c(0,WH/2), to=c(360,WH/2), 
            begin=playButton.click(), dur=2, 
            repeatCount=5 
    )
   )
)
0 20 40 60 80 100 120 140 160 180 200 220 240 260 280 300 320 340 360 380 400 420 440 460 480 500 520 540 560 580 600 620 640 660 680 700 720 740 760 780 800 0 20 40 60 80 100 120 140 160 180 200 220 240 Animate Transform Rotation
WH<-c(800,150)
N<-12
fop<-.07*(2 + (1:N)*(8-2)/N) 
dur=1.25
rc=5
svgR( 
  playBar(wh=WH), 
  text( "I'm busy", xy=c(300,80), fill='red', font.size=60, visibility='hidden',
    set(attributeName="visibility", to="visible",begin=playButton.click(), dur=dur*rc)
  ),
  g(
    lapply(1:N, function(i){
      rect( xy=c(150,50),wh=c(3,10), fill='blue', fill.opacity=fop[i],
          transform=list( rotate=c(i*360/N, 150, 50), translate=c(0,10))
      )
    }),
    animateTransform(attributeName="transform", type='rotate',
            from=c(0,150,50), to=c(360,150,50), 
            begin=playButton.click(), dur=dur, 
            repeatCount=rc 
    )
  )
)
I’m busy

AnimateTransform Scale

Animated Scaling With the Origin as the Fixed Point

Scaling can be animated using animateTransform, however there are a few caveats.

A simple animateTransform with type=scales will scale about the origin, so items not at the origin appear to be moved.

WH<-c(800,250)
dur=5
rect.wh=c(50,50)
svgR( 
  playBar(wh=WH), 
  graphPaper(wh=WH, dxy=c(20,20), labels=TRUE),
  svg( wh=WH, xy=c(0,0),
    text( "Scaling, with the origin as the fixed point", xy=c(30,30), fill='red', font.size=50, visibility='hidden'),
    rect( xy=c(100,50),wh=rect.wh, fill='lightblue', stroke='blue', opacity=.6,
      animateTransform(attributeName="transform", type='scale',
              from=c(1,1), to=c(2,2), 
              begin=playButton.click(), dur=dur
      )
    )    
  )

)
0 20 40 60 80 100 120 140 160 180 200 220 240 260 280 300 320 340 360 380 400 420 440 460 480 500 520 540 560 580 600 620 640 660 680 700 720 740 760 780 800 0 20 40 60 80 100 120 140 160 180 200 220 240 Scaling, with the origin as the fixed point

Norw here, the graphPaper was outside of the scope of animateTransform, so it was not scaled

To scale with a given fixed point pt we need to be a slightly more clever.

Animate Transform Scaling With a Given Fixed Point.

In this example we take a rectangle centered at the middle of the viewPort (WH/2) and use animateTransform with type=scale to scale it, while keeping the rectangle center as a fixed point.

We get this by 2 nested applications of animateTransform.

WH<-c(800,250)
dur=5
pt<-WH/2
alpha<-c(3,2)
rect.wh=c(50,50)
svgR( 
  playBar(wh=WH), 
  graphPaper(wh=WH, dxy=c(20,20), labels=TRUE),
  svg( wh=WH, xy=c(0,0),
    text( "Transform ", xy=c(30,30), fill='red', font.size=50, visibility='hidden'),
    g(
      rect( xy=pt-rect.wh/2, wh=rect.wh, fill='lightblue', stroke='blue', opacity=.6,
        animateTransform(attributeName="transform", type='scale', 
              from=c(1,1), to=alpha, 
              begin=playButton.click(), dur=dur
        )
      ),
      animateTransform(attributeName="transform", type='translate', additive='sum',
        from=c(0,0), to=(c(1,1)-alpha)*pt, 
        begin=playButton.click(), dur=dur
      )     
    )
  )
)
0 20 40 60 80 100 120 140 160 180 200 220 240 260 280 300 320 340 360 380 400 420 440 460 480 500 520 540 560 580 600 620 640 660 680 700 720 740 760 780 800 0 20 40 60 80 100 120 140 160 180 200 220 240 Transform

This works, but the to calculation requires too much forethought.

An easier approach is to use an svg element.

Animate Transform Scaling With a Given Fixed Point Using svg.

This example, like the last example, takes a rectangle centered at the middle of the viewPort and employs animateTransform to scale it, keeping the rectangle center fixed.

However, instead of using two applications of animatedTransform, we wrap the rectangle to be animated inside an svg element. In particular, we set the viewBox of that svg element to have an origin at at the given point -pt, while we keeping the same width and height. We use the same viewPort for this svg as it’s container (xy=c(0,0),wh=WH).

The effect is that the c(p,0) point inside this svg is wilt appears at pt. Thus, centering the rectangle at c(0,0) gives ust the desired result.

WH<-c(800,250)
dur=5
pt<-WH/2
rect.wh=c(50,50)
alpha<-c(3,2)
svgR( 
  playBar(wh=WH), 
  graphPaper(wh=WH, dxy=c(20,20), labels=TRUE),
  svg( wh=WH, xy=c(0,0), 
    text( "Transform ", xy=c(30,30), fill='red', font.size=50, visibility='hidden'),
    svg( viewBox=c(-pt, WH),
    # all contained coordinates are relative to xy=pt
      circle(cxy=c(0,0), r=200, fill='red', opacity=.3),
      rect( cxy=c(0,0), wh=rect.wh, fill='lightgreen', stroke='blue', opacity=.6,
        animateTransform(attributeName="transform", type='scale', 
              from=c(1,1), to=alpha, 
              begin=playButton.click(), dur=dur
        )
      ) 
    )   
  )
)
0 20 40 60 80 100 120 140 160 180 200 220 240 260 280 300 320 340 360 380 400 420 440 460 480 500 520 540 560 580 600 620 640 660 680 700 720 740 760 780 800 0 20 40 60 80 100 120 140 160 180 200 220 240 Transform

WH<-c(800,250)
dur=5
pt<-WH/2
alpha<-c(3,2)
svgR( 
  playBar(wh=WH), 
  graphPaper(wh=WH, dxy=c(40,40), labels=TRUE),
  svg( wh=WH, xy=c(0,0),
    text( "Transform ", xy=c(30,30), fill='red', font.size=50, visibility='hidden'),
    g(
      rect( cxy=pt, wh=c(50,50), fill='lightblue', stroke='blue', opacity=.6,
        animateTransform(attributeName="transform", type='scale', 
              from=c(1,1), to=alpha, 
              begin=playButton.click(), dur=dur
        )
      ),
      animateTransform(attributeName="transform", type='translate', additive='sum',
        from=c(0,0), to=(c(1,1)-alpha)*pt, 
        begin=playButton.click(), dur=dur
      )     
    )
  )
)
0 40 80 120 160 200 240 280 320 360 400 440 480 520 560 600 640 680 720 760 800 0 40 80 120 160 200 240 Transform

AnimateTransform Skew

Skewing Along the X Axis with the Origin as the Fixed Point

Here we take a rectangle located at c(0,0) and use animateTransform type=‘skewX’ to skew it.

WH<-c(800,250)
svgR( 
  playBar(wh=WH), 
  graphPaper(wh=WH, dxy=c(20,20), labels=TRUE),
  text( "Animate Transform skewX", xy=c(400,25), fill='blue', font.size=20),
  rect( xy=c(0,0), wh=c(100,100), fill='lightblue', stroke='blue', opacity=.6,
    animateTransform(attributeName="transform", type='skewX',
            from=0, to=45, 
            begin=playButton.click(), dur=5 
            
    )
   )
)
0 20 40 60 80 100 120 140 160 180 200 220 240 260 280 300 320 340 360 380 400 420 440 460 480 500 520 540 560 580 600 620 640 660 680 700 720 740 760 780 800 0 20 40 60 80 100 120 140 160 180 200 220 240 Animate Transform skewX

Skewing Along the Y Axis with the Origin as the Fixed Point

Here we take a rectangle located at c(0,0) and use animateTransform type=‘skewY’ to skew it.

WH<-c(800,250)
svgR( 
  playBar(wh=WH), 
  graphPaper(wh=WH, dxy=c(20,20), labels=TRUE),
  text( "Animate Transform skewY", xy=c(400,25), fill='blue', font.size=20),
  rect( xy=c(0,0), wh=c(100,100), fill='lightblue', stroke='blue', opacity=.6,
    animateTransform(attributeName="transform", type='skewY',
            from=0, to=45, 
            begin=playButton.click(), dur=5 
            
    )
   )
)
0 20 40 60 80 100 120 140 160 180 200 220 240 260 280 300 320 340 360 380 400 420 440 460 480 500 520 540 560 580 600 620 640 660 680 700 720 740 760 780 800 0 20 40 60 80 100 120 140 160 180 200 220 240 Animate Transform skewY

Skewing Along the X Axis with the Origin with a Given Fixed Point

In this example we take a rectangle centered at the middle of the viewPort (WH/2) and use animateTransform with type=skewX to skew it, while keeping the rectangle center as a fixed point.

We employ, the same trick as before, (which should be standard by now) We wrap the rectangle to be animated inside an svg element. We then set the viewBox of that svg element to have an origin at the point -pt,

The net effect is that the c(p,0) point inside this svg is wilt appears at pt. Thus, centering the rectangle at c(0,0) gives us the desired result.

WH<-c(800,260)
rect.wh<-c(100,100)
pt<-WH/2
dur=5
svgR( 
  playBar(wh=WH), 
  graphPaper(wh=WH, dxy=c(20,20), labels=TRUE),
  text( "Animate Transform skewX", xy=c(400,25), fill='blue', font.size=20),
    svg( viewBox=c(-pt, WH),
    # all contained coordinates are relative to xy=pt
      rect( cxy=c(0,0), wh=rect.wh, fill='lightgreen', stroke='blue', opacity=.6,
        animateTransform(attributeName="transform", type='skewX', 
              from=0, to=45, 
              begin=playButton.click(), dur=dur
        )
      ) 
    )  
)
0 20 40 60 80 100 120 140 160 180 200 220 240 260 280 300 320 340 360 380 400 420 440 460 480 500 520 540 560 580 600 620 640 660 680 700 720 740 760 780 800 0 20 40 60 80 100 120 140 160 180 200 220 240 260 Animate Transform skewX

Chaining Transformations





Continous Movement along a Path

AnimateMotion

Animate motion is used to move a shape along a path

This first example illustrates using from and to.

WH<-c(800,150)
svgR( 
    playBar(wh=WH),
     text("Straight line", xy=c(30,30), font.size=25, fill="blue"),
     circle( cxy=c(20,WH[2]/2-15), r=10, stroke="black", fill="yellow",
      animateMotion(from=c(0,0), to=c(WH[1]-40,0), 
                    begin=playButton.click(), dur=2, fill="remove") 
    )
)
Straight line

Note: The from, to produces a relative effect: cxy is moved to the right.

Animating Text Along a Path

Using Animate Motion with Fixed Text Orientation

This example illustrates text being moved along a path while maintaining text orientation. Here the path d is specified using absolute coordinates.

Using absolute coordinates

WH<-c(800,150)
y0<-WH[2]/2
r <-WH[2]/4
x = seq(100,800, by=20)
y = y0 + r*sin((x-100/40))
pts<-rbind('T',x,y)
d<-c('M', c(60,y0),"Q",c(70,y0), c(80,y0), pts)
dur=10
svgR( 
    playBar(wh=WH),
     text("Flat", xy=c(30,30), font.size=25, fill="blue"),
     path(d=d , stroke='lightblue', stroke.width=3, fill='none'), 
     text("data", cxy=c(0,0),  
        stroke="black", 
      animateMotion(path=d,  
                    begin=playButton.click(), dur=dur, fill="remove") 
    )
)
Flat data

Note In the above, text data is located at cxy=c(0,0) and then moved to the path. Thus, when the animation completes, the text position is restored to c(0,0). To have the initail text postion on the path, we set circle parameter cxy=(60,y0), and then use relative coordinates instead. This is illustrated in the next example

Using Relative Coordinates

WH<-c(800,150)
y0<-WH[2]/2
r <-WH[2]/4
x = seq(100,800, by=20)
y = y0 + r*sin((x-100/40))
pts<-rbind('t',diff(x),diff(y) )
d<-c('m', c(0,0),"q",c(10,0), c(20,0), pts)
dur=10
svgR( 
    playBar(wh=WH),
     text("Flat", xy=c(30,30), font.size=25, fill="blue"),
     path(d=c('M', c(60,y0),d) , 
     stroke='lightblue', stroke.width=3, fill='none'), 
     text("data", cxy=c(60,y0),  
        stroke="black", 
      animateMotion(path=d,  
                    begin=playButton.click(), dur=dur, fill="remove") 
    )
)
Flat data

Animating Text Auto Text Orientation

The key to allowing the the text rotate is to set the rotate attribute to auto. In addition, we use absolute coordinates with the path beginning at c(0,0) and the text at c(0,0). Then we put both in a group and use a translate transform to relocate to c(60,y0).

WH<-c(800,150)
y0<-WH[2]/2
r <-WH[2]/4
x = seq(100,800, by=20)
y = y0 + r*sin((x-100/40))
pts<-rbind('T',x-60,y-y0)
d<-c('M', c(0,0),"Q",c(10,0), c(20,0), pts)
svgR( 
    playBar(wh=WH),
    text("Auto", xy=c(30,30), font.size=25, fill="blue"),
    g(
      path(d=d , stroke='lightblue', stroke.width=3, fill='none'), 
      text("data", cxy=c(0,0),  
        stroke="black", 
        animateMotion(path=d,  
                      begin=playButton.click(), dur=10, fill="remove", rotate='auto') 
      ),
      transform=list(translate=c(60,y0))
    )
)
Auto data

A Bouncing Ball

Here we use path to provide the trajectory of a bouncing ball.

WH<-c(800,300)
rxy<-c(50,250)
startPt<-c(0, 0)
NumberOfJumps<-4
da<-sapply(1:NumberOfJumps,function(i){
  endPt<-c(rxy[1]*2*i, 0)+startPt
  c( "A", rxy, 0, 0, 1, endPt )
})
d<-c("M",startPt,da)
ani.id<-autoId()
splat.id<-autoId()
splat.dur<-.1
svgR( 
    playBar(wh=WH),
    ellipse( cxy=c(20,280), rxy=c(10,10), stroke="black", fill="yellow",
      animateMotion(id=ani.id, path=d, 
                    begin=playButton.click(), dur=3, fill="freeze"),
      animate(attributeName="rx",from=10,to=50,begin=paste0(ani.id,".end"), dur=splat.dur, fill="freeze"),
      animate(id=splat.id, attributeName="ry",from=10,to=2, begin=paste0(ani.id,".end"), dur=splat.dur, fill="freeze" ),
      set(attributeName="cx", to="20",  begin=playButton.click(), fill='freeze'),
      set(attributeName="cy", to="280", begin=playButton.click(), fill='freeze'),
      set(attributeName="rx", to="10",  begin=playButton.click(), fill='freeze'),
      set(attributeName="ry", to="10",  begin=playButton.click(), fill='freeze')
    ),
    text('Splat', font.size=90, cxy=c(650,200), stroke='red', fill='red', visibility='hidden',
      set(attributeName='visibility', to="visible",  begin=paste0(ani.id,".end"), fill='freeze'),
      set(attributeName='visibility', to="hidden",  begin=playButton.click(), fill='freeze')
    )
)
Splat

Although not too bad, the speed of the ball is constant. We can easily alter that using keySplines. See Bouncing Ball With KeySplines

Variable Speed with Animation Motion

Linear

WH<-c(800,150)
d<-c("m", WH*c(0,.0), "l", WH*c(1,0))
aniMotion<-autoId()
svgR( 
    playBar(wh=WH),
    rect(xy=c(0,0),wh=WH, fill="#AAFFAA"),
    rect(xy=c(0,0),wh=WH*c(0.2,1), fill="#FFAAAA"),  
    text("animateMotion of Path with CalcMode='linear'", xy=c(30,30), font.size=25, fill="blue"),
    text("Slow", xy=c(30,WH[2]-30), font.size=20, fill="red"),
    text("Fast", xy=c(WH[1]/2,WH[2]-30), font.size=20, fill="green"),
        
    circle( cxy=c(0,.5)*WH, r=10, stroke="black", fill="yellow",
      animateMotion(id= aniMotion, path=d, 
                    keyTimes=c(0,0.95,1), keyPoints=c(0,0.2,1), calcMode="linear",
                    begin=playButton.click(), dur=5), 
      animateMotion(path=c("M", WH*c(.1,.5), 
                    begin=paste0("on.load;",aniMotion,".onend"), dur=0, fill="freeze"))
    )
)
animateMotion of Path with CalcMode=‘linear’ Slow Fast

Note The default calcMode for animateMotion is paced which results in the values of keyTimes and keySplines being ignored

Spline

WH<-c(800,150)
d<-c("m", WH*c(0,.0), "l", WH*c(1,0))
aniMotion<-autoId()
svgR( 
    playBar(wh=WH),
    rect(xy=c(0,0),wh=WH, fill=linearGradient(x12=c(0,1),colors=c("lightgreen","pink"))),
    text("animateMotion of Path with CalcMode='spline'", xy=c(30,30), font.size=25, fill="blue"),
    text("Fast", xy=c(30,WH[2]-30), font.size=20, fill="green"),
    text("Slow", xy=c(WH[1]-70,WH[2]-30), font.size=20, fill="red"),
        
    circle( cxy=c(0,.5)*WH, r=10, stroke="black", fill="yellow",
      animateMotion(id= aniMotion, path=d, calcMode="spline",
                    keyPoints=c(0,1),
                    keyTimes=c(0,1), keySplines="0 0.75 0.25 1",
                    begin=playButton.click(), dur=5), 
      animateMotion(path=c("M", WH*c(.1,.5), 
                    begin=paste0("on.load;",aniMotion,".onend"), dur=0, fill="freeze"))
    )
)
animateMotion of Path with CalcMode=‘spline’ Fast Slow

A Bouncing Ball with keySplines

Here we use path to provide the trajectory of a bouncing ball.

WH<-c(800,300)
rxy<-c(50,250)
startPt<-c(0, 0)
NumberOfJumps<-4
da<-sapply(1:NumberOfJumps,function(i){
  endPt<-c(rxy[1]*2*i, 0)+startPt
  c( "A", rxy, 0, 0, 1, endPt )
})
d<-c("M",startPt,da)
ani.id<-autoId()
splat.id<-autoId()
splat.dur<-.1
svgR( 
    playBar(wh=WH),
    ellipse( cxy=c(20,280), rxy=c(10,10), stroke="black", fill="yellow",
      animateMotion(id=ani.id, path=d, 
                    calcMode="spline",
                    keyPoints=seq(0,1,length.out=1+NumberOfJumps),
                    keyTimes=seq(0,1,length.out=1+NumberOfJumps), 
                    keySplines=rep( c(0,.6,1,.4), NumberOfJumps),
                    begin=playButton.click(), dur=3, fill="freeze"),
      animate(attributeName="rx",from=10,to=50,begin=paste0(ani.id,".end"), dur=splat.dur, fill="freeze"),
      animate(id=splat.id, attributeName="ry",from=10,to=2, begin=paste0(ani.id,".end"), dur=splat.dur, fill="freeze" ),
      set(attributeName="cxy", to=c(20,280),  begin=playButton.click(), fill='freeze'),
      set(attributeName="rxy", to=c(10,10),  begin=playButton.click(), fill='freeze')

    ),
    text('Splat', font.size=90, cxy=c(650,200), stroke='red', fill='red', visibility='hidden',
      set(attributeName='visibility', to="visible",  begin=paste0(ani.id,".end"), fill='freeze'),
      set(attributeName='visibility', to="hidden",  begin=playButton.click(), fill='freeze')
    )
)
Splat