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 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")
Use devtools to install from github
library(devtools)
install_github("mslegrand/svgR")
Another way to install is to use the drat package manager.
From within R session
A.
install.packages("drat", repos="http://cran.rstudio.com")
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)
either
or
Note Note the mustache is three backticks
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.
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.
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 ")
)
)
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.
Next we break apart the code into it’s basic components, starting with the function call svgR
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.
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.
This text element has 3 components: an xy attribute (in orange), text content (in green), and a child element named tspan (in blue).
The tspan element contains an dy attribute (orange) and text content (green)
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.
In the previous section we walked though an example where we encountered
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:
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 |
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.
Other containers elements are defs, symbol.
However, these are not displayed directly, but are used by reference.
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.
These Modifiers modify the appearence graphical element. More specifically,
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(…) |
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!
This section discusses some of the basics of coordinates systems, 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
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)
)
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)
)
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)
)
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)
)
Summarizing
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))
)
)
Note: The viewBox of the child svg is c(0,0,200,150). When not specified the viewBox defaults to:
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:
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))
)
)
The transform attribute allows for multiple linear operations to be applied prior to rendering.
SVG supports the composition 5 types transforms:
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.
To illustrate the effects of transform we first create a custom element, for our illustration. It will consist of an ensemble of
# 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
WH=c(800, 400) # window rect
svgR(
myBack(WH, "Without any Transform"),
cirCirSqr(cxy=WH/2)
)
We want:
This suggests grouping the circCirSqr with the transform.
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) ) )
)
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)))
)
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.
We begin by presenting two examples:
WH=c(800, 400) # window rect
svgR(
myBack(WH, "With Transform scale=.5"),
cirCirSqr(cxy=WH/2, transform=list(scale=c(.5,1)))
)
WH=c(800, 400) # window rect
svgR(
myBack(WH, "With Transform scale=.5"),
cirCirSqr(cxy=WH/2, transform=list(scale=.5) )
)
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)
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
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)))
)
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)))
)
Reflections are a special case of scaling. Just use a negative as a scale factor. We give 2 examples:
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)))
)
Like scaling, rotations default to having the origin be the fixed point.
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)
)
)
Observer: Rotation is about the origin.
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))
)
)
There are two skews available: skewX, skewY. They are illustrated as following:
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)
)
)
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)
)
)
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))
)
)
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))
)
)
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
But first some background
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} \]
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]) )
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) ) )
)
)
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
svgR(
myBack(WH, "Rotation though the center using matrix"),
cirCirSqr(cxy=WH/2 , transform=list( matrix= A[1:6] ) )
)
To keep a clear conscience, we provide three alternatives for a value representing a transform sequence.
Consider the tranlate, rotate scale sequence.
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))
)
)
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))
)
)
The helper functions, translate, rotate, scale are included as part of the svgR api.
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)"
)
)
In this section we discuss shapes. Shapes are circles, ellipses, lines, paths, polygons, polylines, rectangles.
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 )
)
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:
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.
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' )
)
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:
Alternative we can specify the center of the rectangle
Also we can specify the dimensions in 2 ways:
WH<-c(600, 100) # window rect
svgR( wh=WH,
rect( xy=c(50,20), wh=c(100,40), stroke='blue', fill='none' )
)
For a circle, we specify the center and the radius r. The center can be specified in 2 ways:
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'))
)
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:
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 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.
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') )
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') )
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') )
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)
)
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)
)
)
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)
)
)
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)
)
)
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)
)
)
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)
)
)
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\).
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 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
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" )
)
)
)
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')
)
)
)
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
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") )
)
)
)
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")
)
)
)
)
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)'
)
)
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")
)
)
A good reference is: http://www.hongkiat.com/blog/scalable-vector-graphics-text/
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. |
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())
})
)
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())
})
)
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)
)
}
)
)
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:
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)
)
}
)
)
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() )}
))
)
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() )}
))
)
Baseline.shift can be used to create super/subscripts. Values for baseline.shift are
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
)
}
)
)
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'
)
)
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')
)
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))
)
)
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) )
)
)
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
)
)
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')
)
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')
)
Fill’er up
At this point, we have seen several shapes, with the fill attribute specified either as
However, we can also fill with
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 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)
)
)
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:
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”,
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)
)
)
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)
)
)
)
)
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')
)
)
)
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)
)
)
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
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!!!:
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. |
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” |
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))
)
)
)
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)")
)
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.
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))
)
)
We begin by illustrating a simple filter.
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")
)
)
)
Examining the above example we note
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.
We can visualize this as
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")
)
)
)
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")
)
)
)
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")
)
)
)
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'))
)
}
)
)
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.
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 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
)
)
)
}
)
)
(!!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=" ")
)
)
)
}
)
)
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:
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)
)
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)
)
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)
)
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)
)
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)
)
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)
)
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)
)
)
)
}
)
)
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.
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")
)
)
)
)
)
Compariong the operator options over, in, out, atop, xor for feComposite:
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 )
)
})
)
Here we compare feComposite for an source with opacity=50%
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 )
)
})
)
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),"'")
‘
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 )
)
})
)
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")
))
)
)
)
)
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)"
)
)
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
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") )
)
)
)
)
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)")
)
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"
)
)
)
)
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"
)
)
)
)
)
)
)
Let’s return to our shadow example
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"
)
)
)
)
)
)
)
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"
)
)
)
)
)
)
)
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 )
)
})
)
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 )
)
})
)
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:
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)
)
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)
)
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)
)
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)
)
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)
)
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)
)
To create textures, we often employ the feTurbulance filter primitive. The feTurlence supports two types:
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
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))
)
})
)
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)
)
)
})
)
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 )
)
)
})
)
The type attribute of feTurbulence can have one of two values:
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))
)
By using feTurbulance, feComponentTransfer and feColorMatrix we can discover some intersting effects. For example, define the following filter
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))
)
})
)
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))
)
})
)
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))
)
})
)
To create a wood texture we use
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')
)
)
)
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')
)
)
)
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')
)
)
)
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')
)
)
)
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')
)
)
)
|
To produce 3-D effect using lighting/shading, such as seem in the above letter, involves specifying the following three ingredients:
|
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
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
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)
)
The geometry of the surface requires
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.
‘
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.
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}\).
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 |
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),"'")
‘
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),"'")
‘
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),"'")
‘
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.
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 \]
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 |
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),"'")
‘
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),"'")
‘
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),"'")
‘
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.
There are 3 possible of light sources:
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)))
)
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 |
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),"'")
‘
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),"'")
‘
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.
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)
})
)
})
)
Here we repeat, but with a constant distance from the center
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
)
})
)
})
)
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
)
})
)
})
)
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. |
|
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.
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),"'")
‘
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),"'")
‘
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
)
})
)
})
)
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
)
})
)
})
)
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')
)
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')
)
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')
)
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')
)
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')
)
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')
)
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')
)
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")
)
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
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")
)
Shiny beveling is a popular example of what can be accomplished using the right 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"
)
)
)
)
)
)
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()
)
)
In this section we create several shiny letters S using the sbevelFilter
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()
)
)
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()
)
})
)
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])
)
})
)
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”)
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])
)
})
)
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()
)
)
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")
)
)
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')
)
)
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)")
)
#```{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)"
)
)
#```{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)"
)
)
#```{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)"
)
)
#```{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)"
)
)
#```{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)"
)
)
#```{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)"
)
)
#```{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)")
)
)
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)
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')
)
)
)
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')
)
)
)
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')
)
)
)
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"
)
)
)
)
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"
)
)
)
)
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"
)
)
)
)
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"
)
)
)
)
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"
)
)
)
)
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"
)
)
)
)
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)
)
>
Animatinon can be broke into two basic types:
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:
Another way of putting this is
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.
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.
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
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 ))
)
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.
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")
)
)
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")
)
)
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)
)
)
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
)
)
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")
)
)})
)
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.
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')
)
)
)
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.
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')
)
)
)
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')
)
)
)
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')
)
)
)
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')
)
)
)
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')
)
)
)
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')
)
)
)
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))
)}
))
)
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')
)
)
)
Recall, animation can be broken up as discrete or continous.
The animate element is considered the general work-horse for continuous animation since it encompasses everything except transforms and motions along a path.
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")
)
)
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")
)
)
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")
)
)
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)
)
)
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"
)
)
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"
)
)
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"
)
)
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)
)
)
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)
)
)
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)
)
)
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)
)
)
Recall continous animition can be divide into animation that
Continous animation that does not involve movement is implement with the animate element.
The movement specication is one of the following 3:
Here we describe the first. See the sections Continous Movement via the Transform Attribute and Continous Movement along a Path for 2 and 3.
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")
)
)
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")
)
)
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")
)
)
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")
)
)
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")
)
)
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")
)
)
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())
))
)
)
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() )
))
)
)
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'
)
)
)
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
)
)
)
)
)
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
)
)
)
)
)
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
)
)
)
)
)
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
)
)
)
)
)
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
)
)
)
)
)
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
)
)
)
)
)
This illustration uses both feTurbulence and feDisplacementMap to create a dissolve effect.
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')
)
)
)
)
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')
)
)
)
)
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)
)
)
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 )
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")
)
)
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")
)
)
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")
)
)
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")
)
)
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")
)
)
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"
)
)
)
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')
})
)
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"
)
)
)
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"
)
)
)
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"
)
)
)
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")
)
)
)
)
)
)
)
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"
)
)
)
)
)
)
)
)
AnimateTransform is used to animate the “transform” attribute. Thus animate transform applies to shapes (and groups) and can animate any combination of the following:
We start by showing some basic operations:
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
)
)
)
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
)
)
)
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
)
)
)
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
)
)
)
)
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.
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
)
)
)
)
This works, but the to calculation requires too much forethought.
An easier approach is to use an svg element.
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
)
)
)
)
)
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
)
)
)
)
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
)
)
)
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
)
)
)
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
)
)
)
)
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")
)
)
Note: The from, to produces a relative effect: cxy is moved to the right.
This example illustrates text being moved along a path while maintaining text orientation. Here the path d is specified 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")
)
)
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
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")
)
)
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))
)
)
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')
)
)
Although not too bad, the speed of the ball is constant. We can easily alter that using keySplines. See Bouncing Ball With KeySplines
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"))
)
)
Note The default calcMode for animateMotion is paced which results in the values of keyTimes and keySplines being ignored
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"))
)
)
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')
)
)