Massive leap for SVG support in IE9 Platform Preview 3

Had I known that Microsoft was planning on releasing another platform preview of IE9, I'd have waited before publishing yesterday's post on the performance of filters in the major browsers. There doesn't appear to be any support for the filters, but there is a huge jump in the level of support for SVG.

Consider the results seen in IE9 Platform Preview 2:

Svg_test_-_ie9_pp2

Now look at the same page, rendered in IE9 Platform Preview 3:

Svg_test_-_ie9_pp3

What a difference! In this particular example, I'd say this compares almost identically to Safari's support - I've used the Windows version to take this screenshot so that you don't see the difference between Helvetica and Arial on the OS X version:

Svg_test_-_safari-win

Forgiving a couple of artefacts on the pie vectors, this is stellar stuff from Microsoft - and about bloody time too!

Support for SVG Filters in the Major Browsers

Filters are a really powerful addition to SVG's toolkit, allowing you to apply effects similar to those found in Adobe Photoshop, using techniques including displacement maps, colour matrices, simple image merging, transforms and gaussian blurs.

You'll remember that we're creating a new dashboard - and various associated reports along with it - for our intranet (and if you don't, you can take a look at all the SVG-related posts on this blog to refresh your memory). This means, for some people, replicating as much as possible from existing Excel spreadsheets that currently do the job.

One particular report contains a bunch of single slice pie-charts, which tells people how they're performing in a particular area of the business:

Original_report

Given our recent successes with SVG, when I saw this report, I knew it'd be dead easy to replicate in SVG so, once again, I fired up TextMate and threw some code together. The original report has a drop shadow underneath the pies themselves, which is fairly easy to replicate using SVG filters:

<filter id="dropShadow">
    <feOffset in="SourceAlpha" result="Shadow" dx="0" dy="1" />
    <feColorMatrix in="Shadow" result="FadeShadow" type="matrix" values="0.5 0 0 0 0 0 0.5 0 0 0 0 0 0.5 0 0 0 0 0 0.5 0" />
    <feGaussianBlur in="FadeShadow" result="BlurShadow" stdDeviation="3" />
    <feBlend in="SourceGraphic" in2="BlurShadow" mode="normal" />
</filter>

All of the other drop shadows on the intranet are 25% black (though for demonstration purposes, I've used 50% in this example), use a 1px vertical offset and 3px blur. Using SVG filters, this means we need to apply four steps:

  • Use the offset filter - feOffset - to take the source alpha of our element, and move it 1 pixel down
  • Use the colour matrix filter - feColorMatrix - to reduce the shadow to 50% of it's intensity
  • Use the blur filter - feGaussianBlur - to blur the shadow
  • Use the blend filter - feBlend - to add both graphics together to achieve the drop shadow effect

The feBlend filter can use different modes, much in the same way as Photoshop, to achieve different effects - in this particular case, I've just used a normal mode. As you can see from the images below, the results look pretty good in Chrome, Opera and Firefox, but the effect fails to render in Safari, SVGWeb or IE9 Platform Preview 2. Support still feels a very long way off in IE9 (update: see Massive leap for SVG support in IE9 Platform Preview 3):

(download)

It's a real shame that, whilst filters which are available in WebKit, they don't appear to be supported in the current releases of Safari for both Windows and OS X. A lot of focus appears to be put on the canvas implementation, but little things like this, which could easily be switched on, don't appear to get any love.

So whilst SVG filters are really very powerful, for the time being they're still not supported fully across all browsers - but just like HTML and CSS, that doesn't stop us from using the features for those browsers that do support it.

SVGWeb continues to impress

I'm still very much impressed with SVGWeb, in that it can take the SVG and translate it into something that most Internet Explorer browsers can stomach - an Adobe Flash movie which will display it relatively well. But it's still Flash, and in some cases where the SVG markup is particularly heavy, it can take a second or two for the image to display.

There are other ways, I know. Libraries such as Raphaël are available that will create SVG and VML for IE, but this requires us to switch away from using actual SVG markup and move to a more API-like way of creating graphics - I don't really want to go down that route - not just yet anyway.

So I just can't help but wonder if there's something which would perform the same process as SVGWeb, but output VML instead. If you know of any projects, then I'll be happy to be a willing beta tester, evaluate it and report back.

In the meantime, we'll be sticking to SVGWeb.

Further adventures in SVG, Gauges and Trigonometry

Gauges, the types of which I discussed in my last post, are pretty useless without target zones showing people where the needle should be pointing. With that in mind, it's probably a good idea that we add them to our gauges before they become part of the dashboard. This, of course, means drawing SVG arcs. Which means I need to know where a particular point is on the circumference of a circle.

Which means trigonometry.

I really enjoyed maths at school - two really great teachers helped - even going so far as to enjoy trigonometry. Sadly, as I only use a small portion of the maths I used to be quite fluent in at school on a day-to-day basis, most of the things I used to know now escape me, without a quick trip down Google's memory lane.

Even a fairly basic task - finding the x and y coordinates of a point at a given angle, on the circumference of a circle of radius r, centred at the coordinates cx,cy - saw me grind to a halt for a minute or two this morning. Then you find what you were looking for, and you remember it was dead easy all along:

Trigdiagram

Adding the zones to a circular gauge means I need to know where the arc starts, and where it finishes. Fortunately, SVG will worry about the rest for me, but I need the x,y co-ordinate pairs for four points on the zone.

Gauge-zone

Sure, we could calculate these by hand and hard-code them in, but given that the gauge is going to change and be re-used, it's better - and easier - to get some code to do the calculations for us. Good job that SVG supports JavaScript, then:

    function c_xy(r, a, cx, cy) {
        var cx = 110 + (r * Math.cos(a * (Math.PI/180)));
        var cy = 110 - (r * Math.sin(a * (Math.PI/180)));
        return { 
            x: cx,
            y: cy
        };
    }

This function will return an object with the x and y coordinate of a point at angle a on the circumference of a circle of radius r. All it's doing is exactly what the equations on the chart above show.

So now we have a way of finding the co-ordinates of the points on the zone, let's create a function that will add a zone to the gauge:

    function gauge_addZone(pct1, pct2, fill, className, r1, r2) {
        var a1 = (-270*(pct1/100))+225;
        var a2 = (-270*(pct2/100))+225;
        var c1s = c_xy(r1, a1 % 360, 110, 110);
        var c1f = c_xy(r1, a2 % 360, 110, 110);
        var c2s = c_xy(r2, a2 % 360, 110, 110);
        var c2f = c_xy(r2, a1 % 360, 110, 110);
        
        var zone = document.createElementNS("http://www.w3.org/2000/svg", "path");
        var d = " M " + c1s.x + "," + c1s.y +
                " A " + r1 + "," + r1 + " " + (pct2-pct1 > (200/3) ? "0 1" : "1 0") + " 1 " + c1f.x + "," + c1f.y + 
                " L " + c2s.x + "," + c2s.y + 
                " A " + r2 + "," + r2 + " " + (pct2-pct1 > (200/3) ? "0 1" : "1 0") + " 0 " + c2f.x + "," + c2f.y + 
                " Z";
        zone.setAttribute("d", d);
        zone.setAttribute("fill", fill);
        if (className != null) {
            zone.setAttribute("class", className + " gaugeZone");
        } else {
            zone.setAttribute("class", "gaugeZone")
        }
    
        document.getElementById("gaugeBack").appendChild(zone);
    }

This function does all the heavy lifting. The gauges I'm creating all work on the theory of ranges from 0% to 100%, so by passing in the starting and finishing percentages - pct1 and pct2 - of the target zone, I've a good idea where I'm expecting the zone to be drawn.

The fill value allows me to pass in a colour, if I so desire, and I'm hoping className should be fairly self-explanatory too. The r1 and r2 values allow me to set the thickness of the zones. For the zone in the example above, I've used 80 and 55, but if I want a thin zone to be drawn, I could specify radii of 80 and 75, resulting in something like this:

Screen_shot_2010-06-15_at_12

Now it's time to use our c_xy function to get our co-ordinate objects for the four points. The zone is created as a path element, and we draw using the d attribute. I won't go into the actual syntax, but it's safe to say that it's fairly simple to pick up. SVG Basics has some good tutorials on paths and arcs that you can read if you're so inclined.

Finally, because we don't really want to hard-code the position of the pointer, let's create a function that will move the pointer for us. This one's really basic, but you could extend it to make a smoothly animated pointer with minimal fuss:

    function gauge_setPointer(pct) {
        var a = (270 * (pct/100));
        document.getElementById("gaugeNeedle").setAttribute("transform", "rotate(" + a + ",110,110)");
    }

All we're doing, because we know that the angular range from 0% to 100% spans 270°, is multiplying that angle by the percentage, and then applying a transformation to the gauge needle. Very simple.

Finally, we actually call the functions:

    gauge_addZone(80, 100, "#0f0", "zoneGood", 80, 55);
    gauge_setPointer(83.5);

Which gives us exactly what you've seen above:

Gauge-zone

The code I've used above means that we can also add multiple zones, should we wish. If, as well as showing a safe zone, you want to also add a zone which should be avoided, you can do that. All we'd need to do would be to add another gauge_addZone line, and we're done:

    gauge_addZone(80, 100, "#0f0", "zoneGood", 80, 55);
    gauge_addZone(0, 40, "#f00", "zoneBad", 80, 55);
    gauge_setPointer(36.8);

This would result in the following example:

Screen_shot_2010-06-14_at_18

I'm really enjoying using SVG, and I'll probably post more on it as time goes by.

SVG and today's web browsers

Our intranet's homepage, like many others, features a dashboard - a bunch of key metrics designed to give users an idea of how they're doing, at a glance. In it's current guise, it's fairly limited, featuring the three latest news items, and three metrics relating to stock listings, photography and pricing.

Screen_shot_2010-06-11_at_12

In the 18 months since we re-launched our intranet, we've created loads of new applications, added tons of new features, and hooked into many new parts of the business that were previously unavailable to us. Many different brands use different systems, so we're keen to make sure that anyone, from any part of the business, is able to understand what's going on.

We're redesigning our dashboard, to feature a lot more instant at-a-glance insights into what's going on, and to show our staff, managers and directors what needs their focus. We've had lots of feedback on the gauges, with the majority liking the instant colours but finding the graphs themselves lacking something.

So given that SVG seems like the best candidate for the job, I set about creating a rough idea of what my gauge would look like using very basic shapes, colours and gradients in Adobe Illustrator:

Screen_shot_2010-06-11_at_13

Illustrator's SVG export was a little mark-up heavy, so I scrapped it, fired up TextMate and put this SVG together:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve">
    <defs>
        <!-- Gradients for the gauge bevel -->
        <linearGradient id="grShOuter" x1="0%" x2="100%" y1="100%" y2="0%">
            <stop offset="0" style="stop-color: #e9e9e9" />
            <stop offset="1" style="stop-color: #898989" />
        </linearGradient>
        <linearGradient id="grShInner" x1="0%" x2="100%" y1="100%" y2="0%">
            <stop offset="0"   style="stop-color: #f9f9f9" />
            <stop offset="0.4" style="stop-color: #d9d9d9" />
            <stop offset="0.5" style="stop-color: #898989" />
            <stop offset="0.6" style="stop-color: #d9d9d9" />
            <stop offset="1"   style="stop-color: #f9f9f9" />    
        </linearGradient>
    
        <!-- Gradient for the gauge background -->
        <radialGradient id="grGaugeBack" cx="50%" cy="100%" r="85%">
            <stop offset="0" style="stop-color: #666" />
            <stop offset="1" style="stop-color: #000" />
        </radialGradient>

        <!-- Gradient for the needle itself -->
        <linearGradient id="grNeedle" x1="0%" x2="100%" y1="0%" y2="0%">
            <stop offset="0"       style="stop-color: #f9f9f9" />
            <stop offset="0.4995" style="stop-color: #efefef" />
            <stop offset="0.5005" style="stop-color: #d9d9d9" />
            <stop offset="1"       style="stop-color: #cfcfcf" />
        </linearGradient>
    
        <!-- Gradients for the needle cap -->
        <linearGradient id="grCapOuter" x1="0%" x2="0%" y1="0%" y2="100%">
            <stop offset="0" style="stop-color: #292929" />
            <stop offset="1" style="stop-color: #898989" />
        </linearGradient>
        <linearGradient id="grCapInner" x1="0%" x2="0%" y1="100%" y2="0%">
            <stop offset="0" style="stop-color: #393939" />
            <stop offset="1" style="stop-color: #797979" />
        </linearGradient>
    
        <!-- Tick marks -->
        <rect id="tickMajor" x="30" y="108"  width="25"   height="4"   transform="rotate(-45,110,110)" />
        <rect id="tickMinor" x="30" y="109.25" width="12.5" height="1.5" transform="rotate(-45,110,110)" />
        <g id="tickMinorGroup">
            <use xlink:href="#tickMinor" transform="rotate( 13.5,110,110)" />
            <use xlink:href="#tickMinor" transform="rotate( 27  ,110,110)" />
            <use xlink:href="#tickMinor" transform="rotate( 40.5,110,110)" />
        </g>
        <g id="gaugeTicks">
            <use xlink:href="#tickMajor" />
            <use xlink:href="#tickMinorGroup" />
            <use xlink:href="#tickMajor"        transform="rotate( 54,110,110)" />
            <use xlink:href="#tickMinorGroup"    transform="rotate( 54,110,110)" />
            <use xlink:href="#tickMajor"         transform="rotate(108,110,110)" />
            <use xlink:href="#tickMinorGroup"    transform="rotate(108,110,110)" />
            <use xlink:href="#tickMajor"         transform="rotate(162,110,110)" />
            <use xlink:href="#tickMinorGroup"    transform="rotate(162,110,110)" />
            <use xlink:href="#tickMajor"         transform="rotate(216,110,110)" />
            <use xlink:href="#tickMinorGroup"    transform="rotate(216,110,110)" />
            <use xlink:href="#tickMajor"         transform="rotate(270,110,110)" />
        </g>
    </defs>

    <!-- The Gauge -->
    <g id="gaugeBack">
        <circle fill="url(#grShOuter)" cx="110" cy="110" r="100" />
        <circle fill="url(#grShInner)" cx="110" cy="110" r="95" />
        <circle cx="110" cy="110" r="88" />
        <circle fill="url(#grGaugeBack)" cx="110" cy="110" r="85" />
    </g>

    <!-- Tick marks -->
    <use xlink:href="#gaugeTicks" fill="#fff" opacity="0.35" />

    <g id="gaugePointer">
        <polygon id="gaugeNeedle" fill="url(#grNeedle)" points="107.5,42.5 110,40 112.5,42.5 117.5,135 110,137.5, 102.5,135" transform="rotate(-135,110,110)" />
        <circle fill="url(#grCapOuter)" cx="110" cy="110" r="15" />
        <circle fill="url(#grCapInner)" cx="110" cy="110" r="12" />
    </g>
</svg>

Those of you in the know won't be surprised by IE6, IE7 and IE8's efforts, but I was a little underwhelmed by the IE9 platform preview. There doesn't appear to be any gradient support, and when you do use them use the opacity attribute, you get very odd artefacts on the resulting image:

Svg-comparison

I was, however, very pleased with the other browser's results - there's no need to show you the individual images generated by Firefox, Safari/Chrome and Opera, as they really did look practically identical - and given that we're going to be settling on SVG for the gauges, I needed to find a solution to the IE problem.

Enter SVGWeb

SVGWeb is something I first heard about last September, but wasn't quite useful enough for us to go ahead with. Now, although it's by no means even a beta release, I've found it to be stable enough in testing to do what we need it to do - simply, render the gauge, in Internet Explorer as close as possible to the output we get on Mozilla and WebKit browsers.

All it requires is the addition of a script to your <head>...

<!--[if lte IE 8]><script src="svgweb/svg.js" data-path="svgweb"></script><![endif]-->

...and a slight change to the way in which you add your SVG markup or files to your page - instead of simply adding an <svg> tag, you add your SVG using an <object> tag - not ideal, but certainly not a show-stopper:

Et voila:

Ie6-ie8

There are some very slight differences between the native output from the higher quality browsers, mostly with regards to the opacity of the tick marks, but it's close enough that we can commit to using SVG now and, thanks to the Internet Explorer team's commitment to providing SVG compatibility in IE9, in the future.

Why not Flash or Silverlight?

This article wasn't intended to be an attack on Flash, and I don't want to start any religious wars, but I do really believe that Flash has had it's time. More and more things that we used to require things like Flash for are now easier to create and more accessible when made with open web standards such as SVG, HTML and CSS. For what we require in this particular example, using Flash or Silverlight would be overkill - in fact, I'd rather resort to using server-generated images than using plugins to create the gauges.

The obvious and highly publicised benefit of not using a plugin, of course, is that the content works on mobile devices without the Flash plugin, and given that our company is pretty much standardising on HTC Android devices, with the occasional iPhone and iPad, it means that our lovely new graphics will, to borrow a phrase, 'just work'.

More and more companies are turning away from Flash for infographics and charts - Campaign Monitor have just announced that they've switched from Flash over to the excellent HighCharts JavaScript graphing library. Given that there are excellent graphing libraries out there, I can't imagine that major applications like Google Analytics will continue using Flash for much longer either.