Tuesday 25 August 2009

Dynamically generating SVG in an HTML page

In this post, I want to briefly list the less obvious aspects of using SVG to draw a game board. These are notes from my initial implementation so there may well be more polished alternatives.

For this work I used a recent version of Firefox and I have not yet investigated other browsers in any depth so your mileage with other browsers may vary.

My work was aimed at dynamically creating a page (hence the use of Javascript) but the same techniques could be used to create a static HTML page including SVG.

This project uses XML, SVG and Javascript as well as simple HTML. I have not provided any links to information on these subjects; I recommend finding a good book on XML and Javascript but a simple web search generated enough information on SVG.

Embedding SVG in HTML

I did not want to just have a stand-alone SVG page but an SVG picture embedded in an HTML page. This is because I wanted to make use of HTML elements and I also found HTML better for loading Javascript than stand-alone SVG.

I found that I needed to create the HTML page as an XML page so the start of the page is as follows:


<?xml version="1.0"?>
<html xmlns="http://www.w3.org/1999/xhtml">
xmlns:svg="http://www.w3.org/2000/svg"
>
<head>


This provides both a default XML namespace for the HTML (actually XHTML but don't worry about that) and a namespace for use by SVG elements. This is important as the browser needs to be able to tell whether any element in the page is meant to be HTML or SVG. We will come back to namespaces later. If you are not familiar with XML namespaces then it would be worthwhile brushing up on them but the examples below include all the code necessary.

When I served this from Django I had to ensure that the HTTP Response had the correct MIME type as follows:

return HttpResponse(t.render(c), mimetype="text/xml")

If you are not familiar with Django then don't worry about this line, it just returns the page but with the MIME type set to text/xml.

Within the HTML body I simply embedded the SVG picture directly. I did not include it in any explicit graphical or other object.


<svg:svg id="board" width="500" height="500">
<svg:rect x="10" y="10" width="485" height="485" fill="#FF9933">
</svg:svg>


Note that the elements all use the svg namespace with the shorthand reference defined at the head of the file. We need to give the svg element an id so that we can refer to it from Javascript.

I found that I had to set the width and height of the svg object. I had hoped to have it more dynamically sized but that did not work too well. Although I deliberately did not set the units of the svg element, I found that it ended up using pixels as the units. I was hoping to avoid setting the size in pixels in order to make the page work well with different sized screens and windows but I was unable to make it scale well. I could have investigated further but I chose to pick a plausible size instead. I may return to this at a later date.

In the example above, I included a rectangle. I could have just drawn it dynamically but it helped me during development to ensure that the SVG was drawing properly before I started creating more elements.

If you just put these elements in a HTML page then you should get a page (which you can put any normal text, images etc. as standard HTML) with a picture embedded in it. The next section covers adding more drawing elements.

Drawing SVG elements with Javascript

In this section, I assume that you are already familiar with using Javascript to generate HTML elements and dynamically add them to an existing page. This type of DHTML is covered by many articles online but the basic concept is to create a new element object of the required type, set any attributes and then to append it as a child element under a known location. The code below uses the same technique but with an extra wrinkle for SVG. If your Javascript DHTML knowledge is limited to copying inner HTML then this is the next level but it is not too difficult.

For my purposes, I knew that I was going to create a lot of SVG elements so I wrote a function to create an arbitrary SVG element.


function createSVGElement( eltType, properties) { // Create an arbitrary SVG element
var tgt = document.getElementById("board");
var elt = document.createElementNS('http://www.w3.org/2000/svg', eltType);
for ( prop in properties ) {
elt.setAttribute(prop, properties[prop]);
}
tgt.appendChild(elt);
}


This code has three parts:

1) The createElementNS() function is used to create a new element. If you have worked only in HTML then you may have used the CreateElement() function. This version adds a namespace argument which we need in order to specify an SVG element. Without this, the browser will not recognise the element, even if you create an element with a name that is not valid in HTML.

2) The attributes of the new element need to be set. These attributes will include geometric attributes for location and size as well as fill color etc. I used the array approach simply because of wanting a re-usable function. If I was using inline code to create the elements then I could use SetAttribute() as above or use the dot notation. If you use the dot notation to set attributes then be aware that some SVG attribute names are not legal Javascript names (they include a dash). For these attributes, the SetAttribute() method is necessary as it uses the property name as a string.

3) Finally, the new element is appended as a child to the top-level svg element. This is just the same technique as is used for dynamically adding HTML elements to an HTML page. In my example, I added every element directly under the root svg element but there is nothing to stop you from creating a less flat element structure. This might be particularly useful if you want to try changing properties of grouping elements for dynamic effects.

Given this function (and I can already see at least one way of optimizing it), here is a chunk of code that will draw a simple line.


var lineProperties = {'stroke': 'black'};
lineProperties.x1 = 10;
lineProperties.x2 = 10;
lineProperties.y1 = 10;
lineProperties.y2 = 490;
createSVGElement( 'line', lineProperties);


This chunk is slightly clumsy because it has been taken from a more dynamic function. The lineProperties object is initialized with the line colour and then properties are added for the x and y of each end.

Note that I have used both the string way of setting a property and the dot notation (which is fine for these properties). In this case, I could have just created the properties in one statement.

Given the set of properties, we simply call createSVGElement() with the name of the element and the set of properties.

Full details of the properties of SVG elements can be found in the SVG specification.

Associating events with SVG elements

The code above shows the techniques required to draw an SVG picture within an HTML page. In my case, I wanted to draw a Go board and I was simply able to draw the lines of the board with a for loop. However, in order to make this part of a game, I wanted a way to capture input events. SVG elements support an onclick() attribute which is a reference to a Javascript function. This is set in the same way as any other attribute.

While using onclick(), I used some other techniques. The first is to remember to set the pointer-events attribute (remember that I said that some SVG properties have a dash - this one cannot be set using the dot notation).

The second is that it is possible to have a hidden element that can still capture pointer events.

The third is that I set the id property for each pickable element and I built the id into the onclick() event as an argument. This means that each pickable element can identify itself when selected.


var circProperties = {'visibility': 'hidden', 'pointer-events': 'all', 'r': '10'};
circProperties.id = 'circ_10_10';
circProperties.cx = 10;
circProperties.cy = 10;
circProperties.onclick = 'playMove("'+circProperties.id+'");';
createSVGElement( 'circle', circProperties);


In my real use of this example, the cx and cy and id properties are set from loop variables.

Unfortunately, SVG elements do not have drag events. A simple click event is fine for Go but less than ideal for some other games. For example, in order to implement a game of Chess where the obvious user action would be to select a piece and drag it to its new position, I would need to select a piece and then select a new position. This requires some state in the Javascript and is less obvious for users. One thought is that, when a piece is selected, I could highlight the legal positions that it could move to. This requires the game logic to be embedded in the Javascript rather than at the server side but it would not be too hard.

Alternative Approaches

As I mentioned above, I did this work in Firefox. I like Firefox but I found that SVG support in IE and Konqueror is very limited (i.e. I couldn't make any of this work but I didn't try for more than a few minutes). However, A comment to my last post from Brad Neuberg pointed out that the SVGWeb project (http://code.google.com/p/svgweb) should add SVG support to IE and other browsers. I have not had time to try this but I will definitely give it a go.

However, I have also considered other approaches. One of these is to generate a picture file and lay it over an image map in straight HTML. This requires generating the complete picture at the server side and then sending the picture file to the browser. This picture file will definitely be larger than fragments of XML containing move information and the server will actually have to generate the picture. I have dabbled with generating a PNG file from Python and it can be optimized quite heavily but it is still less efficient than using SVG. However, for compatibility reasons and to see just how difficult it is, I will probably try it out at some time.

1 comment:

Ian McDowall said...

I just found a useful tutorial on SVG that includes sections on including SVG within a HTML document and also scripting it. This would have saved me some time if I had known about it:
http://www.w3.org/Graphics/SVG/IG/resources/svgprimer.html