Adding new content to an SVG document

Before focusing on some examples in detail, let's look at a few instances where new content is added dynamically. Browser support for some of these is spotty, not because of DOM support, but because of the use of SMIL or filters in some of these.

http://srufaculty.sru.edu/david.dailey/svg/createElementBrowser.html

These particular illustrations are neither as broad nor representative as they could be. The general technique of adding new content to documents is ultimately as broad a topic as that of SVG itself. However, these examples illustrate some of the range and complexity of what is possible, and may give you some ideas for things you might want to accomplish.

There are two main ways of adding new content into an SVG document: createElementNS() and cloneNode(). Their purposes are similar, but their syntax and use cases are bit different. Before getting into both of them, let me mention that, for both, the following methods are used:

So, let's use a script to create a new element:

<svg xmlns="http://www.w3.org/2000/svg" width="100%"
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<script><![CDATA[
xmlns="http://www.w3.org/2000/svg"
xlink="http://www.w3.org/1999/xlink"

function add(){
var C=document.createElementNS(xmlns,"circle")
C.setAttributeNS(null, "r", 30)
C.setAttributeNS(null, "cx", evt.clientX)
C.setAttributeNS(null, "cy", evt.clientY)
C.setAttributeNS(null, "opacity", .5)
C.setAttributeNS(null, "fill", "red")
document.documentElement.appendChild(C)
}
]]></script>
<rect width="100%" height="100%" fill="white" onclick="add()"/>
<text id="Text" x="67" y="90" font-size="17"
font-family="arial" fill="black">
Click anywhere to add something</text>
<rect x="55" y="70" height="30" width="285" onclick="add()"
stroke="black"
stroke-width="2" fill="blue" opacity=".5" rx="10">
</rect>
</svg>

You will note from the above example, that:

Items 1 and 3, above, could be changed by adding a statement like

document.documentElement.setAttribute("onclick","add(evt)")

or if you prefer the more verbose (and probably standards-compliant):

document.documentElement.addEventListener("click", function(evt){add(evt)}, false)

to <script> at the very beginning.of the script (outside the function) , and then, literally, clicks will be allowed everywhere, including atop the newly added circles.

In the next example, we show that both declarative and scripted methods may be used together in the same document. (In fact, both scripted and declarative animation may be used together, and even in ways that interact with one another!)

<svg xmlns="http://www.w3.org/2000/svg" width="100%"
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<script><![CDATA[
xmlns="http://www.w3.org/2000/svg"
xlink="http://www.w3.org/1999/xlink"

function add(){
var C=document.createElementNS(xmlns,"circle")
C.setAttributeNS(null, "r", 50)
var x=20+Math.random()*300
var y=20+Math.random()*150
C.setAttributeNS(null, "cx", x)
C.setAttributeNS(null, "cy", y)
C.setAttributeNS(null, "opacity", .5)
C.setAttributeNS(null, "fill", Color())
document.getElementById("underlayer").appendChild(C)
}
function Color(){
var R=parseInt(Math.random()*255)
var G=parseInt(Math.random()*255)
var B=parseInt(Math.random()*255)
return "rgb("+R+","+G+","+B+")"
}

]]>
</script>
<rect width="100%" height="100%" fill="white"/>
<g id="underlayer" />
<text id="Text" x="67" y="90" font-size="17"
font-family="arial" fill="black">
Click here to add something</text>
<rect x="55" y="70" height="30" width="250" onclick="add()"
stroke="black"
stroke-width="2" fill="blue" opacity=".5" rx="10">
<set attributeName="fill" begin="mouseover" to="green"/>
<set attributeName="stroke-width" begin="mouseover" to="5"/>
<set attributeName="fill" begin="mouseout" to="blue"/>
<set attributeName="stroke-width" begin="mouseout" to="2"/>
</rect>
</svg>

From the above, we observe that:

Cloning Nodes

Cloning an existing object rather than building a new on can save on a lot of code. I'll demonstrate with two examples.

function add(evt){
var C=evt.target
var N=C.cloneNode(false)
N.setAttribute("x",Math.random()*300)
N.setAttribute("y",Math.random()*200)
document.documentElement.appendChild(N)
}

]]></script>
<text font-size="17" font-family="arial" y="40" x="20"
fill="black">Click any blue shape</text>
<rect x="55" y="50" height="50" width="85" stroke="#503"
onmousedown="add(evt)"
stroke-width="3" fill="blue" fill-opacity=".5" rx="20"
stroke-dasharray="9,5,2,5">

In this relatively simple example, working in all SVG supported browsers, we arm the <rect> to call the function add() whenever it is clicked. The function first finds the node itself (namely the <rect>) and clones it. The parameter "false" means that it does not copy any child nodes it may find inside the rectangle. We then leave most of the object's eleven attribute values intact, but decide, only, to change the two of them responsible for its positioning namely "x" and "y."

In the next example, the advantages of cloning are even clearer. Unfortunately, it doesn't work in Chrome or Safari 5 yet, because of the dynamic SMIL nodes. But it does in Firefox 4(beta), Opera 10 and Internet Explorer with ASV.

In this example, we see, that an object with children (in this case the object has an <animate> tag as its first child), can be cloned and in such a way that not only its attributes but also its children are cloned.

<svg xmlns="http://www.w3.org/2000/svg" width="100%"
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<script><![CDATA[
xmlns="http://www.w3.org/2000/svg"
xlink="http://www.w3.org/1999/xlink"

function add(evt){
var C=evt.currentTarget
var N=C.cloneNode(true)
N.setAttribute("fill",Color())
var s=(4*Math.random()+1)
N.firstChild.setAttribute("dur",s)
document.documentElement.appendChild(N)
}
function Color(){
var R=parseInt(Math.random()*255)
var G=parseInt(Math.random()*255)
var B=parseInt(Math.random()*255)
return "rgb("+R+","+G+","+B+")"
}

]]>
</script>
<g onmousedown="add(evt)" fill="blue">
<animateTransform attributeName="transform" type="rotate"
dur="5s" values="0,100,100;360,100,100" repeatCount="indefinite"/>
<text x="67" y="70" font-size="17" font-family="arial" fill="black">
Click me</text>
<rect x="55" y="50" height="30" width="90" stroke="black"
stroke-width="2" fill="inherit" opacity=".5" rx="10" />
</g>
</svg>

In this case we have:

Evaluating Nodes (getAttribute)

The method getAttribute() gives us a way of asking what the current value of some attribute of a given node is. As an example, to get the x coordinate of the center of a circle with id="C", we might use syntax like this:

var myCircle=document.getElementById("C")
var value=myCircle.getAttribute("cx")

In the following example, we use getAttribute to find the values of a particular object's size, and then use that to construct slightly smaller objects.

<svg xmlns="http://www.w3.org/2000/svg" width="100%"
xmlns:xlink="http://www.w3.org/1999/xlink" >
<script><![CDATA[
xmlns="http://www.w3.org/2000/svg"
xlink="http://www.w3.org/1999/xlink"

function add(evt){
var C=evt.target
var N=C.cloneNode(true)
var rx=C.getAttribute("rx")
var ry=C.getAttribute("ry")
var cy=C.getAttribute("cy")
//N.setAttribute("cy",cy-5)
//above line doesn't work since this value
//is overridden by animation
N.setAttribute("rx",rx-10)
N.setAttribute("ry",ry-5)
N.setAttribute("transform","translate(0,"+(62-ry)+")")
document.documentElement.appendChild(N)
}

]]></script>
<radialGradient id="r1" cx="50%" cy="90%" r="34%" fy="80%"
spreadMethod="reflect" gradientUnits="objectBoundingBox">
<stop offset=".1" stop-color="black" stop-opacity="0"/>
<stop offset=".8" stop-color="orange"/>
<stop offset=".9" stop-color="white" stop-opacity="0"/>
<stop offset="1" stop-color="grey"/>
</radialGradient>

<text font-size="23" font-family="serif" x="20" y="40" fill="black">
Click the oval</text>
<ellipse ry="60" rx="110" fill="url(#r1)" stroke="black"
onmousedown="add(evt)" stroke-width="2" >
<animate attributeName="cx" type="translate" dur="9s"
values="5%;90%;5%" repeatCount="indefinite"/>

<animate attributeName="cy" type="translate" dur="13s"
values="5%;90%;5%" repeatCount="indefinite"/>

</ellipse>
</svg>

About this example we should note the following:

The last three issues are addressed somewhat in this very similar example. The differences in the code are briefly considered:

function add(evt){
var C=evt.target
var N=C.cloneNode(false)
var rx=C.getAttribute("rx")
var ry=C.getAttribute("ry")
var cy=parseInt(C.getAttribute("cy") )
N.setAttribute("cy",cy+5)
N.setAttribute("rx",rx-10)
N.setAttribute("ry",ry-5)
C.parentNode.appendChild(N)
}

So this last example has introduced only one new concept, getAttribute, but it has provided a chance to explore some earlier ideas in a bit more detail.