I've seen this request many times:
How do I make a Chart in Silverlight?
I'm going to show you that with some simple math and a little bit Silverlight knowledge, you too, can enjoy the clarity of a chart on your web page.
Technically, the goal is to build a simple reusable horizontal bar chart using Silverlight 1.0 and dynamic data. The code will be written in XAML and JavaScript and I will be using Visual Studio 2008 as my editor. How you get the data to the app is up to you, but to keep this simple, I will be using local JSON formatted data.
Creating the Application
The first step is the application creation. I will base my project on the Silverlight Script Web template that comes with the Silverlight Tools Alpha for Visual Studio 2008. The majority of the project will remain relatively unchanged and most of the work will be done in the Chart Control files to be added.
- File -> New -> Web Site...
- Choose the Silverlight Script Web template
- Give the project a name, Let's use SilverlightBarChart
- Hit OK
Now we need to clear the template XAML and JavaScript:
- Open Scene.xaml
- Clear the contents of the root Canvas, only the opening and closing Canvas elements should remain.
- Set the x:Name property of the root Canvas to mainCanvas
- Open Scene.js
- Remove all event handler methods, except handleLoad
- Make sure you remove the comma after the handleLoad method
- Clear the contents of the handleLoad method
We've created a clean slate. If you hit Run, you will see a stark white page. A chart is more than a white page, though, so let's start adding the components.
Adding the Background and Container
The first piece we need is the background. Not only does it serve the purpose of a contrasting color, but also it acts as the container for the bars.
- File -> New -> File...
- Choose the Silverlight JScript Page template
- Name the file Chart.xaml
- In the root Canvas of the Chart.xaml file add the following XAML:
The Rectangle is used to style the background and the Canvas is a named container for us to reference in the code to dynamically add bars.
- Return to Scene.js
- Add the following code in the handleLoad method:
this.plugIn = plugIn;
this.mainCanvas = plugIn.content.findName("mainCanvas");
this.downloader = this.plugIn.createObject("downloader");
this.downloader.addEventListener("completed", Silverlight.createDelegate(this, this.onCompleted));
this.downloader.open("GET", "Chart.xaml");
this.downloader.send();
The first two lines are setting variables for later use. this.plugIn refers to the Silverlight application instance. this.mainCanvas is a reference to the named Canvas in the Scene.xaml.
The next four lines create a Downloader object which is set to asynchronously fetch the Chart.xaml file. Upon completion of downloading the file it will call the onCompleted method. Now let's add that method so it can be successfully called.
- In the Scene.js file after the handleLoad method's closing bracket, add a comma
- After the comma add the onCompleted method as seen below:
onCompleted: function(sender, args)
{
var chartTarget = this.plugIn.content.createFromXamlDownloader(sender, "");
this.mainCanvas.children.add(chartTarget);
}
This function is called after the downloader object has completed downloading the Chart.xaml file. sender is a reference to the downloader object which now holds the content of the xaml file. Using the createFromXamlDownloader method, we create a new Silverlight object based on the downloader instance. The new object is then added to the children collection of mainCanvas in order for the object to render and become interactive.
Now hit Run and you will see an asynchronously loaded gray rectangle. We are really cooking now.
Adding the Bars
It takes two steps to add the bars to the chart. First we need to create the reusable bars as separate components and then we need to add code to the Chart to create the bars dynamically based on the data.
Let's start by creating the bar component.
- File -> New -> File...
- Choose the Silverlight JScript Page template
- Name the file Bar.xaml
- In the root Canvas of the Bar.xaml file add the following XAML:
Our bar is simply a Canvas painted with a LinearGradientBrush ranging from light blue to dark blue.
Now we'll modify the Chart class so it uses the bar control to display the data. The majority of the code here is the math used to size and position the bars properly.
- Open Chart.js
- Add the following parameters to the SilverlightBarChart.Chart function: plugIn, target, data
- Within the brackets of the SilverlightBarChart.Chart function add the following code:
this.plugIn = plugIn;
this.target = target;
this.data = data;
this.barContainer = this.target.findName("barContainer");
this.bars = [];
this.padding = 5;
this.height = 480;
this.width = 640;
The first three lines store the parameters for later use. this.plugIn refers to the Silverlight application instance. this.target represents the Silverlight object that our JavaScript class is wrapping and this.data represents the JSON formatted data.
The next five lines set properties for rendering the dynamic bars. this.barContainer stores a reference to the container Canvas defined in the Chart.xaml file. this.bars is an Array we will use to store references to the Bar JavaScript instances. this.padding, this.height and this.width are all properties used to calculate the size and position of each bar.
- Move down to the SilverlightBarChart.Chart.prototype and remove the handleLoad method
- Add the following code within the brackets of the SilverlightBarChart.Chart.protoype:
createBars: function(barXaml)
{
var barTarget = null;
var barCount = 0;
var barWidth = 0;
var barHeight = 0;
var maxValue = 0;
for(i in this.data)
{
barCount++;
if(this.data[i].value > maxValue)
{
maxValue = this.data[i].value;
}
}
barWidth = (this.width - (this.padding * (barCount + 1))) / barCount;
for(var i = 0; i < barCount; i++)
{
barTarget = this.plugIn.content.createFromXaml(barXaml, true);
this.barContainer.children.add(barTarget);
barHeight = this.height * this.data[i].value / maxValue - this.padding;
barTarget["Canvas.Top"] = this.height - barHeight;
barTarget["Canvas.Left"] = barWidth * i + (this.padding * (i + 1));
barTarget.Width = barWidth;
barTarget.Height = barHeight;
this.bars.push(new SilverlightBarChart.Bar(barTarget));
}
}
Here's the magic method you've been waiting for. Initially we setup a few variables for reuse. Second we loop through the properties of the data object in order to obtain the total count and the largest value. This is done mainly because the JSON data is stored as an object and other than enumerating the properties there is no other way to simply get these values. This initial loop could be avoided with more complex data structures or even by setting the discovered values directly.
Once we we've looped through the data we can figure out how wide each bar should be to fill up the chart, based on the chart width, padding and total bars.
Then we perform a second loop to create and set the properties of each bar. The first line in the loop uses createFromXaml and the XAML string passed as a parameter to this method to create the Silverlight object for the bar. Then we add the bar to the barContainer so it will render and become interactive.
The following 5 lines dynamically set the height and width of the bar along with its position. These numbers are calculated based off the size of the chart, the amount of bars and the value of each bar in comparison to the greatest value in the data set.
Don't hit Run yet, nothing new will happen.
Initializing the Chart
With our Chart and Bar components complete we can initialize the Chart in our main application.
- Open Scene.js
- In the handleLoad method, after the line that starts with this.mainCanvas... add the following line: this.chart = null;
- Remove the code within the brackets of the onCompleted method and add the following code:
if (sender.uri == "Chart.xaml")
{
var chartTarget = this.plugIn.content.createFromXamlDownloader(sender, "");
this.mainCanvas.children.add(chartTarget);
this.chart = new SilverlightBarChart.Chart(this.plugIn, chartTarget, chartData)
this.downloader.open("GET", "Bar.xaml");
this.downloader.send();
}
else
{
this.chart.createBars(sender.ResponseText);
}
What we've done here is daisy chain the asynchronous completed events of the downloader. First in the handleLoad method we asked the downloader to go get Chart.xaml. Once it completes that request, we create a new instance of the Chart class using that response then we ask the downloader to go get the Bar.xaml file. Once it has that response, we call the createBars method of the new Chart instance, passing the ResponseText as the barXaml parameter.
The script is set up now but we need to add a few JavaScript references in the Default.html in order for the newly created script classes to be resolved.
- Open Default.html
- Locate the script tags at the top of the page and change them to match the following code:
First, we still need a reference to the Silverlight.js which will provide the application helper functions. Then a reference to the JSON data file which we will create next. Followed by the class files of our two components and our application.
- File -> New -> File...
- Select the JScriptFile Template
- Name the file chartData.js
- Open the file and add JSON formatted data as follows:
chartData = [{"value":285}, {"value":342}, {"value":248}, {"value":120}, {"value":204}, {"value":190}, {"value":255}, {"value":238}, {"value":501}, {"value":297}];
The values are completely arbitrary but the script is looking for properties named value with numerically typed values. So feel free to change the values, but don't change the format of the object properties until you feel comfortable changing the script.
Hit Run and you should see a chart similar to the picture below:
Success! You have a chart! Awesome!
"But what about..."
Yes, there is plenty more you could do with this chart, but it is all outside of the scope of this tutorial. Many different features come to mind such as:
- Different data sources
- Assets packaged into one .zip file
- Loading data animations
- Easing animations for the bars
- Different visual styles for the chart and bars ( Expression Blend is waiting for you )
- Different types of Charts
- Values on Bar Rollover (This is where the Bar JavaScript class would come in handy)
Hopefully this tutorial has shown you its not that difficult to get started and it can be fun once you start getting seeing the results of your work. I did not explain every single piece of code, but I did try and highlight the key concepts. If you have questions I would suggest starting with the Silverlight API reference. That's my favorite place to start.
And since you have made it all the way through this tutorial or even if you just scrolled to the bottom, you can download the completed source directly.
Cool. That's one line I can check off my Must-Write-About-Because-Its-Constantly-Requested list...
Enjoy!