Wednesday, June 28, 2006

Help with dynamic inline SVG

Normally, I like to post solutions to problems on this blog, but today I'm hoping someone can help me.

In a previous post I presented some methods to embed SVG inline after generating it from XML data. However, there's still a problem, particularly in Internet Explorer, which I discovered when I tried to add some event handling to the SVG. Let me walk through the problem.

First, we start out with some sample XML data like:

<?xml version="1.0" ?>
<SrmData>
<data>
<display_name>srmdb2</display_name>
<date>2006-04-30</date>
<processor_busy>19.04</processor_busy>
</data>
<data>
<display_name>srmxml05</display_name>
<date>2006-04-30</date>
<processor_busy>37.68</processor_busy>
</data>
<data>
<display_name>srmdev1</display_name>
<date>2006-04-30</date>
<processor_busy>26.6</processor_busy>
</data>
<data>
<display_name>srmxml01</display_name>
<date>2006-04-30</date>
<processor_busy>42.74</processor_busy>
</data>
<data>
<display_name>srmweb2</display_name>
<date>2006-04-30</date>
<processor_busy>1.66</processor_busy>
</data>
<data>
<display_name>srmweb1</display_name>
<date>2006-04-30</date>
<processor_busy>5.61</processor_busy>
</data>
<data>
<display_name>srmxml02</display_name>
<date>2006-04-30</date>
<processor_busy>22.74</processor_busy>
</data>
</SrmData>
Which I run through my XSLT and get the following SVG (notice the mouseover events):
<svg:svg baseProfile="full" width="850" height="350" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<svg:g>
<!--Now Draw the main X and Y axis-->
<svg:g style="stroke-width:3; stroke:black">
<!--X Axis-->
<svg:path d="M 30 330 L 830 330 Z" />
<!--Y Axis-->
<svg:path d="M 30 30 L 30 330 Z" />
</svg:g>
<svg:g style="fill:none; stroke:#B0B0B0; stroke-width:2; stroke-dasharray:2 4;text-anchor:end; font-size:12px; stroke-width:5; stroke:black ">
<!--Add dotted line at 50-->
<svg:path d="M 30 30 L 830 30 Z" />
<svg:path d="M 30 165 L 830165 Z" />
<!--Add Y-axis labels-->
<svg:text style="fill:black; stroke:none" x="20" y="30">
100 </svg:text>
<svg:text style="fill:black; stroke:none" x="20" y="165">
50 </svg:text>
<svg:text style="fill:black; stroke:none" x="20" y="330">
0 </svg:text>
</svg:g>
<svg:rect x="35" y="272.88" height="57.12" width="109" style="fill:rgb(49,0,0)">
<set attributeName="fill" from="rgb(49,0,0)" to="red" begin="mouseover" end="mouseout" />
</svg:rect>
<svg:g>
<svg:text style="fill:black; stroke:none" x="35" y="347" rotate="45" font-size="8">
srmdb2</svg:text>
</svg:g>
<svg:rect x="149" y="216.96" height="113.03999999999999" width="109" style="fill:rgb(96,0,0)">
<set attributeName="fill" from="rgb(96,0,0)" to="red" begin="mouseover" end="mouseout" />
</svg:rect>
<svg:g>
<svg:text style="fill:black; stroke:none" x="149" y="347" rotate="45" font-size="8">
srmxml05</svg:text>
</svg:g>
<svg:rect x="263" y="250.2" height="79.80000000000001" width="109" style="fill:rgb(68,0,0)">
<set attributeName="fill" from="rgb(68,0,0)" to="red" begin="mouseover" end="mouseout" />
</svg:rect>
<svg:g>
<svg:text style="fill:black; stroke:none" x="263" y="347" rotate="45" font-size="8">
srmdev1</svg:text>
</svg:g>
<svg:rect x="377" y="201.78" height="128.22" width="109" style="fill:rgb(109,0,0)">
<set attributeName="fill" from="rgb(109,0,0)" to="red" begin="mouseover" end="mouseout" />
</svg:rect>
<svg:g>
<svg:text style="fill:black; stroke:none" x="377" y="347" rotate="45" font-size="8">
srmxml01</svg:text>
</svg:g>
<svg:rect x="491" y="325.02" height="4.9799999999999995" width="109" style="fill:rgb(4,0,0)">
<set attributeName="fill" from="rgb(4,0,0)" to="red" begin="mouseover" end="mouseout" />
</svg:rect>
<svg:g>
<svg:text style="fill:black; stroke:none" x="491" y="347" rotate="45" font-size="8">
srmweb2</svg:text>
</svg:g>
<svg:rect x="605" y="313.17" height="16.830000000000002" width="109" style="fill:rgb(14,0,0)">
<set attributeName="fill" from="rgb(14,0,0)" to="red" begin="mouseover" end="mouseout" />
</svg:rect>
<svg:g>
<svg:text style="fill:black; stroke:none" x="605" y="347" rotate="45" font-size="8">
srmweb1</svg:text>
</svg:g>
<svg:rect x="719" y="261.78" height="68.22" width="109" style="fill:rgb(58,0,0)">
<set attributeName="fill" from="rgb(58,0,0)" to="red" begin="mouseover" end="mouseout" />
</svg:rect>
<svg:g>
<svg:text style="fill:black; stroke:none" x="719" y="347" rotate="45" font-size="8">
srmxml02</svg:text>
</svg:g>
</svg:g>
</svg:svg>
If I embed the above SVG from a static file (via <embed src="myfile.svg">), it is rendered correctly in IE as:
where in the above snapshot, the mouse is over the first bar so the mouseover event has changed it to bright red to highlight it.

The problem is that when it is rendered dynmically inline in Internet Explorer, as described in my previous post, it looks like:

where there is no coloring and no mouseover events. Both cases require the Adobe SVG Viewer to be installed to render it. So why is it rendered differently?

There is one interesting difference in how IE handles this. When using the embed tag, if you right click on the image, the pop-up context menu will look like this:

where it is obviously using the Adobe SVG Viewer as evidenced by the "About Adobe SVG Viewer..." menu option and other options such as copying, viewing, and saving the SVG. On the other hand, when using the inlined SVG method, the context menu will look like:

as it would on any other part of the web page. So even though the Adobe plugin rendered the image, it is not apparent.

Meanwhile, on the Firefox side of things, regardless of whether you embed a static SVG file, put the SVG directly inline (if you're using XHTML), or put it in an object tag, it always looks the same:
which is good except for the fact that the Firefox (Gecko) SVG rendering engine doesn't seem to support the mouseover events either.

As an alternative method, I tried PlotKit which can generate dynamic SVG graphs and charts. So, I built an HTML table from my XML data and told PlotKit to use that and in both IE and Firefox it worked correctly to give me a graph such as:
The context menu of this graph is like my inline approach and doesn't acknowledge the Adobe SVG Viewer, however, PlotKit was able to get coloring to work. The current version of PlotKit does not support events which made it both not acceptable for my overall solution as well as provided me no way to test if the mouseover would have worked with the approach used by PlotKit. However, since at least colors worked, I started investigating how he (Alastair Tse) was able to render the dynamic inline SVG in IE.

I found this note in one of his samples:
Note: With SVG, the most compatible way is NOT to put in SVG tags, but create the SVG element with Javascript. Therefore, instead of adding <svg> tag, we just have a <div> container and create the SVG element using SVGRenderer.SVG().

And looking at the PlotKit code I can see that his library is dynamically building the SVG using JavaScript DOM APIs to add each element.

Well, since I want to use XSLT to generate my SVG from XML and not write my own version of the PlotKit library to build my SVGs, I tried a hybrid approach. I used the DOM APIs to create a temporary DOM element. I assigned my generated SVG to that element's innerHTML in order to have the browser build a DOM tree out of my SVG. I then created an svg DOM element, and appended the first child DOM element of my SVG DOM (the first g element) to the svg DOM element and appened the svg DOM element to the document. Which I would think would be theoretically the same thing, but to no avail.


Does anyone out there know how to fix this? How can I make Internet Explorer render my dynamic inline SVG correctly in order to get proper coloring and event handling?

Update: See follow-up

Monday, June 26, 2006

OpenAjax Alliance

I'm now a memeber of the OpenAjax Alliance (http://www.openajaxalliance.org/)

Saturday, June 03, 2006

Compressing HTML, CSS, and JavaScript files

Adding gzip compression to files received from your web server can enhance the network latency of your web application. As simple way to do this is to define a handler in your .htaccess file to compress your HTML, CSS, and JavaScript files. To do this, add these lines to your .htaccess file:


AddHandler application/x-httpd-php .css .html .js
php_value auto_prepend_file /path/to/gzip-page.php
php_flag zlib.output_compression On

where the path is specific to your installation.

The gzip-page.php is needed to define the content-type for the compressed data so that they are not assumed to be PHP files. The code should look something like:


<?php
$pathinfo = pathinfo($PHP_SELF);
$extension = $pathinfo['extension'];
switch ($extension) {
case "css" :
header("Content-type: text/css");
break;
case "html" :
header("Content-type: text/html");
break;
case "js" :
header("Content-type: text/javascript");
break;

default :
break;
}
?>


Now all your HTML, CSS, and JavaScript content will be compressed if the browser supports compressed data (which most do). For Ajax applications this is most useful since you'll probably have a decent amount of JavaScript code to transmit and potentially a fair amount of CSS as well.

Thursday, June 01, 2006

SVG in IE and Firefox

As follow-on work to my previous post Inline dynamic SVG from XML with Ajax where I discussed how to incorporate dynamically generated SVG in your web page, I've been trying to figure out how to make IE correctly prompt you to install the SVG plugin. That is usually accomplished with the PLUGINSPAGE attribute of the embed tag (see http://developer.apple.com/quicktime/compatibility.html for a nice explanation of the technique), but I'm using the object tag (which is recommended since it complies with the W3C HTML specifications while embed does not and it allows the data to be added inline). Unfortunately, adding the codebase attribute of http://www.adobe.com/svg/viewer/install/ (the Adobe SVG Viewer install site) and the classid attribute of clsid:78156a80-c6a1-4bbf-8e6a-3cd390eeb4e2 (the Adobe SVG Viewer ActiveX control id) didn't do what I had hoped for.

I haven't figured out how to make IE prompt for the plugin yet, but I did find this page which provides an alternative method to check for IE and alternate using the object or the embed (in his case or inline SVG in my example) rather than using the isASVInstalled() method I used.

In his example, Spartanicus uses the MS "uplevel revealed" conditional comment to define a CSS type as:


.svg{display:none}
*>.svg{display:inline}


Then his HTML looks like:


<!--[if IE]><embed src="img/butterfly_vector.svg" height="120" width="170"><![endif]-->
<object data="img/butterfly_vector.svg" type="image/svg+xml" class="svg" height="120" width="170"/>


Where the class of svg in the object version will causes it not to display if not in Internet Explorer.

It's an interesting technique, but in my case of inline generated SVG where I can't use the embed tag since I have no src so I'd have to insert the SVG inline for IE and then also have the wrapped object version. Unfortunately, the non-object version would still display the data in the SVG XML even though it would not be an SVG graphic which would look terrible. So, it doesn't really help me in this case, but I thought it was interesting.