More SVG

In this blog post I’m going to talk about SVG again. One of the great advantages that SVG has over Silverlight is that it is built into the browser. That means that you can use it anywhere in the browser window. Plug-ins are restricted to the box where they are plugged in. Click on the following link for a sample of what I did off the top of my head in a couple of hours:

SVG Demo

First, you’ll notice that I painted some yellow circles behind the text. The text is not part of SVG, but part of the web page HTML itself. I just set the z-index to a negative number to force it behind the text. Second, you can see some annotation text and red text that points to “stuff” just to show that images can be created and plotted anywhere. Unfortunately, there is a pixel difference between different browsers (probably in the font style), so your images might not line up with text in all browsers.

The last demonstration is a bar chart that I threw together just to demonstrate something useful that can be plotted on the screen. The gray grid is nothing more than horizontal and vertical lines painted first, then the 3d looking boxes are painted over using rectangles and polygons (see picture below):

As indicated in the text above this chart, I designed one bar and put the code in a method call that I call repeatedly with different parameters. I just feed the method with the height of the red, yellow then green boxes. In addition, I fed the horizontal and vertical positions.

The Code

First, I’m going to just spit this out from the code-behind page and ignore all conventions regarding business layer, presentation layer, etc. This is just a demonstration to show feasibility and ease of development. So I made up a “Print” method that I use over and over to spit the html out to the browser:

public static class UI
{
    public static void Print(string psText)
    {
        HttpContext.Current.Response.Write(psText);
    }

....

There is also a header and footer method inside the UI class:

public static void Header()
{
    string lsHTML;
    HttpContext.Current.Response.Cache.SetExpires(DateTime.UtcNow.AddDays(-1));
    HttpContext.Current.Response.Cache.SetLastModified(DateTime.UtcNow.AddDays(0));
    HttpContext.Current.Response.Cache.SetCacheability(HttpCacheability.NoCache);

    lsHTML = @"
        <!DOCTYPE html>
        <head>
        <meta http-equiv='Content-type' content='text/html;charset=UTF-8' />         </head>
        <body>
        ";
    Print(lsHTML);
}

public static void Footer()
{
    Print("</body>");
    Print("</html>");
}

} // end of UI class

Now, it’s time to move on to the meat (I made it tiny font to fit the page, sorry if you wear glasses like I do):

using System;
using System.Collections.Generic;

namespace SVGDemo
{
    public partial class svg_demo1 : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            Render();
        }
        private void Render()
        {
            UI.Header();

            UI.Print(@"
                <svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='1000' height='1000'>
                ");
            SVGBarChart laSVGBarChart = new SVGBarChart(570, 300);
            laSVGBarChart.AddDataPoints("5,50,40 10,30,80 15,25,95 25,10,75 30,5,15 30,25,15");
            laSVGBarChart.Render();
            
            UI.Print(@"
                </svg>");
            UI.Footer();
        }
    }

    public class SVGBarChart
    {
        public int BarChartLeft = 570;
        public int BarChartBottom = 300;
        public List<SVGBarChartItem> Items = new List<SVGBarChartItem>();

        public SVGBarChart(int piLeft, int piBottom)
        {
            BarChartLeft = piLeft;
            BarChartBottom = piBottom;
        }
        public void AddDataPoints(string psDataPoints)
        {
            // expecting "x,y,z x,y,z"
            string[] laDataPoints = psDataPoints.Split(' ');
            for (int i = 0; i < laDataPoints.Length; i++)
            {
                string[] laSections = laDataPoints[i].Split(',');
                Items.Add(new SVGBarChartItem(laSections[0].ToInt(), laSections[1].ToInt(), 
                    laSections[2].ToInt()));
            }
        }
        public void Render()
        {
            DrawGrid(BarChartLeft - 50, BarChartBottom - 160, Items.Count * 50 + BarChartLeft, 
                BarChartBottom + 20);

            for (int i = Items.Count - 1; i > -1; i--)
            {
                Items[i].RenderBar(i * 50 + BarChartLeft, BarChartBottom);
            }
        }
        private void DrawGrid(int piLeft, int piTop, int piRight, int piBottom)
        {
            // horizontal lines
            for (int i = 0; i < (piBottom - piTop) / 10 + 1; i++)
            {
                UI.Print("<line x1='" + piLeft + "' y1='" + (piTop + i * 10) + "' x2='" + piRight + "' y2='" 
                    + (piTop + i * 10) + "' stroke='#aaaaaa' />");
            }
            // vertical lines
            for (int i = 0; i < (piRight - piLeft) / 10 + 1; i++)
            {
                UI.Print("<line x1='" + (piLeft + i * 10) + "' y1='" + piTop + "' x2='" + (piLeft + i * 10) 
                    + "' y2='" + piBottom + "' stroke='#aaaaaa' />");
            }
        }
    }

    public class SVGBarChartItem
    {
        int RedHeight;
        int YellowHeight;
        int GreenHeight;

        public SVGBarChartItem(int piRedHeight, int piYellowHeight, int piGreenHeight)
        {
            RedHeight = piRedHeight;
            YellowHeight = piYellowHeight;
            GreenHeight = piGreenHeight;
        }
        public void RenderBar(int piLeft, int piBottom)
        {
            // draw one bar
            int liWidth = 30;
            int liHorizontalOffset = 10;
            int liVerticalOffset = 6;
            int liTop1 = piBottom - GreenHeight - YellowHeight - RedHeight;
            int liTop2 = piBottom - GreenHeight - YellowHeight;
            int liTop3 = piBottom - GreenHeight;

            // top cap
            UI.Print("<polygon points='" + piLeft + @"," + liTop1 + @" " + (piLeft + liWidth) + @"," + 
                liTop1 + @" " + (piLeft + liWidth - liHorizontalOffset) + @"," + (liTop1 - liVerticalOffset) 
                + @" " + (piLeft - liHorizontalOffset) + @"," + (liTop1 - liVerticalOffset) + 
                @"' fill='#cc0000' />");

            // red bar front
            UI.Print("<rect width='30' height='" + (liTop2 - liTop1) + @"' x='" + piLeft + @"' y='" + liTop1 
                + @"' fill='#ff0000' />");

            // yellow bar front
            UI.Print("<rect width='30' height='" + (liTop3 - liTop2) + @"' x='" + piLeft + @"' y='" + liTop2 
                + @"' fill='#ffff00' />");

            // green bar front
            UI.Print("<rect width='30' height='" + (piBottom - liTop3) + @"' x='" + piLeft + @"' y='" + 
                liTop3 + @"' fill='#00ff00' />");

            // red bar side
            UI.Print("<polygon points='" + piLeft + @"," + liTop1 + @" " + piLeft + @"," + liTop2 + @" " + 
                (piLeft - liHorizontalOffset) + @"," + (liTop2 - liVerticalOffset) + @" " + (piLeft - 
                liHorizontalOffset) + @"," + (liTop1 - liVerticalOffset) + @"' fill='#aa0000' />");

            // yellow bar side
            UI.Print("<polygon points='" + piLeft + @"," + liTop2 + @" " + piLeft + @"," + liTop3 + @" " + 
                (piLeft - liHorizontalOffset) + @"," + (liTop3 - liVerticalOffset) + @" " + (piLeft - 
                liHorizontalOffset) + @"," + (liTop2 - liVerticalOffset) + @"' fill='#aaaa00' />");

            // green bar side
            UI.Print("<polygon points='" + piLeft + @"," + liTop3 + @" " + piLeft + @"," + piBottom + @" " + 
                (piLeft - liHorizontalOffset) + @"," + (piBottom - liVerticalOffset) + @" " + (piLeft - 
                liHorizontalOffset) + @"," + (liTop3 - liVerticalOffset) + @"' fill='#00aa00' />");
        }
    }
}

First, I threw a “render()” method together and called it from the main call-behind method. I just did that to keep the main method clean. Inside the primary render method I print out the header and starting SVG code. Then I instantiate the SVGBarChart object and fill some sample data.

If you look closely at the SVGBarChart object, you’ll notice that the AddDataPoints method accepts a string but does not do any input checking. Never do this in a production system! Always verify your data, even if you are the one writing both the method and the call to the method. In this instance I assume that the input will be formatted in a string containing groups of 3 numbers separated by commas (each group separated by a space).

The DrawGrid method will spit out the horizontal and vertical lines for the background grid. This is called from within the SVGBarChart.Render() method.

Each bar is handled from a separate object called SVGBarChartItem. The main object (SVGBarChart) contains a list of these objects, one per bar.

There is a “ToInt()” method that I did not include in this code. You can change the code to cast into an integer or download the code here:

SVGDemo zip file

The entire demo project is included in this zip file. It’s in Visual Studio 2012, but you can just copy the contents of the Default.cs file into your startup website code-behind page. Be sure and remove all but the top line of the aspx page. Otherwise, you’ll end up with some unwanted HTML code.

Summary

The whole point of this blog post was to show the power and ease of use of the SVG capabilities of modern browsers.  This code does not need a plug-in and is compatible with Opera, Chrome, Safari, FireFox and IE9 and above.  My go-to source for SVG object syntax is w3c schools (click here).  Start using SVG and let your imagination run wild.

Leave a Reply