Creating an SVG Wiki
Some time ago, as I was getting hooked on SVG, I saw a reference to the SVG Wiki. This wasn't long after I'd seen Andrew Watt's first all-SVG web site (now defunct) and I assumed that the SVG Wiki would be SVG-based. This seemed a wonderful notion, as SVG is a very rich, extensible medium and I was imagining some wondrous kind of hyperdiagram. The Wiki turned out to be a great source of SVG information but to my disappointment was just a regular text wiki.
Recently the opportunity presented itself to try out the idea of a true SVG wiki in practice. The following is a description of the minimal code needed to get drawing on a wiki, henceforth known as WikiWhiteboard. It allows anyone to scribble on a drawing area on a web page, and clicking a button preserves the drawing for the next viewer (or artist) that comes along.
The design works as an add-on to an existing wiki, with the SVG functionality very loosely coupled to the rest of the system. The code was initially put together to work with a custom Java wiki, but it only took a couple of hours to port the code to JSPWiki as a plugin. Eugene Erik Kim has also created a live WikiWhiteboard using PurpleWiki (based on UseModWiki, Perl). In fact the same technique can be used anywhere that a whiteboard may be needed, as none of the code is wiki-specific.
What follows is a description of the core SVG part of the code, the rest obviously depends on what happens to be your server-side setup.
WikiWhiteboard Overview
The key to the system is a single SVG file. Virtually all of this is Javascript, operating on the SVG DOM. The rest of the WikiWhiteboard system is pretty standard server functionality. In the JSPWiki version a servlet displays the SVG file as an embedded object, which appears in the browser as a rectangular drawing area. The user can scribble with their mouse on this area, and have two buttons they can click -- one to save their drawing, another to clear it.
The cunning part of the system is that the SVG data sent back to the
server contains not only the drawing, but also the drawing functionality
and the means to serialize itself. All the server has to do is persist
(save to file in the JSWiki version) the SVG data, and serve up the
appropriate link in a HTML <embed> element.
Capturing SVG Paths
A (slightly shaky) handwritten slash:
can be represented by the following SVG code :
<?xml version="1.0" ?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg>
<style type="text/css"><![CDATA[
path {stroke: black; stroke-width:2; fill:white;} ]]>
</style>
<path d="M 25 5 L 24 10 22 14 21 17 20 21 19 24 18 27 17 30 16 32 15 33 14 36" />
</svg>
Most of this should be familiar from other XML languages. The CSS
styling is applied in exactly the same way as in XHTML. The
<path> element itself has an attribute
class to apply the styling, but the important part here is in
the d (data) attribute. This contains a series of
instructions on how a virtual pen should be moved. SVG paths support
several instructions, here moveto (M) and
lineto (L) are used. The numbers that follow
are x and y coordinates, and the use of upper-case letters for
M and L indicates that these are absolute
coordinates. The names are almost self-explanatory:
moveto picks the pen up and changes its position, and
lineto draws a straight line from the current position to a
given point. So the above path is drawn by moving the pen to point (25,
5), drawing a line to (24, 10), then another line from there to (22, 14),
and so on for all ten segments. Note that (0,0) corresponds to the
top-left hand corner of the drawing area, and the y-coordinate
increases going down the screen.
We can easily add another path to form a cross:
<svg>
...
<path d="M 25 5 L 24 10 22 14 21 17 20 21 19 24 18 27 17 30 16 32 15 33 14 36" />
<path d="M 11 5 L 12 6 18 15 25 27 27 31 28 33 29 35" />
</svg>
The second line is a little less shaky, hence fewer line segments (6)
are needed. As raw data this isn't very interesting, but through XML (DOM)
spectacles, this looks rather sweet. To draw the second line we've simply
appended a child element to the <svg> element,
something that is very easy to do programmatically. SVG supports script in
very much the same way as HTML, and it's fairly straightforward to create
a simple drawing tool by creating paths based on mouse movements. There's
a small snag: when we tap a pen on paper we see a dot, but when a mouse
clicks on the screen there isn't a path until it has moved. It's not
difficult to append a circle element, but a simpler workaround seems to
work well enough. A path drawn left-a-bit then right-a-bit of the clicked
point is near enough to a dot:
<path d="M 100 100 L 99.5 100 100.5 100" />
If this pseudo-dot does become a true path, all we have to do is append the appropriate coordinates to the data attribute.
At a later date we might want to do more with this document, so it
makes sense to wrap the part of the SVG that will contain the drawing in a
<g> (group) element. This also gives us a nice point to
capture mouse events. There needs to be something inside the
grouping to define the drawing area and here we have a simple
<rect> element.
The SVG shell needed for the scribbler is pretty minimal:
<?xml version="1.0" ?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg width="100%" height="100%" onload="main(evt);">
<script>...see below...</script>
<style type="text/css"><![CDATA[
.sketch { stroke: black; stroke-width:2; fill: none; }
]]></style>
<g id="sketch" class="sketch"
onmousemove="mouseMove(evt);"
onmousedown="mouseDown(evt);"
onmouseup="mouseUp(evt);">
<rect x="0"
y="0" width="100%" height="100%" fill="white" />
</g>
</svg>
The script to handle the mouse events has a few global variables to track the mouse and drawing states:
<script
type="text/ecmascript"><![CDATA[
var svgDocument;
var sketch;
var pathElement;
var mouseIsDown;
var pathData;
When this SVG document is loaded into a browser, the following method
will be called to initialize a couple of those globals -- the SVG DOM
document and the <g> element (which
hasid="sketch"):
function main(evt) {
svgDocument = evt.getTarget().getOwnerDocument();
sketch = svgDocument.getElementById("sketch");
}
The mouse handling methods are very straightforward. When the drawing
area is clicked, mouseDown gets called, which records the
fact that the mouse button is currently pressed. The coordinates of this
mouse click are recorded, and a method called which will initialize a path
element.
function mouseDown(evt) {
mouseIsDown = true;
initPath(evt.getClientX(), evt.getClientY());
}
When the mouse is moved while the mouse button is pressed, the following method will be called to note the mouse's new position:
function mouseMove(evt) {
if (mouseIsDown) {
extendPath(evt.getClientX(), evt.getClientY());
}
}
When the mouse button is released, the following method records the new state:
function mouseUp(evt) {
mouseIsDown = false;
}
The methods that actually do the drawing work do so by making simple
adjustments to the SVG document's DOM tree. The element that will describe
this individual path is created and given an attribute that will represent
the pseudo-dot. This <path> element is then added to
the sketch <g> element:
function initPath(x,y){
pathElement =
svgDocument.createElement("path");
pathData = "M " + x + " " + y + " L ";
extendPath(x-0.5, y);
extendPath(x+0.5, y);
sketch.appendChild(pathElement);
}
When the path needs extending, the extra coordinates are simply
appended to the pathData string:
function extendPath(x,y){
pathData += " " + x + " " + y;
pathElement.setAttribute("d", pathData);
}
Embedding the Scribble Screen
The scribble code above can be used directly, i.e. serve it up from
file or the Web and you can scribble in your browser. In the wiki
application, and in many other environments, it's a lot more useful to
integrate it as a plugin in a regular (X)HTML page. SVG is a relatively
new technology, thus browser support lags behind the specs somewhat; and,
especially in browser support, implementations may deviate from the
specs. Right now the best way of getting an SVG diagram embedded in HTML
to work in a browser is to use the <embed> element,
rather than the more specification-friendly
<object>. In practice it will look something like
this:
<embed src="sketch.svg" type="image/svg+xml" width="500" height="300"
pluginspage="http://www.adobe.com/svg/viewer/install/" />
When used with the code above this will display in a 500x300 box on the
web page, the outline of the box being provided by the
<rect> element.
Pull Out the SVG
To be able to save the changes made to the SVG DOM, it's necessary to serialize it. The code below is a generic method for serializing a DOM to text. There may be alternatives such as using ActiveX to pass the object as a whole, but this approach is based squarely on DOM2 and so should work anywhere. What we have is a recursive treewalker that steps through the elements of the DOM tree, serializing them in turn. The children of a particular element are tested for their node type and an appropriate text representation added to the accumulator. If a nested element is encountered, this method itself is called to serialize it. A helper method uses regular expressions to swap reserved characters (&, <, >) for their escaped counterparts.
var ELEMENT_NODE = 1;
var TEXT_NODE = 3;
var CDATA_SECTION_NODE = 4;
var accumulator; // holds the serialized XML
function elementToString(element) {
if (element){
var attribute;
var i;
accumulator += "<" + element.nodeName;
// Add the attributes
for (i = element.attributes.length-1; i>=0; i--){
attribute = element.attributes.item(i);
accumulator += " " + attribute.nodeName + '="' + attribute.nodeValue+ '"';
}
// Run through any children
if (element.hasChildNodes()){
var children = element.childNodes;
accumulator += ">";
for (i=0; i<children.length; i++){
switch(children.item(i).nodeType){
case ELEMENT_NODE :
elementToString(children.item(i)); // RECURSE!!
break;
case TEXT_NODE :
accumulator += escape(children.item(i).nodeValue);
break;
case CDATA_SECTION_NODE :
accumulator += "\x3c![CDATA["; // unescaped <
accumulator += children.item(i).nodeValue;
accumulator += "]]\x3e"; // unescaped >
}
}
accumulator += "</" + element.nodeName + ">";
} else {
accumulator += " />";
}
}
return accumulator;
}
function escape(markup){
markup = markup.replace(/&/g, "&");
markup = markup.replace(/</g, "<");
markup = markup.replace(/>/g, ">");
return markup;
}
While this code can be used anywhere a DOM serialization is needed, the following is SVG-specific. It uses the method above to serialize the document element, then adds appropriate XML header material.
function getSVG(){
var svgDocElement = svgDocument.getDocumentElement();
var content = elementToString(svgDocElement);
accumulator = "";
return '<?xml version="1.0" ?>'
+ '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">'
+ content;
}
A little extra initialization is needed than that shown earlier. As
well as getting a pointer to the document, and specific element of
interest from the event as before, this also makes the getSVG
method above available to the HTML document in which the SVG is
embedded:
function main(evt) {
svgDocument = evt.getTarget().getOwnerDocument();
sketch = svgDocument.getElementById("sketch");
parent.getSVG = getSVG;
}
To recap on what we've got so far, the XML part of the document is a very simple structure which will display a rectangle in the HTML page in which it is embedded. The first part of the script in the SVG looks after the scribbling itself by adding path elements to the DOM tree of the SVG document. We now also have a way of getting a text serialization of the complete SVG document.
Back to the HTML
Once the user has scribbled on the drawing area, the new data just sits
there in the client DOM. To be able to save the changes we need to pass
the serialization to the server. The easiest way of doing this is with a
HTML form. In the current JSPWiki plugin the form has two buttons:
Save and Clear, which correspond to the
<input> elements below. The server state will be
modified, so the HTTP POST method is used to pass the data. The
implementation has a servlet to receive the data at the relative URI
svgupload, which is given in the action
attribute. In JSPWiki this page will be generated dynamically, and the
name of the page will be passed into the first of the hidden inputs. The
second of the hidden inputs is used to pass the SVG serialization back to
the server. The onsubmit method first gets a pointer to this
form, then calling the getSVG method loads the hidden value
with the XML string.
<form name="SvgForm"
action="svgupload"
method="post"
onsubmit= "svgForm=document.forms['SvgForm'];
svgForm.svg.value= window.getSVG();
return true;" >
<input type="submit" value="Save"/>
<input type="submit" name="submit" value="Clear"/>
<input type="hidden" name="pageName" value="SomePage" />
<input type="hidden" name="svg" value="" />
</form>
Future Directions
Further improvements that could be made to the SVG wiki include making parts of the diagram clickable through hyperlinks; adding extra drawing code to make it easy to create certain kinds of diagrams, such as organization charts or to add background images; adding RDF to markup the diagrams with metadata.
ResourcesFor those who wish to read further, or attempt this themselves, the Protocol7 wiki has good information on various techniques: see Embedding SVG in HTML, Inter-Document Communication and Cross-browser Scripting. Adobe's SVG viewer can be downloaded from http://www.adobe.com/svg/viewer/install/main.html (released version for Mac or Windows), and the latest beta release for Windows can be downloaded from http://www.adobe.com/svg/viewer/install/beta.html. |
- New sketch.svg
2004-06-08 05:03:30 Danny Ayers - New sketch.svg
2010-08-29 23:17:54 lonpair789 - SVG -basedTactile Graphics
2003-11-21 19:45:52 Susan Jolly - PS.
2003-11-21 02:39:53 Danny Ayers
2003-11-20 05:26:28 Henning Follmann