Archive for the ‘Webpage’ tag
Interactive Diagrams Using Inline JS+SVG+XHTML
>>> Attached: ( inline_svg_demo.xhtml — demo using the below technique for a silly drawing ) <<<
Compatibility: The following method doesn’t work for Internet Explorer 8, so if interoperability is really important to you, skip this series! Don’t worry though — Firefox 4.0, Internet Explorer 9 and WebKit (Safari / Chrome) will all natively support HTML5. HTML5 includes inline SVG for HTML so this ad hoc version can be retired when these three products take hold. The below method is compatible with Firefox 3.x, Opera 9.x and the current version of WebKit (Safari 5.x, Chrome 7.x). For display reasons, I’ll show off bitmap files inline while displaying inline SVG in linked pages.
Part One of Two
Part one is for everyone! I describe how to display an SVG inline on a webpage (XHTML) along with some JavaScript driven behaviour (clicking changes a displayed message and CSS defined colours).
Part two is for the bioinformaticians and phylogenists that survive and stick around — I utilize a parsed Newick format traversal to build up the drawing of a tree recursively — then I finish off by reintroducing JavaScript driven interactivity — tree nodes are highlighted via CSS when clicked, and information is displayed for the selected node (perhaps a multiple sequence alignment, a global alignment score, branch lengths — etc.).
( See part two here: Simple Interactive Phylogenetic Tree Sketches JS+SVG+XHTML )
Wait a sec — what do you mean by inline SVG?
Scalable Vector Graphics (SVG) is an XML vector image format. All browsers allow you to specify SVG files to be included into a webpage as an image. What we’ll be discussing here is how to include an SVG image as an XML element directly in a XHTML file — that is, the single webpage file will contain all of the markup for itself and its included SVG images without linking to any external files.
I won’t go into detail about how to make SVG images. You may create them with software like InkScape or by manual manipulation of the raw XML using a text or code editor (which I’ve done in this post). If you do end up using drawing software, simply copy out all of the XML elements with a text editor afterward and inject them into the XHTML page you create.
XHTML File Nuances
Include the following as a attribute of your html tag — this ensures that the browser reading your page knows which document type definition (DTD) to use.
<html xmlns="http://www.w3.org/1999/xhtml"> ... </html>
You must include your SVG with the following tags.
<svg xmlns="http://www.w3.org/2000/svg" width="300" height="300" xmlns:xlink="http://www.w3.org/1999/xlink"> ... </svg>
In the above, we parameterize the “svg” tag with the correct xml namespace (xmlns) as an attribute. We specify the width and height of the image we want to inline and also specify that we want to be able to use hyperlinks within the SVG image with “xlink”. Notice that both namespace attributes are meant to enable a client browser to correctly render your page.
A Silly Drawing and Target Behaviour
In this post, we’ll be manipulating a picture of a silly face I’ve named Smile Again — if you can render the attached file, the face looks like the one to the right.
The eventual target behaviour we want is as follows. Clicking on different parts of Smile Again’s face causes (1) the displayed message to change and (2) the clicked part to change colours. Notice that I’ll be using JavaScript (JS) to perform both of these functions. We’ll get into the specifics soon — if you’re feeling up to it, you can always go to the attached demo xhtml file now and look at the source to reverse engineer the thing.
We can create this face manually with the following inline SVG code — we’ll start with pure SVG and worry about the JavaScript behaviour afterward.
<svg xmlns="http://www.w3.org/2000/svg" width="300" height="300" xmlns:xlink="http://www.w3.org/1999/xlink"> <circle cx="100" cy="100" r="40" style="fill:white;stroke:black;stroke-width:2" /> <circle cx="100" cy="100" r="20" style="fill:black;stroke:black;stroke-width:2" /> <circle cx="200" cy="100" r="60" style="fill:white;stroke:black;stroke-width:2" /> <circle cx="200" cy="100" r="10" style="fill:black;stroke:black;stroke-width:2" /> <path d="M60 200 C80 300 220 300 240 200 L60 200" style="fill:white;stroke:black;stroke-width:2" /> <path d="M100 235 L200 235" style="fill:white;stroke:black;stroke-width:2" /> <path d="M130 224 L130 246" style="fill:white;stroke:black;stroke-width:2" /> <path d="M150 220 L150 250" style="fill:white;stroke:black;stroke-width:2" /> <path d="M170 224 L170 246" style="fill:white;stroke:black;stroke-width:2" /> </svg>
Each of the XML elements above correspond to a different piece of the SVG image. The first four circles draw the eyes, next the complicated path draws the mouth and the four simpler paths draw the stitches of the teeth.
Changing the Displayed Message with JavaScript
Now that we’ve drawn Smile Again, we want to have it react to mouse clicks. I want to have both the clicked features highlight as well as the displayed message to change. This would be an important feature of actual practical diagrams. To make this task a bit easier to explain, I will break down the behaviour into smaller components and functions and build them back up as I go along. This will also make it a lot easier to read.
Let us focus on just the two circles that makes up Smile Again’s right eye (on the left side of the diagram).
We want our clicks to call a function — in order for that to happen, we need to use anchor tags (hyperlinks) within the SVG. This is made possible with our careful xmlns (namespace) attributes we declared earlier!
<svg xmlns="http://www.w3.org/2000/svg" width="300" height="300" xmlns:xlink="http://www.w3.org/1999/xlink"> <a xlink:href="#" onclick="javascript:change_thoughts('Oww! My right cornea!')"> <circle cx="100" cy="100" r="40" style="fill:white;stroke:black;stroke-width:2" /> </a> <a xlink:href="#" onclick="javascript:change_thoughts('Ouch! My right iris!')"> <circle cx="100" cy="100" r="20" style="fill:black;stroke:black;stroke-width:2" /> </a> ... </svg>
Notice that we’ve now enclosed entire clickable elements in anchor tags and have used the “xlink:href” hyperlink attribute. In reality, we can have these links point to actual other webpages. Instead, these point to the same page and call the JavaScript function to change the text in the description called “change_thoughts”. In this case, the output is an expression of pain after being poked in the eye (“Oww! My right cornea!”). We can now define the “change_thoughts” function and also the place where we want our messages to appear.
<head> ... function change_thoughts(say_this) { document.getElementById("thoughts").innerHTML = "\"" + say_this + "\"" } ... </head> <body> ... <h2 id = "thoughts" style="font-family:monospace;"> </h2> ... </body>
The “change_thoughts” function can go in the head or somewhere early in the body of the XHTML. It doesn’t really matter as long as it occurs before the SVG. This function gets an element with the id “thoughts” and changes the HTML inside of it. In the above, I add a pair of double quotes to the string to show. The element with the id “thoughts” is a h2 element — this really could been any valid HTML element. You do however have to place the “thoughts” element somewhere after you declare the “change_thoughts” function.
Changing the Highlighting with JavaScript and CSS
The last thing we want to do is to change the highlighting by making our JavaScript function touch the style (CSS) of the inline SVG element that was clicked. We could use a separate CSS file, but in the spirit of making everything one inlined file, the CSS is specified with the “style” html attribute.
Again, we’ll be looking only at the two circles corresponding to Smile Again’s right eye (I’ve changed the way the lines are broken in the code listing below so that it will actually fit on this page).
<a xlink:href="#" onclick= "javascript:change_thoughts('Oww! My right cornea!','right_cornea', '#F00')"> <circle cx="100" cy="100" r="40" style="fill:white;stroke:black;stroke-width:2" id="right_cornea" /> </a> <a xlink:href="#" onclick= "javascript:change_thoughts('Ouch! My right iris!', 'right_iris', '#00F')"> <circle cx="100" cy="100" r="20" style="fill:black;stroke:black;stroke-width:2" id="right_iris" /> </a>
Here are our two changes. First — each of the SVG elements now have a unique id (“right_cornea”, “right_iris”) so that we can use JS to manipulate each shape independently. Second — we’re now calling a new version of “change_thoughts” — this time, we’re giving the function the id of the shape to highlight and the colour (in hexadecimal) to highlight it with.
Finally, I can show you the new version of “change_thoughts” which will accept the new arguments and change the colours of the shapes.
function change_thoughts(say_this, highlight_this, colour) { // Say this ... document.getElementById("thoughts").innerHTML = "\"" + say_this + "\"" // Highlight this ... var change = document.getElementById(highlight_this) change.setAttribute("style", "fill:" + colour + ";stroke:black;stroke-width:5") }
In the above, we’ve added the code after “// Highlight this …”. We select the element “highlight_this” — remember, the id and colour are given by the function called with mouse clicks in the SVG. We then change the style by overwriting the inline CSS with the new specified fill colour (and a fat black stroke).
We now have a problem — each new mouseclick on a different shape will cause that new shape to highlight. The previous highlighted shape doesn’t automatically revert, and eventually all of the shapes are highlighted. What we need to do now is to improve the “change_thoughts” function one last time with some way to “undo” the previous change, so that new calls to this function will not only highlight the new shape, but will also remove the highlight from the previous shape.
Finishing with an Undo Object to remove previous changes
In the following version, we add an object to behave like a hash in the JavaScript. This hash will take html id attributes as keys, and the previous style as values. This behaves like an “undo” stack since we apply the styles to the elements with the corresponding id before we commit the changes to the new shape.
var _undo_render_hash = new Object function change_thoughts(say_this, highlight_this, colour) { // Say this ... document.getElementById("thoughts").innerHTML = "\"" + say_this + "\"" // Undo previous highlight ... for(var i in _undo_render_hash) { document.getElementById(i).setAttribute("style", _undo_render_hash[i]) } // Reset undo stack ... _undo_render_hash = new Object // Highlight this ... var change = document.getElementById(highlight_this) _undo_render_hash[highlight_this] = change.getAttribute("style") change.setAttribute("style", "fill:" + colour + ";stroke:black;stroke-width:5") }
Walking through the code, we start with a blank object as the undo stack — this is OK. On the first call of this function, since the undo object is empty, simply nothing is done before the new shape is highlighted. After the first call, all of the changes that were committed in the last call of the function are undone as their original styles are reapplied. We just add the next object to change to the undo object before applying changes to it.
The undo object is treated like a hash here, and in fact, we iterate all of its elements even though we only expect to have captured one id in the last call of the function. This means we can use this undo technique for functions that will modify many shapes with many changes since each of those modifications is iteratively undone in the next call. We must of course ensure that we save the original style of each of these shapes.
Next
At some point, I’ll want to revisit this and combine some of the other JavaScript stuff I’ve shown in this blog before. Displaying a rendered clickable phylogenetic tree is a task I figured out since I needed to quickly visualize some data for my team in my thesis work. Since I’ve already covered parsing a newick tree in this blog, it makes sense to complete the discussion with how I ended up with my phylogeny visualizer.