ASP.NET charts example: Odin Sphere: Part 3 - Creating the chart

In part one of this series we covered what we'd be doing, and what data model we'd be using.

In part two of this series we used LINQ to XML to query the XML file with the data we want to display.

This time we'll be doing the heavy lifting of actually creating the chart and displaying it to the user. For ease, I'll be implementing very basic caching.

Preliminary requirement

Before you can use the charting functionality you need to have a reference to System.Web.DataVisualization. We can then use this in our handler as below.

using System.Web.UI.DataVisualization.Charting;

Next we can do the heavy lifting of creating the basics of the chart:

// Create a new chart, and set the basic properties of it.
Chart hpChart = new Chart();
hpChart.Width = 800;
hpChart.Height = 500;
hpChart.Titles.Add("Odin Sphere HP leveling");
hpChart.Palette = ChartColorPalette.Bright;
hpChart.Legends.Add("Main");
hpChart.Legends[0].LegendStyle = LegendStyle.Row;
hpChart.Legends[0].Docking = Docking.Bottom;
// Create a new area for the main chart to display within.
ChartArea mainArea = new ChartArea("Main chart");
// Set the properties for the x-axis.
mainArea.AxisX.Name = "Level";
mainArea.AxisX.Title = "Level";
mainArea.AxisX.MajorGrid.LineColor = System.Drawing.Color.DimGray;
mainArea.AxisX.MinorGrid.Enabled = true;
mainArea.AxisX.MinorGrid.LineColor = System.Drawing.Color.LightGray;
// Set the properties for the y-axis.
mainArea.AxisY.Name = "Hit points";
mainArea.AxisY.Title = "Hit points";
mainArea.AxisY.MajorGrid.LineColor = System.Drawing.Color.DimGray;
mainArea.AxisY.MinorGrid.Enabled = true;
mainArea.AxisY.MinorGrid.LineColor = System.Drawing.Color.LightGray;
mainArea.AxisY.MinorGrid.Interval = 50;
hpChart.ChartAreas.Add(mainArea);

With our chart created we can now add our data.

foreach (Character character in characterData) {
    // Add a new series of points for each character.
    Series characterSeries = new Series();
    characterSeries.Name = character.Name;
    characterSeries.ChartType = SeriesChartType.Line;
    foreach (HpLevel characterLevel in character.HpLevels) {
        // Add a point for each level recorded.
        characterSeries.Points.AddXY(characterLevel.Level, characterLevel.HitPoints);
    }
    hpChart.Series.Add(characterSeries);
}

Since we want to cache the chart, we'll add an informational message.

// Add a new informational title.
Title cacheTitle = new Title("Cached " + DateTime.Now.ToString() + " and based on http://jamesrskemp.com/files/OdinSphere.xml");
cacheTitle.Docking = Docking.Bottom;
hpChart.Titles.Add(cacheTitle);

Next we'll set the rendering type of the chart and add it to the cache.

hpChart.RenderType = RenderType.BinaryStreaming;
// Cache our object for an amount of time
HttpRuntime.Cache.Add("OdinSphereChart", hpChart, null, DateTime.Now.AddMinutes(5), System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.Low, null);

Everything above, as well as the XDocument load from part two, can be wrapped by a check for whether the chart is in the cache.

// Determine whether the chart is already cached.
if (HttpRuntime.Cache["OdinSphereChart"] == null) {
//...
}

And then we can finally output the chart to the user.

// Output the cached chart to the browser.
using (System.IO.MemoryStream stream = new System.IO.MemoryStream()) {
    ((Chart)HttpRuntime.Cache["OdinSphereChart"]).SaveImage(stream);
    context.Response.ContentType = "image/png";
    context.Response.BinaryWrite(stream.GetBuffer());
}

Final code

At the end of our exercise, our handler (OdinSphere.ashx) looks something like the following:

<%@ WebHandler Language="C#" Class="OdinSphere" %>

using System;
using System.Web;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Text;
using System.Web.UI.DataVisualization.Charting;

public class OdinSphere : IHttpHandler {

    /// <summary>
    /// One of the five playable characters in Odin Sphere, for the Playstation 2.
    /// </summary>
    public class Character {
        /// <summary>
        /// Name of the character.
        /// </summary>
        public String Name { get; set; }
        /// <summary>
        /// List of hit point leveling information.
        /// </summary>
        public List<HpLevel> HpLevels { get; set; }
    }

    /// <summary>
    /// Hit point information at a particular level.
    /// </summary>
    public class HpLevel {
        /// <summary>
        /// Level of the character.
        /// </summary>
        public int Level { get; set; }
        /// <summary>
        /// Hit points at a level, for a character.
        /// </summary>
        public int HitPoints { get; set; }
    }

    public void ProcessRequest(HttpContext context) {
        // Determine whether the chart is already cached.
        if (HttpRuntime.Cache["OdinSphereChart"] == null) {
            // Grab the current data.
            XDocument dataFile = XDocument.Load("http://jamesrskemp.com/files/OdinSphere.xml");
            IEnumerable<Character> characterData = from characters in dataFile.Descendants("Character")
                                                   select new Character {
                                                       Name = characters.Attribute("name").Value,
                                                       HpLevels = (from levels in characters.Element("HP").Element("Levels").Descendants("Level")
                                                                   select new HpLevel {
                                                                       Level = int.Parse(levels.Attribute("id").Value),
                                                                       HitPoints = int.Parse(levels.Attribute("hitPoints").Value)
                                                                   }
                                                       ).ToList()
                                                   };

            // Create a new chart, and set the basic properties of it.
            Chart hpChart = new Chart();
            hpChart.Width = 800;
            hpChart.Height = 500;
            hpChart.Titles.Add("Odin Sphere HP leveling");
            hpChart.Palette = ChartColorPalette.Bright;
            hpChart.Legends.Add("Main");
            hpChart.Legends[0].LegendStyle = LegendStyle.Row;
            hpChart.Legends[0].Docking = Docking.Bottom;
            // Create a new area for the main chart to display within.
            ChartArea mainArea = new ChartArea("Main chart");
            // Set the properties for the x-axis.
            mainArea.AxisX.Name = "Level";
            mainArea.AxisX.Title = "Level";
            mainArea.AxisX.MajorGrid.LineColor = System.Drawing.Color.DimGray;
            mainArea.AxisX.MinorGrid.Enabled = true;
            mainArea.AxisX.MinorGrid.LineColor = System.Drawing.Color.LightGray;
            // Set the properties for the y-axis.
            mainArea.AxisY.Name = "Hit points";
            mainArea.AxisY.Title = "Hit points";
            mainArea.AxisY.MajorGrid.LineColor = System.Drawing.Color.DimGray;
            mainArea.AxisY.MinorGrid.Enabled = true;
            mainArea.AxisY.MinorGrid.LineColor = System.Drawing.Color.LightGray;
            mainArea.AxisY.MinorGrid.Interval = 50;
            hpChart.ChartAreas.Add(mainArea);

            foreach (Character character in characterData) {
                // Add a new series of points for each character.
                Series characterSeries = new Series();
                characterSeries.Name = character.Name;
                characterSeries.ChartType = SeriesChartType.Line;
                foreach (HpLevel characterLevel in character.HpLevels) {
                    // Add a point for each level recorded.
                    characterSeries.Points.AddXY(characterLevel.Level, characterLevel.HitPoints);
                }
                hpChart.Series.Add(characterSeries);
            }

            // Add a new informational title.
            Title cacheTitle = new Title("Cached " + DateTime.Now.ToString() + " and based on http://jamesrskemp.com/files/OdinSphere.xml");
            cacheTitle.Docking = Docking.Bottom;
            hpChart.Titles.Add(cacheTitle);
            
            hpChart.RenderType = RenderType.BinaryStreaming;
            // Cache our object for an amount of time
            HttpRuntime.Cache.Add("OdinSphereChart", hpChart, null, DateTime.Now.AddMinutes(5), System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.Low, null);
        }

        // Output the cached chart to the browser.
        using (System.IO.MemoryStream stream = new System.IO.MemoryStream()) {
            ((Chart)HttpRuntime.Cache["OdinSphereChart"]).SaveImage(stream);
            context.Response.ContentType = "image/png";
            context.Response.BinaryWrite(stream.GetBuffer());
        }
    }
 
    public bool IsReusable {
        get {
            return false;
        }
    }
}

You can see this in action online.